From 4fbf990ccbb0862b2aad8d69954efb3a7e1f5d39 Mon Sep 17 00:00:00 2001 From: wangzijun Date: Fri, 14 Oct 2022 16:50:42 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=B5=84=E6=BA=90=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jsm/animation/AnimationClipCreator.js | 114 + jsm/animation/CCDIKSolver.js | 458 + jsm/animation/MMDAnimationHelper.js | 1207 + jsm/animation/MMDPhysics.js | 1400 + jsm/cameras/CinematicCamera.js | 209 + jsm/capabilities/WebGL.js | 91 + jsm/capabilities/WebGPU.js | 33 + jsm/controls/ArcballControls.js | 3201 ++ jsm/controls/DragControls.js | 220 + jsm/controls/FirstPersonControls.js | 332 + jsm/controls/FlyControls.js | 292 + jsm/controls/OrbitControls.js | 1252 + jsm/controls/PointerLockControls.js | 164 + jsm/controls/TrackballControls.js | 805 + jsm/controls/TransformControls.js | 1556 + jsm/controls/experimental/CameraControls.js | 1248 + jsm/csm/CSM.js | 377 + jsm/csm/CSMFrustum.js | 152 + jsm/csm/CSMHelper.js | 163 + jsm/csm/CSMShader.js | 251 + jsm/curves/CurveExtras.js | 422 + jsm/curves/NURBSCurve.js | 80 + jsm/curves/NURBSSurface.js | 52 + jsm/curves/NURBSUtils.js | 487 + jsm/deprecated/Geometry.js | 1869 + jsm/effects/AnaglyphEffect.js | 168 + jsm/effects/AsciiEffect.js | 279 + jsm/effects/OutlineEffect.js | 574 + jsm/effects/ParallaxBarrierEffect.js | 116 + jsm/effects/PeppersGhostEffect.js | 153 + jsm/effects/StereoEffect.js | 55 + jsm/environments/DebugEnvironment.js | 52 + jsm/environments/RoomEnvironment.js | 121 + jsm/exporters/ColladaExporter.js | 722 + jsm/exporters/DRACOExporter.js | 238 + jsm/exporters/EXRExporter.js | 507 + jsm/exporters/GLTFExporter.js | 2647 + jsm/exporters/MMDExporter.js | 217 + jsm/exporters/OBJExporter.js | 302 + jsm/exporters/PLYExporter.js | 529 + jsm/exporters/STLExporter.js | 203 + jsm/exporters/USDZExporter.js | 547 + jsm/geometries/BoxLineGeometry.js | 69 + jsm/geometries/ConvexGeometry.js | 59 + jsm/geometries/DecalGeometry.js | 363 + jsm/geometries/LightningStrike.js | 1017 + jsm/geometries/ParametricGeometries.js | 254 + jsm/geometries/ParametricGeometry.js | 135 + jsm/geometries/RoundedBoxGeometry.js | 155 + jsm/geometries/TeapotGeometry.js | 704 + jsm/geometries/TextGeometry.js | 57 + jsm/helpers/LightProbeHelper.js | 130 + jsm/helpers/OctreeHelper.js | 58 + jsm/helpers/PositionalAudioHelper.js | 109 + jsm/helpers/RectAreaLightHelper.js | 85 + jsm/helpers/VertexNormalsHelper.js | 95 + jsm/helpers/VertexTangentsHelper.js | 81 + jsm/helpers/ViewHelper.js | 295 + jsm/interactive/HTMLMesh.js | 386 + jsm/interactive/InteractiveGroup.js | 114 + jsm/interactive/SelectionBox.js | 227 + jsm/interactive/SelectionHelper.js | 95 + jsm/libs/OimoPhysics/OimoPhysics.js | 37071 ++++++++++++ jsm/libs/OimoPhysics/index.js | 43 + jsm/libs/chevrotain.module.min.js | 141 + jsm/libs/ecsy.module.js | 1792 + jsm/libs/fflate.module.js | 2474 + jsm/libs/flow.module.js | 4102 ++ jsm/libs/ktx-parse.module.js | 1 + jsm/libs/lil-gui.module.min.js | 8 + jsm/libs/meshopt_decoder.module.js | 113 + jsm/libs/mikktspace.module.js | 128 + jsm/libs/mmdparser.module.js | 11530 ++++ jsm/libs/motion-controllers.module.js | 397 + jsm/libs/opentype.module.js | 14568 +++++ jsm/libs/potpack.module.js | 125 + jsm/libs/rhino3dm/rhino3dm.js | 21 + jsm/libs/rhino3dm/rhino3dm.module.js | 16 + jsm/libs/rhino3dm/rhino3dm.wasm | Bin 0 -> 2803968 bytes jsm/libs/stats.module.js | 167 + jsm/libs/tween.module.min.js | 3 + jsm/lights/LightProbeGenerator.js | 250 + jsm/lights/RectAreaLightUniformsLib.js | 79 + jsm/lines/Line2.js | 19 + jsm/lines/LineGeometry.js | 87 + jsm/lines/LineMaterial.js | 702 + jsm/lines/LineSegments2.js | 355 + jsm/lines/LineSegmentsGeometry.js | 250 + jsm/lines/Wireframe.js | 56 + jsm/lines/WireframeGeometry2.js | 24 + jsm/loaders/3DMLoader.js | 1494 + jsm/loaders/3MFLoader.js | 1473 + jsm/loaders/AMFLoader.js | 518 + jsm/loaders/BVHLoader.js | 437 + jsm/loaders/BasisTextureLoader.js | 790 + jsm/loaders/ColladaLoader.js | 4091 ++ jsm/loaders/DDSLoader.js | 281 + jsm/loaders/DRACOLoader.js | 587 + jsm/loaders/EXRLoader.js | 2320 + jsm/loaders/FBXLoader.js | 4128 ++ jsm/loaders/FontLoader.js | 196 + jsm/loaders/GCodeLoader.js | 262 + jsm/loaders/GLTFLoader.js | 4402 ++ jsm/loaders/HDRCubeTextureLoader.js | 128 + jsm/loaders/IFCLoader.js | 2431 + jsm/loaders/KMZLoader.js | 130 + jsm/loaders/KTX2Loader.js | 591 + jsm/loaders/KTXLoader.js | 176 + jsm/loaders/LDrawLoader.js | 2403 + jsm/loaders/LUT3dlLoader.js | 151 + jsm/loaders/LUTCubeLoader.js | 153 + jsm/loaders/LWOLoader.js | 1067 + jsm/loaders/LogLuvLoader.js | 606 + jsm/loaders/LottieLoader.js | 73 + jsm/loaders/MD2Loader.js | 399 + jsm/loaders/MDDLoader.js | 102 + jsm/loaders/MMDLoader.js | 2224 + jsm/loaders/MTLLoader.js | 567 + jsm/loaders/NRRDLoader.js | 672 + jsm/loaders/OBJLoader.js | 913 + jsm/loaders/PCDLoader.js | 413 + jsm/loaders/PDBLoader.js | 227 + jsm/loaders/PLYLoader.js | 564 + jsm/loaders/PRWMLoader.js | 299 + jsm/loaders/PVRLoader.js | 251 + jsm/loaders/RGBELoader.js | 476 + jsm/loaders/RGBMLoader.js | 1065 + jsm/loaders/STLLoader.js | 399 + jsm/loaders/SVGLoader.js | 2949 + jsm/loaders/TDSLoader.js | 1124 + jsm/loaders/TGALoader.js | 517 + jsm/loaders/TTFLoader.js | 221 + jsm/loaders/TiltLoader.js | 514 + jsm/loaders/VOXLoader.js | 305 + jsm/loaders/VRMLLoader.js | 3483 ++ jsm/loaders/VTKLoader.js | 1154 + jsm/loaders/XYZLoader.js | 100 + jsm/loaders/ifc/web-ifc-api.js | 47504 ++++++++++++++++ jsm/loaders/ifc/web-ifc.wasm | Bin 0 -> 428259 bytes jsm/loaders/lwo/IFFParser.js | 1218 + jsm/loaders/lwo/LWO2Parser.js | 416 + jsm/loaders/lwo/LWO3Parser.js | 375 + jsm/math/Capsule.js | 137 + jsm/math/ColorConverter.js | 84 + jsm/math/ConvexHull.js | 1280 + jsm/math/ImprovedNoise.js | 71 + jsm/math/Lut.js | 203 + jsm/math/MeshSurfaceSampler.js | 211 + jsm/math/OBB.js | 423 + jsm/math/Octree.js | 462 + jsm/math/SimplexNoise.js | 444 + jsm/misc/ConvexObjectBreaker.js | 525 + jsm/misc/GPUComputationRenderer.js | 411 + jsm/misc/Gyroscope.js | 66 + jsm/misc/MD2Character.js | 276 + jsm/misc/MD2CharacterComplex.js | 576 + jsm/misc/MorphAnimMesh.js | 75 + jsm/misc/MorphBlendMesh.js | 322 + jsm/misc/ProgressiveLightMap.js | 321 + jsm/misc/RollerCoaster.js | 560 + jsm/misc/TubePainter.js | 205 + jsm/misc/Volume.js | 462 + jsm/misc/VolumeSlice.js | 229 + jsm/modifiers/CurveModifier.js | 325 + jsm/modifiers/EdgeSplitModifier.js | 286 + jsm/modifiers/SimplifyModifier.js | 532 + jsm/modifiers/TessellateModifier.js | 314 + jsm/node-editor/NodeEditor.js | 778 + jsm/node-editor/accessors/MatcapUVEditor.js | 14 + jsm/node-editor/accessors/NormalEditor.js | 30 + jsm/node-editor/accessors/PositionEditor.js | 30 + jsm/node-editor/accessors/UVEditor.js | 25 + jsm/node-editor/core/BaseNode.js | 89 + jsm/node-editor/core/DataFile.js | 59 + jsm/node-editor/core/FileEditor.js | 20 + jsm/node-editor/core/FileURLEditor.js | 29 + jsm/node-editor/display/BlendEditor.js | 44 + jsm/node-editor/display/NormalMapEditor.js | 49 + jsm/node-editor/examples/animate-uv.json | 1 + jsm/node-editor/examples/fake-top-light.json | 1 + jsm/node-editor/examples/matcap.json | 1 + .../examples/oscillator-color.json | 1 + jsm/node-editor/examples/rim.json | 1 + jsm/node-editor/inputs/ColorEditor.js | 96 + jsm/node-editor/inputs/FloatEditor.js | 23 + jsm/node-editor/inputs/SliderEditor.js | 67 + jsm/node-editor/inputs/TextureEditor.js | 155 + jsm/node-editor/inputs/Vector2Editor.js | 28 + jsm/node-editor/inputs/Vector3Editor.js | 30 + jsm/node-editor/inputs/Vector4Editor.js | 37 + .../materials/BasicMaterialEditor.js | 87 + .../materials/PointsMaterialEditor.js | 102 + .../materials/StandardMaterialEditor.js | 121 + jsm/node-editor/math/AngleEditor.js | 40 + jsm/node-editor/math/DotEditor.js | 35 + jsm/node-editor/math/InvertEditor.js | 39 + jsm/node-editor/math/LimiterEditor.js | 62 + jsm/node-editor/math/NormalizeEditor.js | 28 + jsm/node-editor/math/OperatorEditor.js | 63 + jsm/node-editor/math/PowerEditor.js | 44 + jsm/node-editor/math/TrigonometryEditor.js | 45 + jsm/node-editor/procedural/CheckerEditor.js | 27 + jsm/node-editor/scene/MeshEditor.js | 99 + jsm/node-editor/scene/Object3DEditor.js | 160 + jsm/node-editor/scene/PointsEditor.js | 99 + jsm/node-editor/utils/JoinEditor.js | 58 + jsm/node-editor/utils/OscillatorEditor.js | 43 + jsm/node-editor/utils/PreviewEditor.js | 166 + jsm/node-editor/utils/SplitEditor.js | 39 + jsm/node-editor/utils/TimerEditor.js | 58 + jsm/nodes/Nodes.js | 263 + jsm/nodes/accessors/BufferNode.js | 24 + jsm/nodes/accessors/CameraNode.js | 67 + jsm/nodes/accessors/CubeTextureNode.js | 76 + jsm/nodes/accessors/InstanceNode.js | 58 + jsm/nodes/accessors/MaterialNode.js | 114 + jsm/nodes/accessors/MaterialReferenceNode.js | 23 + jsm/nodes/accessors/ModelNode.js | 13 + .../accessors/ModelViewProjectionNode.js | 30 + jsm/nodes/accessors/NormalNode.js | 79 + jsm/nodes/accessors/Object3DNode.js | 121 + jsm/nodes/accessors/PointUVNode.js | 21 + jsm/nodes/accessors/PositionNode.js | 83 + jsm/nodes/accessors/ReferenceNode.js | 65 + jsm/nodes/accessors/ReflectNode.js | 63 + jsm/nodes/accessors/SkinningNode.js | 102 + jsm/nodes/accessors/TextureNode.js | 100 + jsm/nodes/accessors/UVNode.js | 41 + jsm/nodes/core/ArrayUniformNode.js | 23 + jsm/nodes/core/AttributeNode.js | 54 + jsm/nodes/core/BypassNode.js | 38 + jsm/nodes/core/CodeNode.js | 50 + jsm/nodes/core/ConstNode.js | 23 + jsm/nodes/core/ContextNode.js | 38 + jsm/nodes/core/ExpressionNode.js | 32 + jsm/nodes/core/FunctionCallNode.js | 67 + jsm/nodes/core/FunctionNode.js | 105 + jsm/nodes/core/InputNode.js | 62 + jsm/nodes/core/InstanceIndexNode.js | 21 + jsm/nodes/core/Node.js | 244 + jsm/nodes/core/NodeAttribute.js | 14 + jsm/nodes/core/NodeBuilder.js | 735 + jsm/nodes/core/NodeCode.js | 15 + jsm/nodes/core/NodeFrame.js | 59 + jsm/nodes/core/NodeFunction.js | 22 + jsm/nodes/core/NodeFunctionInput.js | 17 + jsm/nodes/core/NodeKeywords.js | 80 + jsm/nodes/core/NodeParser.js | 11 + jsm/nodes/core/NodeUniform.js | 28 + jsm/nodes/core/NodeUtils.js | 103 + jsm/nodes/core/NodeVar.js | 14 + jsm/nodes/core/NodeVary.js | 14 + jsm/nodes/core/PropertyNode.js | 36 + jsm/nodes/core/TempNode.js | 44 + jsm/nodes/core/UniformNode.js | 40 + jsm/nodes/core/VarNode.js | 60 + jsm/nodes/core/VaryNode.js | 54 + jsm/nodes/core/constants.js | 21 + jsm/nodes/display/ColorSpaceNode.js | 94 + jsm/nodes/display/NormalMapNode.js | 90 + jsm/nodes/fog/FogNode.js | 24 + jsm/nodes/fog/FogRangeNode.js | 27 + jsm/nodes/functions/BSDFs.js | 123 + .../functions/PhysicalMaterialFunctions.js | 25 + jsm/nodes/lights/LightContextNode.js | 60 + jsm/nodes/lights/LightNode.js | 84 + jsm/nodes/lights/LightsNode.js | 111 + jsm/nodes/loaders/NodeLoader.js | 107 + jsm/nodes/loaders/NodeMaterialLoader.js | 42 + jsm/nodes/loaders/NodeObjectLoader.js | 70 + jsm/nodes/materials/LineBasicNodeMaterial.js | 46 + jsm/nodes/materials/Materials.js | 62 + jsm/nodes/materials/MeshBasicNodeMaterial.js | 48 + .../materials/MeshStandardNodeMaterial.js | 119 + jsm/nodes/materials/NodeMaterial.js | 208 + jsm/nodes/materials/PointsNodeMaterial.js | 52 + jsm/nodes/math/CondNode.js | 60 + jsm/nodes/math/MathNode.js | 258 + jsm/nodes/math/OperatorNode.js | 206 + jsm/nodes/parsers/GLSLNodeFunction.js | 137 + jsm/nodes/parsers/GLSLNodeParser.js | 14 + jsm/nodes/parsers/WGSLNodeFunction.js | 96 + jsm/nodes/parsers/WGSLNodeParser.js | 14 + jsm/nodes/procedural/CheckerNode.js | 35 + jsm/nodes/shadernode/ShaderNode.js | 5 + jsm/nodes/shadernode/ShaderNodeElements.js | 231 + jsm/nodes/shadernode/ShaderNodeUtils.js | 235 + jsm/nodes/utils/ArrayElementNode.js | 31 + jsm/nodes/utils/ConvertNode.js | 41 + jsm/nodes/utils/JoinNode.js | 42 + jsm/nodes/utils/MatcapUVNode.js | 25 + jsm/nodes/utils/OscNode.js | 74 + jsm/nodes/utils/SplitNode.js | 86 + jsm/nodes/utils/SpriteSheetUVNode.js | 58 + jsm/nodes/utils/TimerNode.js | 64 + jsm/objects/Lensflare.js | 378 + jsm/objects/LightningStorm.js | 245 + jsm/objects/MarchingCubes.js | 1176 + jsm/objects/Reflector.js | 261 + jsm/objects/ReflectorForSSRPass.js | 351 + jsm/objects/Refractor.js | 325 + jsm/objects/ShadowMesh.js | 74 + jsm/objects/Sky.js | 219 + jsm/objects/Water.js | 329 + jsm/objects/Water2.js | 358 + jsm/offscreen/jank.js | 45 + jsm/offscreen/offscreen.js | 8 + jsm/offscreen/scene.js | 86 + jsm/package.json | 3 + jsm/physics/AmmoPhysics.js | 286 + jsm/physics/OimoPhysics.js | 231 + jsm/postprocessing/AdaptiveToneMappingPass.js | 369 + jsm/postprocessing/AfterimagePass.js | 90 + jsm/postprocessing/BloomPass.js | 150 + jsm/postprocessing/BokehPass.js | 131 + jsm/postprocessing/ClearPass.js | 46 + jsm/postprocessing/CubeTexturePass.js | 78 + jsm/postprocessing/DotScreenPass.js | 58 + jsm/postprocessing/EffectComposer.js | 310 + jsm/postprocessing/FilmPass.js | 59 + jsm/postprocessing/GlitchPass.js | 118 + jsm/postprocessing/HalftonePass.js | 77 + jsm/postprocessing/LUTPass.js | 173 + jsm/postprocessing/MaskPass.js | 101 + jsm/postprocessing/OutlinePass.js | 634 + jsm/postprocessing/Pass.js | 80 + jsm/postprocessing/RenderPass.js | 81 + jsm/postprocessing/SAOPass.js | 413 + jsm/postprocessing/SMAAPass.js | 188 + jsm/postprocessing/SSAARenderPass.js | 224 + jsm/postprocessing/SSAOPass.js | 450 + jsm/postprocessing/SSRPass.js | 645 + jsm/postprocessing/SavePass.js | 62 + jsm/postprocessing/ShaderPass.js | 68 + jsm/postprocessing/TAARenderPass.js | 167 + jsm/postprocessing/TexturePass.js | 60 + jsm/postprocessing/UnrealBloomPass.js | 405 + jsm/renderers/CSS2DRenderer.js | 219 + jsm/renderers/CSS3DRenderer.js | 313 + jsm/renderers/Projector.js | 967 + jsm/renderers/SVGRenderer.js | 553 + jsm/renderers/webgl/nodes/SlotNode.js | 22 + jsm/renderers/webgl/nodes/WebGLNodeBuilder.js | 561 + jsm/renderers/webgl/nodes/WebGLNodes.js | 45 + .../webgl/nodes/WebGLPhysicalContextNode.js | 45 + jsm/renderers/webgpu/WebGPUAttributes.js | 129 + jsm/renderers/webgpu/WebGPUBackground.js | 108 + jsm/renderers/webgpu/WebGPUBinding.js | 22 + jsm/renderers/webgpu/WebGPUBindings.js | 254 + jsm/renderers/webgpu/WebGPUBufferUtils.js | 33 + .../webgpu/WebGPUComputePipelines.js | 65 + jsm/renderers/webgpu/WebGPUGeometries.js | 76 + jsm/renderers/webgpu/WebGPUInfo.js | 74 + jsm/renderers/webgpu/WebGPUObjects.js | 42 + .../webgpu/WebGPUProgrammableStage.js | 22 + jsm/renderers/webgpu/WebGPUProperties.js | 38 + jsm/renderers/webgpu/WebGPURenderLists.js | 199 + jsm/renderers/webgpu/WebGPURenderPipeline.js | 737 + jsm/renderers/webgpu/WebGPURenderPipelines.js | 293 + jsm/renderers/webgpu/WebGPURenderStates.js | 66 + jsm/renderers/webgpu/WebGPURenderer.js | 985 + jsm/renderers/webgpu/WebGPUSampledTexture.js | 73 + jsm/renderers/webgpu/WebGPUSampler.js | 29 + jsm/renderers/webgpu/WebGPUStorageBuffer.js | 23 + jsm/renderers/webgpu/WebGPUTextureRenderer.js | 40 + jsm/renderers/webgpu/WebGPUTextureUtils.js | 179 + jsm/renderers/webgpu/WebGPUTextures.js | 781 + jsm/renderers/webgpu/WebGPUUniform.js | 136 + jsm/renderers/webgpu/WebGPUUniformBuffer.js | 45 + jsm/renderers/webgpu/WebGPUUniformsGroup.js | 299 + jsm/renderers/webgpu/constants.js | 261 + .../webgpu/nodes/WebGPUNodeBuilder.js | 679 + .../webgpu/nodes/WebGPUNodeSampledTexture.js | 39 + .../webgpu/nodes/WebGPUNodeSampler.js | 21 + .../webgpu/nodes/WebGPUNodeUniform.js | 135 + .../webgpu/nodes/WebGPUNodeUniformsGroup.js | 20 + jsm/renderers/webgpu/nodes/WebGPUNodes.js | 82 + jsm/shaders/ACESFilmicToneMappingShader.js | 87 + jsm/shaders/AfterimageShader.js | 56 + jsm/shaders/BasicShader.js | 27 + jsm/shaders/BleachBypassShader.js | 60 + jsm/shaders/BlendShader.js | 48 + jsm/shaders/BokehShader.js | 143 + jsm/shaders/BokehShader2.js | 390 + jsm/shaders/BrightnessContrastShader.js | 54 + jsm/shaders/ColorCorrectionShader.js | 50 + jsm/shaders/ColorifyShader.js | 49 + jsm/shaders/ConvolutionShader.js | 101 + jsm/shaders/CopyShader.js | 43 + jsm/shaders/DOFMipMapShader.js | 54 + jsm/shaders/DepthLimitedBlurShader.js | 166 + jsm/shaders/DigitalGlitch.js | 101 + jsm/shaders/DotScreenShader.js | 68 + jsm/shaders/FXAAShader.js | 286 + jsm/shaders/FilmShader.js | 100 + jsm/shaders/FocusShader.js | 87 + jsm/shaders/FreiChenShader.js | 94 + jsm/shaders/GammaCorrectionShader.js | 41 + jsm/shaders/GodRaysShader.js | 313 + jsm/shaders/HalftoneShader.js | 310 + jsm/shaders/HorizontalBlurShader.js | 57 + jsm/shaders/HorizontalTiltShiftShader.js | 61 + jsm/shaders/HueSaturationShader.js | 65 + jsm/shaders/KaleidoShader.js | 56 + jsm/shaders/LuminosityHighPassShader.js | 64 + jsm/shaders/LuminosityShader.js | 46 + jsm/shaders/MMDToonShader.js | 123 + jsm/shaders/MirrorShader.js | 54 + jsm/shaders/NormalMapShader.js | 53 + jsm/shaders/PixelShader.js | 44 + jsm/shaders/RGBShiftShader.js | 52 + jsm/shaders/SAOShader.js | 188 + jsm/shaders/SMAAShader.js | 460 + jsm/shaders/SSAOShader.js | 288 + jsm/shaders/SSRShader.js | 364 + jsm/shaders/SepiaShader.js | 50 + jsm/shaders/SobelOperatorShader.js | 90 + jsm/shaders/SubsurfaceScatteringShader.js | 88 + jsm/shaders/TechnicolorShader.js | 43 + jsm/shaders/ToneMapShader.js | 73 + jsm/shaders/ToonShader.js | 318 + jsm/shaders/TriangleBlurShader.js | 72 + jsm/shaders/UnpackDepthRGBAShader.js | 45 + jsm/shaders/VerticalBlurShader.js | 57 + jsm/shaders/VerticalTiltShiftShader.js | 61 + jsm/shaders/VignetteShader.js | 49 + jsm/shaders/VolumeShader.js | 289 + jsm/shaders/WaterRefractionShader.js | 90 + jsm/textures/FlakesTexture.js | 40 + jsm/utils/BufferGeometryUtils.js | 1224 + jsm/utils/CameraUtils.js | 73 + jsm/utils/GPUStatsPanel.js | 128 + jsm/utils/GeometryCompressionUtils.js | 639 + jsm/utils/GeometryUtils.js | 229 + jsm/utils/LDrawUtils.js | 202 + jsm/utils/PackedPhongMaterial.js | 252 + jsm/utils/SceneUtils.js | 145 + jsm/utils/ShadowMapViewer.js | 210 + jsm/utils/SkeletonUtils.js | 596 + jsm/utils/UVsDebug.js | 174 + jsm/utils/WorkerPool.js | 102 + jsm/webxr/ARButton.js | 208 + jsm/webxr/OculusHandModel.js | 108 + jsm/webxr/OculusHandPointerModel.js | 387 + jsm/webxr/Text2D.js | 38 + jsm/webxr/VRButton.js | 207 + jsm/webxr/XRControllerModelFactory.js | 299 + jsm/webxr/XREstimatedLight.js | 223 + jsm/webxr/XRHandMeshModel.js | 107 + jsm/webxr/XRHandModelFactory.js | 105 + jsm/webxr/XRHandPrimitiveModel.js | 102 + 451 files changed, 248756 insertions(+) create mode 100644 jsm/animation/AnimationClipCreator.js create mode 100644 jsm/animation/CCDIKSolver.js create mode 100644 jsm/animation/MMDAnimationHelper.js create mode 100644 jsm/animation/MMDPhysics.js create mode 100644 jsm/cameras/CinematicCamera.js create mode 100644 jsm/capabilities/WebGL.js create mode 100644 jsm/capabilities/WebGPU.js create mode 100644 jsm/controls/ArcballControls.js create mode 100644 jsm/controls/DragControls.js create mode 100644 jsm/controls/FirstPersonControls.js create mode 100644 jsm/controls/FlyControls.js create mode 100644 jsm/controls/OrbitControls.js create mode 100644 jsm/controls/PointerLockControls.js create mode 100644 jsm/controls/TrackballControls.js create mode 100644 jsm/controls/TransformControls.js create mode 100644 jsm/controls/experimental/CameraControls.js create mode 100644 jsm/csm/CSM.js create mode 100644 jsm/csm/CSMFrustum.js create mode 100644 jsm/csm/CSMHelper.js create mode 100644 jsm/csm/CSMShader.js create mode 100644 jsm/curves/CurveExtras.js create mode 100644 jsm/curves/NURBSCurve.js create mode 100644 jsm/curves/NURBSSurface.js create mode 100644 jsm/curves/NURBSUtils.js create mode 100644 jsm/deprecated/Geometry.js create mode 100644 jsm/effects/AnaglyphEffect.js create mode 100644 jsm/effects/AsciiEffect.js create mode 100644 jsm/effects/OutlineEffect.js create mode 100644 jsm/effects/ParallaxBarrierEffect.js create mode 100644 jsm/effects/PeppersGhostEffect.js create mode 100644 jsm/effects/StereoEffect.js create mode 100644 jsm/environments/DebugEnvironment.js create mode 100644 jsm/environments/RoomEnvironment.js create mode 100644 jsm/exporters/ColladaExporter.js create mode 100644 jsm/exporters/DRACOExporter.js create mode 100644 jsm/exporters/EXRExporter.js create mode 100644 jsm/exporters/GLTFExporter.js create mode 100644 jsm/exporters/MMDExporter.js create mode 100644 jsm/exporters/OBJExporter.js create mode 100644 jsm/exporters/PLYExporter.js create mode 100644 jsm/exporters/STLExporter.js create mode 100644 jsm/exporters/USDZExporter.js create mode 100644 jsm/geometries/BoxLineGeometry.js create mode 100644 jsm/geometries/ConvexGeometry.js create mode 100644 jsm/geometries/DecalGeometry.js create mode 100644 jsm/geometries/LightningStrike.js create mode 100644 jsm/geometries/ParametricGeometries.js create mode 100644 jsm/geometries/ParametricGeometry.js create mode 100644 jsm/geometries/RoundedBoxGeometry.js create mode 100644 jsm/geometries/TeapotGeometry.js create mode 100644 jsm/geometries/TextGeometry.js create mode 100644 jsm/helpers/LightProbeHelper.js create mode 100644 jsm/helpers/OctreeHelper.js create mode 100644 jsm/helpers/PositionalAudioHelper.js create mode 100644 jsm/helpers/RectAreaLightHelper.js create mode 100644 jsm/helpers/VertexNormalsHelper.js create mode 100644 jsm/helpers/VertexTangentsHelper.js create mode 100644 jsm/helpers/ViewHelper.js create mode 100644 jsm/interactive/HTMLMesh.js create mode 100644 jsm/interactive/InteractiveGroup.js create mode 100644 jsm/interactive/SelectionBox.js create mode 100644 jsm/interactive/SelectionHelper.js create mode 100644 jsm/libs/OimoPhysics/OimoPhysics.js create mode 100644 jsm/libs/OimoPhysics/index.js create mode 100644 jsm/libs/chevrotain.module.min.js create mode 100644 jsm/libs/ecsy.module.js create mode 100644 jsm/libs/fflate.module.js create mode 100644 jsm/libs/flow.module.js create mode 100644 jsm/libs/ktx-parse.module.js create mode 100644 jsm/libs/lil-gui.module.min.js create mode 100644 jsm/libs/meshopt_decoder.module.js create mode 100644 jsm/libs/mikktspace.module.js create mode 100644 jsm/libs/mmdparser.module.js create mode 100644 jsm/libs/motion-controllers.module.js create mode 100644 jsm/libs/opentype.module.js create mode 100644 jsm/libs/potpack.module.js create mode 100644 jsm/libs/rhino3dm/rhino3dm.js create mode 100644 jsm/libs/rhino3dm/rhino3dm.module.js create mode 100644 jsm/libs/rhino3dm/rhino3dm.wasm create mode 100644 jsm/libs/stats.module.js create mode 100644 jsm/libs/tween.module.min.js create mode 100644 jsm/lights/LightProbeGenerator.js create mode 100644 jsm/lights/RectAreaLightUniformsLib.js create mode 100644 jsm/lines/Line2.js create mode 100644 jsm/lines/LineGeometry.js create mode 100644 jsm/lines/LineMaterial.js create mode 100644 jsm/lines/LineSegments2.js create mode 100644 jsm/lines/LineSegmentsGeometry.js create mode 100644 jsm/lines/Wireframe.js create mode 100644 jsm/lines/WireframeGeometry2.js create mode 100644 jsm/loaders/3DMLoader.js create mode 100644 jsm/loaders/3MFLoader.js create mode 100644 jsm/loaders/AMFLoader.js create mode 100644 jsm/loaders/BVHLoader.js create mode 100644 jsm/loaders/BasisTextureLoader.js create mode 100644 jsm/loaders/ColladaLoader.js create mode 100644 jsm/loaders/DDSLoader.js create mode 100644 jsm/loaders/DRACOLoader.js create mode 100644 jsm/loaders/EXRLoader.js create mode 100644 jsm/loaders/FBXLoader.js create mode 100644 jsm/loaders/FontLoader.js create mode 100644 jsm/loaders/GCodeLoader.js create mode 100644 jsm/loaders/GLTFLoader.js create mode 100644 jsm/loaders/HDRCubeTextureLoader.js create mode 100644 jsm/loaders/IFCLoader.js create mode 100644 jsm/loaders/KMZLoader.js create mode 100644 jsm/loaders/KTX2Loader.js create mode 100644 jsm/loaders/KTXLoader.js create mode 100644 jsm/loaders/LDrawLoader.js create mode 100644 jsm/loaders/LUT3dlLoader.js create mode 100644 jsm/loaders/LUTCubeLoader.js create mode 100644 jsm/loaders/LWOLoader.js create mode 100644 jsm/loaders/LogLuvLoader.js create mode 100644 jsm/loaders/LottieLoader.js create mode 100644 jsm/loaders/MD2Loader.js create mode 100644 jsm/loaders/MDDLoader.js create mode 100644 jsm/loaders/MMDLoader.js create mode 100644 jsm/loaders/MTLLoader.js create mode 100644 jsm/loaders/NRRDLoader.js create mode 100644 jsm/loaders/OBJLoader.js create mode 100644 jsm/loaders/PCDLoader.js create mode 100644 jsm/loaders/PDBLoader.js create mode 100644 jsm/loaders/PLYLoader.js create mode 100644 jsm/loaders/PRWMLoader.js create mode 100644 jsm/loaders/PVRLoader.js create mode 100644 jsm/loaders/RGBELoader.js create mode 100644 jsm/loaders/RGBMLoader.js create mode 100644 jsm/loaders/STLLoader.js create mode 100644 jsm/loaders/SVGLoader.js create mode 100644 jsm/loaders/TDSLoader.js create mode 100644 jsm/loaders/TGALoader.js create mode 100644 jsm/loaders/TTFLoader.js create mode 100644 jsm/loaders/TiltLoader.js create mode 100644 jsm/loaders/VOXLoader.js create mode 100644 jsm/loaders/VRMLLoader.js create mode 100644 jsm/loaders/VTKLoader.js create mode 100644 jsm/loaders/XYZLoader.js create mode 100644 jsm/loaders/ifc/web-ifc-api.js create mode 100644 jsm/loaders/ifc/web-ifc.wasm create mode 100644 jsm/loaders/lwo/IFFParser.js create mode 100644 jsm/loaders/lwo/LWO2Parser.js create mode 100644 jsm/loaders/lwo/LWO3Parser.js create mode 100644 jsm/math/Capsule.js create mode 100644 jsm/math/ColorConverter.js create mode 100644 jsm/math/ConvexHull.js create mode 100644 jsm/math/ImprovedNoise.js create mode 100644 jsm/math/Lut.js create mode 100644 jsm/math/MeshSurfaceSampler.js create mode 100644 jsm/math/OBB.js create mode 100644 jsm/math/Octree.js create mode 100644 jsm/math/SimplexNoise.js create mode 100644 jsm/misc/ConvexObjectBreaker.js create mode 100644 jsm/misc/GPUComputationRenderer.js create mode 100644 jsm/misc/Gyroscope.js create mode 100644 jsm/misc/MD2Character.js create mode 100644 jsm/misc/MD2CharacterComplex.js create mode 100644 jsm/misc/MorphAnimMesh.js create mode 100644 jsm/misc/MorphBlendMesh.js create mode 100644 jsm/misc/ProgressiveLightMap.js create mode 100644 jsm/misc/RollerCoaster.js create mode 100644 jsm/misc/TubePainter.js create mode 100644 jsm/misc/Volume.js create mode 100644 jsm/misc/VolumeSlice.js create mode 100644 jsm/modifiers/CurveModifier.js create mode 100644 jsm/modifiers/EdgeSplitModifier.js create mode 100644 jsm/modifiers/SimplifyModifier.js create mode 100644 jsm/modifiers/TessellateModifier.js create mode 100644 jsm/node-editor/NodeEditor.js create mode 100644 jsm/node-editor/accessors/MatcapUVEditor.js create mode 100644 jsm/node-editor/accessors/NormalEditor.js create mode 100644 jsm/node-editor/accessors/PositionEditor.js create mode 100644 jsm/node-editor/accessors/UVEditor.js create mode 100644 jsm/node-editor/core/BaseNode.js create mode 100644 jsm/node-editor/core/DataFile.js create mode 100644 jsm/node-editor/core/FileEditor.js create mode 100644 jsm/node-editor/core/FileURLEditor.js create mode 100644 jsm/node-editor/display/BlendEditor.js create mode 100644 jsm/node-editor/display/NormalMapEditor.js create mode 100644 jsm/node-editor/examples/animate-uv.json create mode 100644 jsm/node-editor/examples/fake-top-light.json create mode 100644 jsm/node-editor/examples/matcap.json create mode 100644 jsm/node-editor/examples/oscillator-color.json create mode 100644 jsm/node-editor/examples/rim.json create mode 100644 jsm/node-editor/inputs/ColorEditor.js create mode 100644 jsm/node-editor/inputs/FloatEditor.js create mode 100644 jsm/node-editor/inputs/SliderEditor.js create mode 100644 jsm/node-editor/inputs/TextureEditor.js create mode 100644 jsm/node-editor/inputs/Vector2Editor.js create mode 100644 jsm/node-editor/inputs/Vector3Editor.js create mode 100644 jsm/node-editor/inputs/Vector4Editor.js create mode 100644 jsm/node-editor/materials/BasicMaterialEditor.js create mode 100644 jsm/node-editor/materials/PointsMaterialEditor.js create mode 100644 jsm/node-editor/materials/StandardMaterialEditor.js create mode 100644 jsm/node-editor/math/AngleEditor.js create mode 100644 jsm/node-editor/math/DotEditor.js create mode 100644 jsm/node-editor/math/InvertEditor.js create mode 100644 jsm/node-editor/math/LimiterEditor.js create mode 100644 jsm/node-editor/math/NormalizeEditor.js create mode 100644 jsm/node-editor/math/OperatorEditor.js create mode 100644 jsm/node-editor/math/PowerEditor.js create mode 100644 jsm/node-editor/math/TrigonometryEditor.js create mode 100644 jsm/node-editor/procedural/CheckerEditor.js create mode 100644 jsm/node-editor/scene/MeshEditor.js create mode 100644 jsm/node-editor/scene/Object3DEditor.js create mode 100644 jsm/node-editor/scene/PointsEditor.js create mode 100644 jsm/node-editor/utils/JoinEditor.js create mode 100644 jsm/node-editor/utils/OscillatorEditor.js create mode 100644 jsm/node-editor/utils/PreviewEditor.js create mode 100644 jsm/node-editor/utils/SplitEditor.js create mode 100644 jsm/node-editor/utils/TimerEditor.js create mode 100644 jsm/nodes/Nodes.js create mode 100644 jsm/nodes/accessors/BufferNode.js create mode 100644 jsm/nodes/accessors/CameraNode.js create mode 100644 jsm/nodes/accessors/CubeTextureNode.js create mode 100644 jsm/nodes/accessors/InstanceNode.js create mode 100644 jsm/nodes/accessors/MaterialNode.js create mode 100644 jsm/nodes/accessors/MaterialReferenceNode.js create mode 100644 jsm/nodes/accessors/ModelNode.js create mode 100644 jsm/nodes/accessors/ModelViewProjectionNode.js create mode 100644 jsm/nodes/accessors/NormalNode.js create mode 100644 jsm/nodes/accessors/Object3DNode.js create mode 100644 jsm/nodes/accessors/PointUVNode.js create mode 100644 jsm/nodes/accessors/PositionNode.js create mode 100644 jsm/nodes/accessors/ReferenceNode.js create mode 100644 jsm/nodes/accessors/ReflectNode.js create mode 100644 jsm/nodes/accessors/SkinningNode.js create mode 100644 jsm/nodes/accessors/TextureNode.js create mode 100644 jsm/nodes/accessors/UVNode.js create mode 100644 jsm/nodes/core/ArrayUniformNode.js create mode 100644 jsm/nodes/core/AttributeNode.js create mode 100644 jsm/nodes/core/BypassNode.js create mode 100644 jsm/nodes/core/CodeNode.js create mode 100644 jsm/nodes/core/ConstNode.js create mode 100644 jsm/nodes/core/ContextNode.js create mode 100644 jsm/nodes/core/ExpressionNode.js create mode 100644 jsm/nodes/core/FunctionCallNode.js create mode 100644 jsm/nodes/core/FunctionNode.js create mode 100644 jsm/nodes/core/InputNode.js create mode 100644 jsm/nodes/core/InstanceIndexNode.js create mode 100644 jsm/nodes/core/Node.js create mode 100644 jsm/nodes/core/NodeAttribute.js create mode 100644 jsm/nodes/core/NodeBuilder.js create mode 100644 jsm/nodes/core/NodeCode.js create mode 100644 jsm/nodes/core/NodeFrame.js create mode 100644 jsm/nodes/core/NodeFunction.js create mode 100644 jsm/nodes/core/NodeFunctionInput.js create mode 100644 jsm/nodes/core/NodeKeywords.js create mode 100644 jsm/nodes/core/NodeParser.js create mode 100644 jsm/nodes/core/NodeUniform.js create mode 100644 jsm/nodes/core/NodeUtils.js create mode 100644 jsm/nodes/core/NodeVar.js create mode 100644 jsm/nodes/core/NodeVary.js create mode 100644 jsm/nodes/core/PropertyNode.js create mode 100644 jsm/nodes/core/TempNode.js create mode 100644 jsm/nodes/core/UniformNode.js create mode 100644 jsm/nodes/core/VarNode.js create mode 100644 jsm/nodes/core/VaryNode.js create mode 100644 jsm/nodes/core/constants.js create mode 100644 jsm/nodes/display/ColorSpaceNode.js create mode 100644 jsm/nodes/display/NormalMapNode.js create mode 100644 jsm/nodes/fog/FogNode.js create mode 100644 jsm/nodes/fog/FogRangeNode.js create mode 100644 jsm/nodes/functions/BSDFs.js create mode 100644 jsm/nodes/functions/PhysicalMaterialFunctions.js create mode 100644 jsm/nodes/lights/LightContextNode.js create mode 100644 jsm/nodes/lights/LightNode.js create mode 100644 jsm/nodes/lights/LightsNode.js create mode 100644 jsm/nodes/loaders/NodeLoader.js create mode 100644 jsm/nodes/loaders/NodeMaterialLoader.js create mode 100644 jsm/nodes/loaders/NodeObjectLoader.js create mode 100644 jsm/nodes/materials/LineBasicNodeMaterial.js create mode 100644 jsm/nodes/materials/Materials.js create mode 100644 jsm/nodes/materials/MeshBasicNodeMaterial.js create mode 100644 jsm/nodes/materials/MeshStandardNodeMaterial.js create mode 100644 jsm/nodes/materials/NodeMaterial.js create mode 100644 jsm/nodes/materials/PointsNodeMaterial.js create mode 100644 jsm/nodes/math/CondNode.js create mode 100644 jsm/nodes/math/MathNode.js create mode 100644 jsm/nodes/math/OperatorNode.js create mode 100644 jsm/nodes/parsers/GLSLNodeFunction.js create mode 100644 jsm/nodes/parsers/GLSLNodeParser.js create mode 100644 jsm/nodes/parsers/WGSLNodeFunction.js create mode 100644 jsm/nodes/parsers/WGSLNodeParser.js create mode 100644 jsm/nodes/procedural/CheckerNode.js create mode 100644 jsm/nodes/shadernode/ShaderNode.js create mode 100644 jsm/nodes/shadernode/ShaderNodeElements.js create mode 100644 jsm/nodes/shadernode/ShaderNodeUtils.js create mode 100644 jsm/nodes/utils/ArrayElementNode.js create mode 100644 jsm/nodes/utils/ConvertNode.js create mode 100644 jsm/nodes/utils/JoinNode.js create mode 100644 jsm/nodes/utils/MatcapUVNode.js create mode 100644 jsm/nodes/utils/OscNode.js create mode 100644 jsm/nodes/utils/SplitNode.js create mode 100644 jsm/nodes/utils/SpriteSheetUVNode.js create mode 100644 jsm/nodes/utils/TimerNode.js create mode 100644 jsm/objects/Lensflare.js create mode 100644 jsm/objects/LightningStorm.js create mode 100644 jsm/objects/MarchingCubes.js create mode 100644 jsm/objects/Reflector.js create mode 100644 jsm/objects/ReflectorForSSRPass.js create mode 100644 jsm/objects/Refractor.js create mode 100644 jsm/objects/ShadowMesh.js create mode 100644 jsm/objects/Sky.js create mode 100644 jsm/objects/Water.js create mode 100644 jsm/objects/Water2.js create mode 100644 jsm/offscreen/jank.js create mode 100644 jsm/offscreen/offscreen.js create mode 100644 jsm/offscreen/scene.js create mode 100644 jsm/package.json create mode 100644 jsm/physics/AmmoPhysics.js create mode 100644 jsm/physics/OimoPhysics.js create mode 100644 jsm/postprocessing/AdaptiveToneMappingPass.js create mode 100644 jsm/postprocessing/AfterimagePass.js create mode 100644 jsm/postprocessing/BloomPass.js create mode 100644 jsm/postprocessing/BokehPass.js create mode 100644 jsm/postprocessing/ClearPass.js create mode 100644 jsm/postprocessing/CubeTexturePass.js create mode 100644 jsm/postprocessing/DotScreenPass.js create mode 100644 jsm/postprocessing/EffectComposer.js create mode 100644 jsm/postprocessing/FilmPass.js create mode 100644 jsm/postprocessing/GlitchPass.js create mode 100644 jsm/postprocessing/HalftonePass.js create mode 100644 jsm/postprocessing/LUTPass.js create mode 100644 jsm/postprocessing/MaskPass.js create mode 100644 jsm/postprocessing/OutlinePass.js create mode 100644 jsm/postprocessing/Pass.js create mode 100644 jsm/postprocessing/RenderPass.js create mode 100644 jsm/postprocessing/SAOPass.js create mode 100644 jsm/postprocessing/SMAAPass.js create mode 100644 jsm/postprocessing/SSAARenderPass.js create mode 100644 jsm/postprocessing/SSAOPass.js create mode 100644 jsm/postprocessing/SSRPass.js create mode 100644 jsm/postprocessing/SavePass.js create mode 100644 jsm/postprocessing/ShaderPass.js create mode 100644 jsm/postprocessing/TAARenderPass.js create mode 100644 jsm/postprocessing/TexturePass.js create mode 100644 jsm/postprocessing/UnrealBloomPass.js create mode 100644 jsm/renderers/CSS2DRenderer.js create mode 100644 jsm/renderers/CSS3DRenderer.js create mode 100644 jsm/renderers/Projector.js create mode 100644 jsm/renderers/SVGRenderer.js create mode 100644 jsm/renderers/webgl/nodes/SlotNode.js create mode 100644 jsm/renderers/webgl/nodes/WebGLNodeBuilder.js create mode 100644 jsm/renderers/webgl/nodes/WebGLNodes.js create mode 100644 jsm/renderers/webgl/nodes/WebGLPhysicalContextNode.js create mode 100644 jsm/renderers/webgpu/WebGPUAttributes.js create mode 100644 jsm/renderers/webgpu/WebGPUBackground.js create mode 100644 jsm/renderers/webgpu/WebGPUBinding.js create mode 100644 jsm/renderers/webgpu/WebGPUBindings.js create mode 100644 jsm/renderers/webgpu/WebGPUBufferUtils.js create mode 100644 jsm/renderers/webgpu/WebGPUComputePipelines.js create mode 100644 jsm/renderers/webgpu/WebGPUGeometries.js create mode 100644 jsm/renderers/webgpu/WebGPUInfo.js create mode 100644 jsm/renderers/webgpu/WebGPUObjects.js create mode 100644 jsm/renderers/webgpu/WebGPUProgrammableStage.js create mode 100644 jsm/renderers/webgpu/WebGPUProperties.js create mode 100644 jsm/renderers/webgpu/WebGPURenderLists.js create mode 100644 jsm/renderers/webgpu/WebGPURenderPipeline.js create mode 100644 jsm/renderers/webgpu/WebGPURenderPipelines.js create mode 100644 jsm/renderers/webgpu/WebGPURenderStates.js create mode 100644 jsm/renderers/webgpu/WebGPURenderer.js create mode 100644 jsm/renderers/webgpu/WebGPUSampledTexture.js create mode 100644 jsm/renderers/webgpu/WebGPUSampler.js create mode 100644 jsm/renderers/webgpu/WebGPUStorageBuffer.js create mode 100644 jsm/renderers/webgpu/WebGPUTextureRenderer.js create mode 100644 jsm/renderers/webgpu/WebGPUTextureUtils.js create mode 100644 jsm/renderers/webgpu/WebGPUTextures.js create mode 100644 jsm/renderers/webgpu/WebGPUUniform.js create mode 100644 jsm/renderers/webgpu/WebGPUUniformBuffer.js create mode 100644 jsm/renderers/webgpu/WebGPUUniformsGroup.js create mode 100644 jsm/renderers/webgpu/constants.js create mode 100644 jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js create mode 100644 jsm/renderers/webgpu/nodes/WebGPUNodeSampledTexture.js create mode 100644 jsm/renderers/webgpu/nodes/WebGPUNodeSampler.js create mode 100644 jsm/renderers/webgpu/nodes/WebGPUNodeUniform.js create mode 100644 jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js create mode 100644 jsm/renderers/webgpu/nodes/WebGPUNodes.js create mode 100644 jsm/shaders/ACESFilmicToneMappingShader.js create mode 100644 jsm/shaders/AfterimageShader.js create mode 100644 jsm/shaders/BasicShader.js create mode 100644 jsm/shaders/BleachBypassShader.js create mode 100644 jsm/shaders/BlendShader.js create mode 100644 jsm/shaders/BokehShader.js create mode 100644 jsm/shaders/BokehShader2.js create mode 100644 jsm/shaders/BrightnessContrastShader.js create mode 100644 jsm/shaders/ColorCorrectionShader.js create mode 100644 jsm/shaders/ColorifyShader.js create mode 100644 jsm/shaders/ConvolutionShader.js create mode 100644 jsm/shaders/CopyShader.js create mode 100644 jsm/shaders/DOFMipMapShader.js create mode 100644 jsm/shaders/DepthLimitedBlurShader.js create mode 100644 jsm/shaders/DigitalGlitch.js create mode 100644 jsm/shaders/DotScreenShader.js create mode 100644 jsm/shaders/FXAAShader.js create mode 100644 jsm/shaders/FilmShader.js create mode 100644 jsm/shaders/FocusShader.js create mode 100644 jsm/shaders/FreiChenShader.js create mode 100644 jsm/shaders/GammaCorrectionShader.js create mode 100644 jsm/shaders/GodRaysShader.js create mode 100644 jsm/shaders/HalftoneShader.js create mode 100644 jsm/shaders/HorizontalBlurShader.js create mode 100644 jsm/shaders/HorizontalTiltShiftShader.js create mode 100644 jsm/shaders/HueSaturationShader.js create mode 100644 jsm/shaders/KaleidoShader.js create mode 100644 jsm/shaders/LuminosityHighPassShader.js create mode 100644 jsm/shaders/LuminosityShader.js create mode 100644 jsm/shaders/MMDToonShader.js create mode 100644 jsm/shaders/MirrorShader.js create mode 100644 jsm/shaders/NormalMapShader.js create mode 100644 jsm/shaders/PixelShader.js create mode 100644 jsm/shaders/RGBShiftShader.js create mode 100644 jsm/shaders/SAOShader.js create mode 100644 jsm/shaders/SMAAShader.js create mode 100644 jsm/shaders/SSAOShader.js create mode 100644 jsm/shaders/SSRShader.js create mode 100644 jsm/shaders/SepiaShader.js create mode 100644 jsm/shaders/SobelOperatorShader.js create mode 100644 jsm/shaders/SubsurfaceScatteringShader.js create mode 100644 jsm/shaders/TechnicolorShader.js create mode 100644 jsm/shaders/ToneMapShader.js create mode 100644 jsm/shaders/ToonShader.js create mode 100644 jsm/shaders/TriangleBlurShader.js create mode 100644 jsm/shaders/UnpackDepthRGBAShader.js create mode 100644 jsm/shaders/VerticalBlurShader.js create mode 100644 jsm/shaders/VerticalTiltShiftShader.js create mode 100644 jsm/shaders/VignetteShader.js create mode 100644 jsm/shaders/VolumeShader.js create mode 100644 jsm/shaders/WaterRefractionShader.js create mode 100644 jsm/textures/FlakesTexture.js create mode 100644 jsm/utils/BufferGeometryUtils.js create mode 100644 jsm/utils/CameraUtils.js create mode 100644 jsm/utils/GPUStatsPanel.js create mode 100644 jsm/utils/GeometryCompressionUtils.js create mode 100644 jsm/utils/GeometryUtils.js create mode 100644 jsm/utils/LDrawUtils.js create mode 100644 jsm/utils/PackedPhongMaterial.js create mode 100644 jsm/utils/SceneUtils.js create mode 100644 jsm/utils/ShadowMapViewer.js create mode 100644 jsm/utils/SkeletonUtils.js create mode 100644 jsm/utils/UVsDebug.js create mode 100644 jsm/utils/WorkerPool.js create mode 100644 jsm/webxr/ARButton.js create mode 100644 jsm/webxr/OculusHandModel.js create mode 100644 jsm/webxr/OculusHandPointerModel.js create mode 100644 jsm/webxr/Text2D.js create mode 100644 jsm/webxr/VRButton.js create mode 100644 jsm/webxr/XRControllerModelFactory.js create mode 100644 jsm/webxr/XREstimatedLight.js create mode 100644 jsm/webxr/XRHandMeshModel.js create mode 100644 jsm/webxr/XRHandModelFactory.js create mode 100644 jsm/webxr/XRHandPrimitiveModel.js diff --git a/jsm/animation/AnimationClipCreator.js b/jsm/animation/AnimationClipCreator.js new file mode 100644 index 0000000..2646c7c --- /dev/null +++ b/jsm/animation/AnimationClipCreator.js @@ -0,0 +1,114 @@ +import { + AnimationClip, + BooleanKeyframeTrack, + ColorKeyframeTrack, + NumberKeyframeTrack, + Vector3, + VectorKeyframeTrack +} from 'three'; + +class AnimationClipCreator { + + static CreateRotationAnimation( period, axis = 'x' ) { + + const times = [ 0, period ], values = [ 0, 360 ]; + + const trackName = '.rotation[' + axis + ']'; + + const track = new NumberKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, period, [ track ] ); + + } + + static CreateScaleAxisAnimation( period, axis = 'x' ) { + + const times = [ 0, period ], values = [ 0, 1 ]; + + const trackName = '.scale[' + axis + ']'; + + const track = new NumberKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, period, [ track ] ); + + } + + static CreateShakeAnimation( duration, shakeScale ) { + + const times = [], values = [], tmp = new Vector3(); + + for ( let i = 0; i < duration * 10; i ++ ) { + + times.push( i / 10 ); + + tmp.set( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ). + multiply( shakeScale ). + toArray( values, values.length ); + + } + + const trackName = '.position'; + + const track = new VectorKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + + static CreatePulsationAnimation( duration, pulseScale ) { + + const times = [], values = [], tmp = new Vector3(); + + for ( let i = 0; i < duration * 10; i ++ ) { + + times.push( i / 10 ); + + const scaleFactor = Math.random() * pulseScale; + tmp.set( scaleFactor, scaleFactor, scaleFactor ). + toArray( values, values.length ); + + } + + const trackName = '.scale'; + + const track = new VectorKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + + static CreateVisibilityAnimation( duration ) { + + const times = [ 0, duration / 2, duration ], values = [ true, false, true ]; + + const trackName = '.visible'; + + const track = new BooleanKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + + static CreateMaterialColorAnimation( duration, colors ) { + + const times = [], values = [], + timeStep = duration / colors.length; + + for ( let i = 0; i <= colors.length; i ++ ) { + + times.push( i * timeStep ); + values.push( colors[ i % colors.length ] ); + + } + + const trackName = '.material[0].color'; + + const track = new ColorKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + +} + +export { AnimationClipCreator }; diff --git a/jsm/animation/CCDIKSolver.js b/jsm/animation/CCDIKSolver.js new file mode 100644 index 0000000..7282c76 --- /dev/null +++ b/jsm/animation/CCDIKSolver.js @@ -0,0 +1,458 @@ +import { + BufferAttribute, + BufferGeometry, + Color, + Line, + LineBasicMaterial, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + Quaternion, + SphereGeometry, + Vector3 +} from 'three'; + +const _q = new Quaternion(); +const _targetPos = new Vector3(); +const _targetVec = new Vector3(); +const _effectorPos = new Vector3(); +const _effectorVec = new Vector3(); +const _linkPos = new Vector3(); +const _invLinkQ = new Quaternion(); +const _linkScale = new Vector3(); +const _axis = new Vector3(); +const _vector = new Vector3(); +const _matrix = new Matrix4(); + + +/** + * CCD Algorithm + * - https://sites.google.com/site/auraliusproject/ccd-algorithm + * + * // ik parameter example + * // + * // target, effector, index in links are bone index in skeleton.bones. + * // the bones relation should be + * // <-- parent child --> + * // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector + * iks = [ { + * target: 1, + * effector: 2, + * links: [ { index: 5, limitation: new Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ], + * iteration: 10, + * minAngle: 0.0, + * maxAngle: 1.0, + * } ]; + */ + +class CCDIKSolver { + + /** + * @param {THREE.SkinnedMesh} mesh + * @param {Array} iks + */ + constructor( mesh, iks = [] ) { + + this.mesh = mesh; + this.iks = iks; + + this._valid(); + + } + + /** + * Update all IK bones. + * + * @return {CCDIKSolver} + */ + update() { + + const iks = this.iks; + + for ( let i = 0, il = iks.length; i < il; i ++ ) { + + this.updateOne( iks[ i ] ); + + } + + return this; + + } + + /** + * Update one IK bone + * + * @param {Object} ik parameter + * @return {CCDIKSolver} + */ + updateOne( ik ) { + + const bones = this.mesh.skeleton.bones; + + // for reference overhead reduction in loop + const math = Math; + + const effector = bones[ ik.effector ]; + const target = bones[ ik.target ]; + + // don't use getWorldPosition() here for the performance + // because it calls updateMatrixWorld( true ) inside. + _targetPos.setFromMatrixPosition( target.matrixWorld ); + + const links = ik.links; + const iteration = ik.iteration !== undefined ? ik.iteration : 1; + + for ( let i = 0; i < iteration; i ++ ) { + + let rotated = false; + + for ( let j = 0, jl = links.length; j < jl; j ++ ) { + + const link = bones[ links[ j ].index ]; + + // skip this link and following links. + // this skip is used for MMD performance optimization. + if ( links[ j ].enabled === false ) break; + + const limitation = links[ j ].limitation; + const rotationMin = links[ j ].rotationMin; + const rotationMax = links[ j ].rotationMax; + + // don't use getWorldPosition/Quaternion() here for the performance + // because they call updateMatrixWorld( true ) inside. + link.matrixWorld.decompose( _linkPos, _invLinkQ, _linkScale ); + _invLinkQ.invert(); + _effectorPos.setFromMatrixPosition( effector.matrixWorld ); + + // work in link world + _effectorVec.subVectors( _effectorPos, _linkPos ); + _effectorVec.applyQuaternion( _invLinkQ ); + _effectorVec.normalize(); + + _targetVec.subVectors( _targetPos, _linkPos ); + _targetVec.applyQuaternion( _invLinkQ ); + _targetVec.normalize(); + + let angle = _targetVec.dot( _effectorVec ); + + if ( angle > 1.0 ) { + + angle = 1.0; + + } else if ( angle < - 1.0 ) { + + angle = - 1.0; + + } + + angle = math.acos( angle ); + + // skip if changing angle is too small to prevent vibration of bone + if ( angle < 1e-5 ) continue; + + if ( ik.minAngle !== undefined && angle < ik.minAngle ) { + + angle = ik.minAngle; + + } + + if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) { + + angle = ik.maxAngle; + + } + + _axis.crossVectors( _effectorVec, _targetVec ); + _axis.normalize(); + + _q.setFromAxisAngle( _axis, angle ); + link.quaternion.multiply( _q ); + + // TODO: re-consider the limitation specification + if ( limitation !== undefined ) { + + let c = link.quaternion.w; + + if ( c > 1.0 ) c = 1.0; + + const c2 = math.sqrt( 1 - c * c ); + link.quaternion.set( limitation.x * c2, + limitation.y * c2, + limitation.z * c2, + c ); + + } + + if ( rotationMin !== undefined ) { + + link.rotation.setFromVector3( _vector.setFromEuler( link.rotation ).max( rotationMin ) ); + + } + + if ( rotationMax !== undefined ) { + + link.rotation.setFromVector3( _vector.setFromEuler( link.rotation ).min( rotationMax ) ); + + } + + link.updateMatrixWorld( true ); + + rotated = true; + + } + + if ( ! rotated ) break; + + } + + return this; + + } + + /** + * Creates Helper + * + * @return {CCDIKHelper} + */ + createHelper() { + + return new CCDIKHelper( this.mesh, this.mesh.geometry.userData.MMD.iks ); + + } + + // private methods + + _valid() { + + const iks = this.iks; + const bones = this.mesh.skeleton.bones; + + for ( let i = 0, il = iks.length; i < il; i ++ ) { + + const ik = iks[ i ]; + const effector = bones[ ik.effector ]; + const links = ik.links; + let link0, link1; + + link0 = effector; + + for ( let j = 0, jl = links.length; j < jl; j ++ ) { + + link1 = bones[ links[ j ].index ]; + + if ( link0.parent !== link1 ) { + + console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name ); + + } + + link0 = link1; + + } + + } + + } + +} + +function getPosition( bone, matrixWorldInv ) { + + return _vector + .setFromMatrixPosition( bone.matrixWorld ) + .applyMatrix4( matrixWorldInv ); + +} + +function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) { + + const v = getPosition( bone, matrixWorldInv ); + + array[ index * 3 + 0 ] = v.x; + array[ index * 3 + 1 ] = v.y; + array[ index * 3 + 2 ] = v.z; + +} + +/** + * Visualize IK bones + * + * @param {SkinnedMesh} mesh + * @param {Array} iks + */ +class CCDIKHelper extends Object3D { + + constructor( mesh, iks = [] ) { + + super(); + + this.root = mesh; + this.iks = iks; + + this.matrix.copy( mesh.matrixWorld ); + this.matrixAutoUpdate = false; + + this.sphereGeometry = new SphereGeometry( 0.25, 16, 8 ); + + this.targetSphereMaterial = new MeshBasicMaterial( { + color: new Color( 0xff8888 ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this.effectorSphereMaterial = new MeshBasicMaterial( { + color: new Color( 0x88ff88 ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this.linkSphereMaterial = new MeshBasicMaterial( { + color: new Color( 0x8888ff ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this.lineMaterial = new LineBasicMaterial( { + color: new Color( 0xff0000 ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this._init(); + + } + + /** + * Updates IK bones visualization. + */ + updateMatrixWorld( force ) { + + const mesh = this.root; + + if ( this.visible ) { + + let offset = 0; + + const iks = this.iks; + const bones = mesh.skeleton.bones; + + _matrix.copy( mesh.matrixWorld ).invert(); + + for ( let i = 0, il = iks.length; i < il; i ++ ) { + + const ik = iks[ i ]; + + const targetBone = bones[ ik.target ]; + const effectorBone = bones[ ik.effector ]; + + const targetMesh = this.children[ offset ++ ]; + const effectorMesh = this.children[ offset ++ ]; + + targetMesh.position.copy( getPosition( targetBone, _matrix ) ); + effectorMesh.position.copy( getPosition( effectorBone, _matrix ) ); + + for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { + + const link = ik.links[ j ]; + const linkBone = bones[ link.index ]; + + const linkMesh = this.children[ offset ++ ]; + + linkMesh.position.copy( getPosition( linkBone, _matrix ) ); + + } + + const line = this.children[ offset ++ ]; + const array = line.geometry.attributes.position.array; + + setPositionOfBoneToAttributeArray( array, 0, targetBone, _matrix ); + setPositionOfBoneToAttributeArray( array, 1, effectorBone, _matrix ); + + for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { + + const link = ik.links[ j ]; + const linkBone = bones[ link.index ]; + setPositionOfBoneToAttributeArray( array, j + 2, linkBone, _matrix ); + + } + + line.geometry.attributes.position.needsUpdate = true; + + } + + } + + this.matrix.copy( mesh.matrixWorld ); + + super.updateMatrixWorld( force ); + + } + + // private method + + _init() { + + const scope = this; + const iks = this.iks; + + function createLineGeometry( ik ) { + + const geometry = new BufferGeometry(); + const vertices = new Float32Array( ( 2 + ik.links.length ) * 3 ); + geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) ); + + return geometry; + + } + + function createTargetMesh() { + + return new Mesh( scope.sphereGeometry, scope.targetSphereMaterial ); + + } + + function createEffectorMesh() { + + return new Mesh( scope.sphereGeometry, scope.effectorSphereMaterial ); + + } + + function createLinkMesh() { + + return new Mesh( scope.sphereGeometry, scope.linkSphereMaterial ); + + } + + function createLine( ik ) { + + return new Line( createLineGeometry( ik ), scope.lineMaterial ); + + } + + for ( let i = 0, il = iks.length; i < il; i ++ ) { + + const ik = iks[ i ]; + + this.add( createTargetMesh() ); + this.add( createEffectorMesh() ); + + for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { + + this.add( createLinkMesh() ); + + } + + this.add( createLine( ik ) ); + + } + + } + +} + +export { CCDIKSolver, CCDIKHelper }; diff --git a/jsm/animation/MMDAnimationHelper.js b/jsm/animation/MMDAnimationHelper.js new file mode 100644 index 0000000..b24dea1 --- /dev/null +++ b/jsm/animation/MMDAnimationHelper.js @@ -0,0 +1,1207 @@ +import { + AnimationMixer, + Object3D, + Quaternion, + Vector3 +} from 'three'; +import { CCDIKSolver } from '../animation/CCDIKSolver.js'; +import { MMDPhysics } from '../animation/MMDPhysics.js'; + +/** + * MMDAnimationHelper handles animation of MMD assets loaded by MMDLoader + * with MMD special features as IK, Grant, and Physics. + * + * Dependencies + * - ammo.js https://github.com/kripken/ammo.js + * - MMDPhysics + * - CCDIKSolver + * + * TODO + * - more precise grant skinning support. + */ +class MMDAnimationHelper { + + /** + * @param {Object} params - (optional) + * @param {boolean} params.sync - Whether animation durations of added objects are synched. Default is true. + * @param {Number} params.afterglow - Default is 0.0. + * @param {boolean} params.resetPhysicsOnLoop - Default is true. + */ + constructor( params = {} ) { + + this.meshes = []; + + this.camera = null; + this.cameraTarget = new Object3D(); + this.cameraTarget.name = 'target'; + + this.audio = null; + this.audioManager = null; + + this.objects = new WeakMap(); + + this.configuration = { + sync: params.sync !== undefined ? params.sync : true, + afterglow: params.afterglow !== undefined ? params.afterglow : 0.0, + resetPhysicsOnLoop: params.resetPhysicsOnLoop !== undefined ? params.resetPhysicsOnLoop : true, + pmxAnimation: params.pmxAnimation !== undefined ? params.pmxAnimation : false + }; + + this.enabled = { + animation: true, + ik: true, + grant: true, + physics: true, + cameraAnimation: true + }; + + this.onBeforePhysics = function ( /* mesh */ ) {}; + + // experimental + this.sharedPhysics = false; + this.masterPhysics = null; + + } + + /** + * Adds an Three.js Object to helper and setups animation. + * The anmation durations of added objects are synched + * if this.configuration.sync is true. + * + * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object + * @param {Object} params - (optional) + * @param {THREE.AnimationClip|Array} params.animation - Only for THREE.SkinnedMesh and THREE.Camera. Default is undefined. + * @param {boolean} params.physics - Only for THREE.SkinnedMesh. Default is true. + * @param {Integer} params.warmup - Only for THREE.SkinnedMesh and physics is true. Default is 60. + * @param {Number} params.unitStep - Only for THREE.SkinnedMesh and physics is true. Default is 1 / 65. + * @param {Integer} params.maxStepNum - Only for THREE.SkinnedMesh and physics is true. Default is 3. + * @param {Vector3} params.gravity - Only for THREE.SkinnedMesh and physics is true. Default ( 0, - 9.8 * 10, 0 ). + * @param {Number} params.delayTime - Only for THREE.Audio. Default is 0.0. + * @return {MMDAnimationHelper} + */ + add( object, params = {} ) { + + if ( object.isSkinnedMesh ) { + + this._addMesh( object, params ); + + } else if ( object.isCamera ) { + + this._setupCamera( object, params ); + + } else if ( object.type === 'Audio' ) { + + this._setupAudio( object, params ); + + } else { + + throw new Error( 'THREE.MMDAnimationHelper.add: ' + + 'accepts only ' + + 'THREE.SkinnedMesh or ' + + 'THREE.Camera or ' + + 'THREE.Audio instance.' ); + + } + + if ( this.configuration.sync ) this._syncDuration(); + + return this; + + } + + /** + * Removes an Three.js Object from helper. + * + * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object + * @return {MMDAnimationHelper} + */ + remove( object ) { + + if ( object.isSkinnedMesh ) { + + this._removeMesh( object ); + + } else if ( object.isCamera ) { + + this._clearCamera( object ); + + } else if ( object.type === 'Audio' ) { + + this._clearAudio( object ); + + } else { + + throw new Error( 'THREE.MMDAnimationHelper.remove: ' + + 'accepts only ' + + 'THREE.SkinnedMesh or ' + + 'THREE.Camera or ' + + 'THREE.Audio instance.' ); + + } + + if ( this.configuration.sync ) this._syncDuration(); + + return this; + + } + + /** + * Updates the animation. + * + * @param {Number} delta + * @return {MMDAnimationHelper} + */ + update( delta ) { + + if ( this.audioManager !== null ) this.audioManager.control( delta ); + + for ( let i = 0; i < this.meshes.length; i ++ ) { + + this._animateMesh( this.meshes[ i ], delta ); + + } + + if ( this.sharedPhysics ) this._updateSharedPhysics( delta ); + + if ( this.camera !== null ) this._animateCamera( this.camera, delta ); + + return this; + + } + + /** + * Changes the pose of SkinnedMesh as VPD specifies. + * + * @param {THREE.SkinnedMesh} mesh + * @param {Object} vpd - VPD content parsed MMDParser + * @param {Object} params - (optional) + * @param {boolean} params.resetPose - Default is true. + * @param {boolean} params.ik - Default is true. + * @param {boolean} params.grant - Default is true. + * @return {MMDAnimationHelper} + */ + pose( mesh, vpd, params = {} ) { + + if ( params.resetPose !== false ) mesh.pose(); + + const bones = mesh.skeleton.bones; + const boneParams = vpd.bones; + + const boneNameDictionary = {}; + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + boneNameDictionary[ bones[ i ].name ] = i; + + } + + const vector = new Vector3(); + const quaternion = new Quaternion(); + + for ( let i = 0, il = boneParams.length; i < il; i ++ ) { + + const boneParam = boneParams[ i ]; + const boneIndex = boneNameDictionary[ boneParam.name ]; + + if ( boneIndex === undefined ) continue; + + const bone = bones[ boneIndex ]; + bone.position.add( vector.fromArray( boneParam.translation ) ); + bone.quaternion.multiply( quaternion.fromArray( boneParam.quaternion ) ); + + } + + mesh.updateMatrixWorld( true ); + + // PMX animation system special path + if ( this.configuration.pmxAnimation && + mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx' ) { + + const sortedBonesData = this._sortBoneDataArray( mesh.geometry.userData.MMD.bones.slice() ); + const ikSolver = params.ik !== false ? this._createCCDIKSolver( mesh ) : null; + const grantSolver = params.grant !== false ? this.createGrantSolver( mesh ) : null; + this._animatePMXMesh( mesh, sortedBonesData, ikSolver, grantSolver ); + + } else { + + if ( params.ik !== false ) { + + this._createCCDIKSolver( mesh ).update(); + + } + + if ( params.grant !== false ) { + + this.createGrantSolver( mesh ).update(); + + } + + } + + return this; + + } + + /** + * Enabes/Disables an animation feature. + * + * @param {string} key + * @param {boolean} enabled + * @return {MMDAnimationHelper} + */ + enable( key, enabled ) { + + if ( this.enabled[ key ] === undefined ) { + + throw new Error( 'THREE.MMDAnimationHelper.enable: ' + + 'unknown key ' + key ); + + } + + this.enabled[ key ] = enabled; + + if ( key === 'physics' ) { + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + this._optimizeIK( this.meshes[ i ], enabled ); + + } + + } + + return this; + + } + + /** + * Creates an GrantSolver instance. + * + * @param {THREE.SkinnedMesh} mesh + * @return {GrantSolver} + */ + createGrantSolver( mesh ) { + + return new GrantSolver( mesh, mesh.geometry.userData.MMD.grants ); + + } + + // private methods + + _addMesh( mesh, params ) { + + if ( this.meshes.indexOf( mesh ) >= 0 ) { + + throw new Error( 'THREE.MMDAnimationHelper._addMesh: ' + + 'SkinnedMesh \'' + mesh.name + '\' has already been added.' ); + + } + + this.meshes.push( mesh ); + this.objects.set( mesh, { looped: false } ); + + this._setupMeshAnimation( mesh, params.animation ); + + if ( params.physics !== false ) { + + this._setupMeshPhysics( mesh, params ); + + } + + return this; + + } + + _setupCamera( camera, params ) { + + if ( this.camera === camera ) { + + throw new Error( 'THREE.MMDAnimationHelper._setupCamera: ' + + 'Camera \'' + camera.name + '\' has already been set.' ); + + } + + if ( this.camera ) this.clearCamera( this.camera ); + + this.camera = camera; + + camera.add( this.cameraTarget ); + + this.objects.set( camera, {} ); + + if ( params.animation !== undefined ) { + + this._setupCameraAnimation( camera, params.animation ); + + } + + return this; + + } + + _setupAudio( audio, params ) { + + if ( this.audio === audio ) { + + throw new Error( 'THREE.MMDAnimationHelper._setupAudio: ' + + 'Audio \'' + audio.name + '\' has already been set.' ); + + } + + if ( this.audio ) this.clearAudio( this.audio ); + + this.audio = audio; + this.audioManager = new AudioManager( audio, params ); + + this.objects.set( this.audioManager, { + duration: this.audioManager.duration + } ); + + return this; + + } + + _removeMesh( mesh ) { + + let found = false; + let writeIndex = 0; + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + if ( this.meshes[ i ] === mesh ) { + + this.objects.delete( mesh ); + found = true; + + continue; + + } + + this.meshes[ writeIndex ++ ] = this.meshes[ i ]; + + } + + if ( ! found ) { + + throw new Error( 'THREE.MMDAnimationHelper._removeMesh: ' + + 'SkinnedMesh \'' + mesh.name + '\' has not been added yet.' ); + + } + + this.meshes.length = writeIndex; + + return this; + + } + + _clearCamera( camera ) { + + if ( camera !== this.camera ) { + + throw new Error( 'THREE.MMDAnimationHelper._clearCamera: ' + + 'Camera \'' + camera.name + '\' has not been set yet.' ); + + } + + this.camera.remove( this.cameraTarget ); + + this.objects.delete( this.camera ); + this.camera = null; + + return this; + + } + + _clearAudio( audio ) { + + if ( audio !== this.audio ) { + + throw new Error( 'THREE.MMDAnimationHelper._clearAudio: ' + + 'Audio \'' + audio.name + '\' has not been set yet.' ); + + } + + this.objects.delete( this.audioManager ); + + this.audio = null; + this.audioManager = null; + + return this; + + } + + _setupMeshAnimation( mesh, animation ) { + + const objects = this.objects.get( mesh ); + + if ( animation !== undefined ) { + + const animations = Array.isArray( animation ) + ? animation : [ animation ]; + + objects.mixer = new AnimationMixer( mesh ); + + for ( let i = 0, il = animations.length; i < il; i ++ ) { + + objects.mixer.clipAction( animations[ i ] ).play(); + + } + + // TODO: find a workaround not to access ._clip looking like a private property + objects.mixer.addEventListener( 'loop', function ( event ) { + + const tracks = event.action._clip.tracks; + + if ( tracks.length > 0 && tracks[ 0 ].name.slice( 0, 6 ) !== '.bones' ) return; + + objects.looped = true; + + } ); + + } + + objects.ikSolver = this._createCCDIKSolver( mesh ); + objects.grantSolver = this.createGrantSolver( mesh ); + + return this; + + } + + _setupCameraAnimation( camera, animation ) { + + const animations = Array.isArray( animation ) + ? animation : [ animation ]; + + const objects = this.objects.get( camera ); + + objects.mixer = new AnimationMixer( camera ); + + for ( let i = 0, il = animations.length; i < il; i ++ ) { + + objects.mixer.clipAction( animations[ i ] ).play(); + + } + + } + + _setupMeshPhysics( mesh, params ) { + + const objects = this.objects.get( mesh ); + + // shared physics is experimental + + if ( params.world === undefined && this.sharedPhysics ) { + + const masterPhysics = this._getMasterPhysics(); + + if ( masterPhysics !== null ) world = masterPhysics.world; // eslint-disable-line no-undef + + } + + objects.physics = this._createMMDPhysics( mesh, params ); + + if ( objects.mixer && params.animationWarmup !== false ) { + + this._animateMesh( mesh, 0 ); + objects.physics.reset(); + + } + + objects.physics.warmup( params.warmup !== undefined ? params.warmup : 60 ); + + this._optimizeIK( mesh, true ); + + } + + _animateMesh( mesh, delta ) { + + const objects = this.objects.get( mesh ); + + const mixer = objects.mixer; + const ikSolver = objects.ikSolver; + const grantSolver = objects.grantSolver; + const physics = objects.physics; + const looped = objects.looped; + + if ( mixer && this.enabled.animation ) { + + // alternate solution to save/restore bones but less performant? + //mesh.pose(); + //this._updatePropertyMixersBuffer( mesh ); + + this._restoreBones( mesh ); + + mixer.update( delta ); + + this._saveBones( mesh ); + + // PMX animation system special path + if ( this.configuration.pmxAnimation && + mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx' ) { + + if ( ! objects.sortedBonesData ) objects.sortedBonesData = this._sortBoneDataArray( mesh.geometry.userData.MMD.bones.slice() ); + + this._animatePMXMesh( + mesh, + objects.sortedBonesData, + ikSolver && this.enabled.ik ? ikSolver : null, + grantSolver && this.enabled.grant ? grantSolver : null + ); + + } else { + + if ( ikSolver && this.enabled.ik ) { + + mesh.updateMatrixWorld( true ); + ikSolver.update(); + + } + + if ( grantSolver && this.enabled.grant ) { + + grantSolver.update(); + + } + + } + + } + + if ( looped === true && this.enabled.physics ) { + + if ( physics && this.configuration.resetPhysicsOnLoop ) physics.reset(); + + objects.looped = false; + + } + + if ( physics && this.enabled.physics && ! this.sharedPhysics ) { + + this.onBeforePhysics( mesh ); + physics.update( delta ); + + } + + } + + // Sort bones in order by 1. transformationClass and 2. bone index. + // In PMX animation system, bone transformations should be processed + // in this order. + _sortBoneDataArray( boneDataArray ) { + + return boneDataArray.sort( function ( a, b ) { + + if ( a.transformationClass !== b.transformationClass ) { + + return a.transformationClass - b.transformationClass; + + } else { + + return a.index - b.index; + + } + + } ); + + } + + // PMX Animation system is a bit too complex and doesn't great match to + // Three.js Animation system. This method attempts to simulate it as much as + // possible but doesn't perfectly simulate. + // This method is more costly than the regular one so + // you are recommended to set constructor parameter "pmxAnimation: true" + // only if your PMX model animation doesn't work well. + // If you need better method you would be required to write your own. + _animatePMXMesh( mesh, sortedBonesData, ikSolver, grantSolver ) { + + _quaternionIndex = 0; + _grantResultMap.clear(); + + for ( let i = 0, il = sortedBonesData.length; i < il; i ++ ) { + + updateOne( mesh, sortedBonesData[ i ].index, ikSolver, grantSolver ); + + } + + mesh.updateMatrixWorld( true ); + return this; + + } + + _animateCamera( camera, delta ) { + + const mixer = this.objects.get( camera ).mixer; + + if ( mixer && this.enabled.cameraAnimation ) { + + mixer.update( delta ); + + camera.updateProjectionMatrix(); + + camera.up.set( 0, 1, 0 ); + camera.up.applyQuaternion( camera.quaternion ); + camera.lookAt( this.cameraTarget.position ); + + } + + } + + _optimizeIK( mesh, physicsEnabled ) { + + const iks = mesh.geometry.userData.MMD.iks; + const bones = mesh.geometry.userData.MMD.bones; + + for ( let i = 0, il = iks.length; i < il; i ++ ) { + + const ik = iks[ i ]; + const links = ik.links; + + for ( let j = 0, jl = links.length; j < jl; j ++ ) { + + const link = links[ j ]; + + if ( physicsEnabled === true ) { + + // disable IK of the bone the corresponding rigidBody type of which is 1 or 2 + // because its rotation will be overriden by physics + link.enabled = bones[ link.index ].rigidBodyType > 0 ? false : true; + + } else { + + link.enabled = true; + + } + + } + + } + + } + + _createCCDIKSolver( mesh ) { + + if ( CCDIKSolver === undefined ) { + + throw new Error( 'THREE.MMDAnimationHelper: Import CCDIKSolver.' ); + + } + + return new CCDIKSolver( mesh, mesh.geometry.userData.MMD.iks ); + + } + + _createMMDPhysics( mesh, params ) { + + if ( MMDPhysics === undefined ) { + + throw new Error( 'THREE.MMDPhysics: Import MMDPhysics.' ); + + } + + return new MMDPhysics( + mesh, + mesh.geometry.userData.MMD.rigidBodies, + mesh.geometry.userData.MMD.constraints, + params ); + + } + + /* + * Detects the longest duration and then sets it to them to sync. + * TODO: Not to access private properties ( ._actions and ._clip ) + */ + _syncDuration() { + + let max = 0.0; + + const objects = this.objects; + const meshes = this.meshes; + const camera = this.camera; + const audioManager = this.audioManager; + + // get the longest duration + + for ( let i = 0, il = meshes.length; i < il; i ++ ) { + + const mixer = this.objects.get( meshes[ i ] ).mixer; + + if ( mixer === undefined ) continue; + + for ( let j = 0; j < mixer._actions.length; j ++ ) { + + const clip = mixer._actions[ j ]._clip; + + if ( ! objects.has( clip ) ) { + + objects.set( clip, { + duration: clip.duration + } ); + + } + + max = Math.max( max, objects.get( clip ).duration ); + + } + + } + + if ( camera !== null ) { + + const mixer = this.objects.get( camera ).mixer; + + if ( mixer !== undefined ) { + + for ( let i = 0, il = mixer._actions.length; i < il; i ++ ) { + + const clip = mixer._actions[ i ]._clip; + + if ( ! objects.has( clip ) ) { + + objects.set( clip, { + duration: clip.duration + } ); + + } + + max = Math.max( max, objects.get( clip ).duration ); + + } + + } + + } + + if ( audioManager !== null ) { + + max = Math.max( max, objects.get( audioManager ).duration ); + + } + + max += this.configuration.afterglow; + + // update the duration + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + const mixer = this.objects.get( this.meshes[ i ] ).mixer; + + if ( mixer === undefined ) continue; + + for ( let j = 0, jl = mixer._actions.length; j < jl; j ++ ) { + + mixer._actions[ j ]._clip.duration = max; + + } + + } + + if ( camera !== null ) { + + const mixer = this.objects.get( camera ).mixer; + + if ( mixer !== undefined ) { + + for ( let i = 0, il = mixer._actions.length; i < il; i ++ ) { + + mixer._actions[ i ]._clip.duration = max; + + } + + } + + } + + if ( audioManager !== null ) { + + audioManager.duration = max; + + } + + } + + // workaround + + _updatePropertyMixersBuffer( mesh ) { + + const mixer = this.objects.get( mesh ).mixer; + + const propertyMixers = mixer._bindings; + const accuIndex = mixer._accuIndex; + + for ( let i = 0, il = propertyMixers.length; i < il; i ++ ) { + + const propertyMixer = propertyMixers[ i ]; + const buffer = propertyMixer.buffer; + const stride = propertyMixer.valueSize; + const offset = ( accuIndex + 1 ) * stride; + + propertyMixer.binding.getValue( buffer, offset ); + + } + + } + + /* + * Avoiding these two issues by restore/save bones before/after mixer animation. + * + * 1. PropertyMixer used by AnimationMixer holds cache value in .buffer. + * Calculating IK, Grant, and Physics after mixer animation can break + * the cache coherency. + * + * 2. Applying Grant two or more times without reset the posing breaks model. + */ + _saveBones( mesh ) { + + const objects = this.objects.get( mesh ); + + const bones = mesh.skeleton.bones; + + let backupBones = objects.backupBones; + + if ( backupBones === undefined ) { + + backupBones = new Float32Array( bones.length * 7 ); + objects.backupBones = backupBones; + + } + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + const bone = bones[ i ]; + bone.position.toArray( backupBones, i * 7 ); + bone.quaternion.toArray( backupBones, i * 7 + 3 ); + + } + + } + + _restoreBones( mesh ) { + + const objects = this.objects.get( mesh ); + + const backupBones = objects.backupBones; + + if ( backupBones === undefined ) return; + + const bones = mesh.skeleton.bones; + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + const bone = bones[ i ]; + bone.position.fromArray( backupBones, i * 7 ); + bone.quaternion.fromArray( backupBones, i * 7 + 3 ); + + } + + } + + // experimental + + _getMasterPhysics() { + + if ( this.masterPhysics !== null ) return this.masterPhysics; + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + const physics = this.meshes[ i ].physics; + + if ( physics !== undefined && physics !== null ) { + + this.masterPhysics = physics; + return this.masterPhysics; + + } + + } + + return null; + + } + + _updateSharedPhysics( delta ) { + + if ( this.meshes.length === 0 || ! this.enabled.physics || ! this.sharedPhysics ) return; + + const physics = this._getMasterPhysics(); + + if ( physics === null ) return; + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + const p = this.meshes[ i ].physics; + + if ( p !== null && p !== undefined ) { + + p.updateRigidBodies(); + + } + + } + + physics.stepSimulation( delta ); + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + const p = this.meshes[ i ].physics; + + if ( p !== null && p !== undefined ) { + + p.updateBones(); + + } + + } + + } + +} + +// Keep working quaternions for less GC +const _quaternions = []; +let _quaternionIndex = 0; + +function getQuaternion() { + + if ( _quaternionIndex >= _quaternions.length ) { + + _quaternions.push( new Quaternion() ); + + } + + return _quaternions[ _quaternionIndex ++ ]; + +} + +// Save rotation whose grant and IK are already applied +// used by grant children +const _grantResultMap = new Map(); + +function updateOne( mesh, boneIndex, ikSolver, grantSolver ) { + + const bones = mesh.skeleton.bones; + const bonesData = mesh.geometry.userData.MMD.bones; + const boneData = bonesData[ boneIndex ]; + const bone = bones[ boneIndex ]; + + // Return if already updated by being referred as a grant parent. + if ( _grantResultMap.has( boneIndex ) ) return; + + const quaternion = getQuaternion(); + + // Initialize grant result here to prevent infinite loop. + // If it's referred before updating with actual result later + // result without applyting IK or grant is gotten + // but better than composing of infinite loop. + _grantResultMap.set( boneIndex, quaternion.copy( bone.quaternion ) ); + + // @TODO: Support global grant and grant position + if ( grantSolver && boneData.grant && + ! boneData.grant.isLocal && boneData.grant.affectRotation ) { + + const parentIndex = boneData.grant.parentIndex; + const ratio = boneData.grant.ratio; + + if ( ! _grantResultMap.has( parentIndex ) ) { + + updateOne( mesh, parentIndex, ikSolver, grantSolver ); + + } + + grantSolver.addGrantRotation( bone, _grantResultMap.get( parentIndex ), ratio ); + + } + + if ( ikSolver && boneData.ik ) { + + // @TODO: Updating world matrices every time solving an IK bone is + // costly. Optimize if possible. + mesh.updateMatrixWorld( true ); + ikSolver.updateOne( boneData.ik ); + + // No confident, but it seems the grant results with ik links should be updated? + const links = boneData.ik.links; + + for ( let i = 0, il = links.length; i < il; i ++ ) { + + const link = links[ i ]; + + if ( link.enabled === false ) continue; + + const linkIndex = link.index; + + if ( _grantResultMap.has( linkIndex ) ) { + + _grantResultMap.set( linkIndex, _grantResultMap.get( linkIndex ).copy( bones[ linkIndex ].quaternion ) ); + + } + + } + + } + + // Update with the actual result here + quaternion.copy( bone.quaternion ); + +} + +// + +class AudioManager { + + /** + * @param {THREE.Audio} audio + * @param {Object} params - (optional) + * @param {Nuumber} params.delayTime + */ + constructor( audio, params = {} ) { + + this.audio = audio; + + this.elapsedTime = 0.0; + this.currentTime = 0.0; + this.delayTime = params.delayTime !== undefined + ? params.delayTime : 0.0; + + this.audioDuration = this.audio.buffer.duration; + this.duration = this.audioDuration + this.delayTime; + + } + + /** + * @param {Number} delta + * @return {AudioManager} + */ + control( delta ) { + + this.elapsed += delta; + this.currentTime += delta; + + if ( this._shouldStopAudio() ) this.audio.stop(); + if ( this._shouldStartAudio() ) this.audio.play(); + + return this; + + } + + // private methods + + _shouldStartAudio() { + + if ( this.audio.isPlaying ) return false; + + while ( this.currentTime >= this.duration ) { + + this.currentTime -= this.duration; + + } + + if ( this.currentTime < this.delayTime ) return false; + + // 'duration' can be bigger than 'audioDuration + delayTime' because of sync configuration + if ( ( this.currentTime - this.delayTime ) > this.audioDuration ) return false; + + return true; + + } + + _shouldStopAudio() { + + return this.audio.isPlaying && + this.currentTime >= this.duration; + + } + +} + +const _q = new Quaternion(); + +/** + * Solver for Grant (Fuyo in Japanese. I just google translated because + * Fuyo may be MMD specific term and may not be common word in 3D CG terms.) + * Grant propagates a bone's transform to other bones transforms even if + * they are not children. + * @param {THREE.SkinnedMesh} mesh + * @param {Array} grants + */ +class GrantSolver { + + constructor( mesh, grants = [] ) { + + this.mesh = mesh; + this.grants = grants; + + } + + /** + * Solve all the grant bones + * @return {GrantSolver} + */ + update() { + + const grants = this.grants; + + for ( let i = 0, il = grants.length; i < il; i ++ ) { + + this.updateOne( grants[ i ] ); + + } + + return this; + + } + + /** + * Solve a grant bone + * @param {Object} grant - grant parameter + * @return {GrantSolver} + */ + updateOne( grant ) { + + const bones = this.mesh.skeleton.bones; + const bone = bones[ grant.index ]; + const parentBone = bones[ grant.parentIndex ]; + + if ( grant.isLocal ) { + + // TODO: implement + if ( grant.affectPosition ) { + + } + + // TODO: implement + if ( grant.affectRotation ) { + + } + + } else { + + // TODO: implement + if ( grant.affectPosition ) { + + } + + if ( grant.affectRotation ) { + + this.addGrantRotation( bone, parentBone.quaternion, grant.ratio ); + + } + + } + + return this; + + } + + addGrantRotation( bone, q, ratio ) { + + _q.set( 0, 0, 0, 1 ); + _q.slerp( q, ratio ); + bone.quaternion.multiply( _q ); + + return this; + + } + +} + +export { MMDAnimationHelper }; diff --git a/jsm/animation/MMDPhysics.js b/jsm/animation/MMDPhysics.js new file mode 100644 index 0000000..92d789f --- /dev/null +++ b/jsm/animation/MMDPhysics.js @@ -0,0 +1,1400 @@ +import { + Bone, + BoxGeometry, + Color, + CylinderGeometry, + Euler, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + Quaternion, + SphereGeometry, + Vector3 +} from 'three'; + +/** + * Dependencies + * - Ammo.js https://github.com/kripken/ammo.js + * + * MMDPhysics calculates physics with Ammo(Bullet based JavaScript Physics engine) + * for MMD model loaded by MMDLoader. + * + * TODO + * - Physics in Worker + */ + +/* global Ammo */ + +class MMDPhysics { + + /** + * @param {THREE.SkinnedMesh} mesh + * @param {Array} rigidBodyParams + * @param {Array} (optional) constraintParams + * @param {Object} params - (optional) + * @param {Number} params.unitStep - Default is 1 / 65. + * @param {Integer} params.maxStepNum - Default is 3. + * @param {Vector3} params.gravity - Default is ( 0, - 9.8 * 10, 0 ) + */ + constructor( mesh, rigidBodyParams, constraintParams = [], params = {} ) { + + if ( typeof Ammo === 'undefined' ) { + + throw new Error( 'THREE.MMDPhysics: Import ammo.js https://github.com/kripken/ammo.js' ); + + } + + this.manager = new ResourceManager(); + + this.mesh = mesh; + + /* + * I don't know why but 1/60 unitStep easily breaks models + * so I set it 1/65 so far. + * Don't set too small unitStep because + * the smaller unitStep can make the performance worse. + */ + this.unitStep = ( params.unitStep !== undefined ) ? params.unitStep : 1 / 65; + this.maxStepNum = ( params.maxStepNum !== undefined ) ? params.maxStepNum : 3; + this.gravity = new Vector3( 0, - 9.8 * 10, 0 ); + + if ( params.gravity !== undefined ) this.gravity.copy( params.gravity ); + + this.world = params.world !== undefined ? params.world : null; // experimental + + this.bodies = []; + this.constraints = []; + + this._init( mesh, rigidBodyParams, constraintParams ); + + } + + /** + * Advances Physics calculation and updates bones. + * + * @param {Number} delta - time in second + * @return {MMDPhysics} + */ + update( delta ) { + + const manager = this.manager; + const mesh = this.mesh; + + // rigid bodies and constrains are for + // mesh's world scale (1, 1, 1). + // Convert to (1, 1, 1) if it isn't. + + let isNonDefaultScale = false; + + const position = manager.allocThreeVector3(); + const quaternion = manager.allocThreeQuaternion(); + const scale = manager.allocThreeVector3(); + + mesh.matrixWorld.decompose( position, quaternion, scale ); + + if ( scale.x !== 1 || scale.y !== 1 || scale.z !== 1 ) { + + isNonDefaultScale = true; + + } + + let parent; + + if ( isNonDefaultScale ) { + + parent = mesh.parent; + + if ( parent !== null ) mesh.parent = null; + + scale.copy( this.mesh.scale ); + + mesh.scale.set( 1, 1, 1 ); + mesh.updateMatrixWorld( true ); + + } + + // calculate physics and update bones + + this._updateRigidBodies(); + this._stepSimulation( delta ); + this._updateBones(); + + // restore mesh if converted above + + if ( isNonDefaultScale ) { + + if ( parent !== null ) mesh.parent = parent; + + mesh.scale.copy( scale ); + + } + + manager.freeThreeVector3( scale ); + manager.freeThreeQuaternion( quaternion ); + manager.freeThreeVector3( position ); + + return this; + + } + + /** + * Resets rigid bodies transorm to current bone's. + * + * @return {MMDPhysics} + */ + reset() { + + for ( let i = 0, il = this.bodies.length; i < il; i ++ ) { + + this.bodies[ i ].reset(); + + } + + return this; + + } + + /** + * Warm ups Rigid bodies. Calculates cycles steps. + * + * @param {Integer} cycles + * @return {MMDPhysics} + */ + warmup( cycles ) { + + for ( let i = 0; i < cycles; i ++ ) { + + this.update( 1 / 60 ); + + } + + return this; + + } + + /** + * Sets gravity. + * + * @param {Vector3} gravity + * @return {MMDPhysicsHelper} + */ + setGravity( gravity ) { + + this.world.setGravity( new Ammo.btVector3( gravity.x, gravity.y, gravity.z ) ); + this.gravity.copy( gravity ); + + return this; + + } + + /** + * Creates MMDPhysicsHelper + * + * @return {MMDPhysicsHelper} + */ + createHelper() { + + return new MMDPhysicsHelper( this.mesh, this ); + + } + + // private methods + + _init( mesh, rigidBodyParams, constraintParams ) { + + const manager = this.manager; + + // rigid body/constraint parameters are for + // mesh's default world transform as position(0, 0, 0), + // quaternion(0, 0, 0, 1) and scale(0, 0, 0) + + let parent = mesh.parent; + + if ( parent !== null ) parent = null; + + const currentPosition = manager.allocThreeVector3(); + const currentQuaternion = manager.allocThreeQuaternion(); + const currentScale = manager.allocThreeVector3(); + + currentPosition.copy( mesh.position ); + currentQuaternion.copy( mesh.quaternion ); + currentScale.copy( mesh.scale ); + + mesh.position.set( 0, 0, 0 ); + mesh.quaternion.set( 0, 0, 0, 1 ); + mesh.scale.set( 1, 1, 1 ); + + mesh.updateMatrixWorld( true ); + + if ( this.world === null ) { + + this.world = this._createWorld(); + this.setGravity( this.gravity ); + + } + + this._initRigidBodies( rigidBodyParams ); + this._initConstraints( constraintParams ); + + if ( parent !== null ) mesh.parent = parent; + + mesh.position.copy( currentPosition ); + mesh.quaternion.copy( currentQuaternion ); + mesh.scale.copy( currentScale ); + + mesh.updateMatrixWorld( true ); + + this.reset(); + + manager.freeThreeVector3( currentPosition ); + manager.freeThreeQuaternion( currentQuaternion ); + manager.freeThreeVector3( currentScale ); + + } + + _createWorld() { + + const config = new Ammo.btDefaultCollisionConfiguration(); + const dispatcher = new Ammo.btCollisionDispatcher( config ); + const cache = new Ammo.btDbvtBroadphase(); + const solver = new Ammo.btSequentialImpulseConstraintSolver(); + const world = new Ammo.btDiscreteDynamicsWorld( dispatcher, cache, solver, config ); + return world; + + } + + _initRigidBodies( rigidBodies ) { + + for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) { + + this.bodies.push( new RigidBody( + this.mesh, this.world, rigidBodies[ i ], this.manager ) ); + + } + + } + + _initConstraints( constraints ) { + + for ( let i = 0, il = constraints.length; i < il; i ++ ) { + + const params = constraints[ i ]; + const bodyA = this.bodies[ params.rigidBodyIndex1 ]; + const bodyB = this.bodies[ params.rigidBodyIndex2 ]; + this.constraints.push( new Constraint( this.mesh, this.world, bodyA, bodyB, params, this.manager ) ); + + } + + } + + _stepSimulation( delta ) { + + const unitStep = this.unitStep; + let stepTime = delta; + let maxStepNum = ( ( delta / unitStep ) | 0 ) + 1; + + if ( stepTime < unitStep ) { + + stepTime = unitStep; + maxStepNum = 1; + + } + + if ( maxStepNum > this.maxStepNum ) { + + maxStepNum = this.maxStepNum; + + } + + this.world.stepSimulation( stepTime, maxStepNum, unitStep ); + + } + + _updateRigidBodies() { + + for ( let i = 0, il = this.bodies.length; i < il; i ++ ) { + + this.bodies[ i ].updateFromBone(); + + } + + } + + _updateBones() { + + for ( let i = 0, il = this.bodies.length; i < il; i ++ ) { + + this.bodies[ i ].updateBone(); + + } + + } + +} + +/** + * This manager's responsibilies are + * + * 1. manage Ammo.js and Three.js object resources and + * improve the performance and the memory consumption by + * reusing objects. + * + * 2. provide simple Ammo object operations. + */ +class ResourceManager { + + constructor() { + + // for Three.js + this.threeVector3s = []; + this.threeMatrix4s = []; + this.threeQuaternions = []; + this.threeEulers = []; + + // for Ammo.js + this.transforms = []; + this.quaternions = []; + this.vector3s = []; + + } + + allocThreeVector3() { + + return ( this.threeVector3s.length > 0 ) + ? this.threeVector3s.pop() + : new Vector3(); + + } + + freeThreeVector3( v ) { + + this.threeVector3s.push( v ); + + } + + allocThreeMatrix4() { + + return ( this.threeMatrix4s.length > 0 ) + ? this.threeMatrix4s.pop() + : new Matrix4(); + + } + + freeThreeMatrix4( m ) { + + this.threeMatrix4s.push( m ); + + } + + allocThreeQuaternion() { + + return ( this.threeQuaternions.length > 0 ) + ? this.threeQuaternions.pop() + : new Quaternion(); + + } + + freeThreeQuaternion( q ) { + + this.threeQuaternions.push( q ); + + } + + allocThreeEuler() { + + return ( this.threeEulers.length > 0 ) + ? this.threeEulers.pop() + : new Euler(); + + } + + freeThreeEuler( e ) { + + this.threeEulers.push( e ); + + } + + allocTransform() { + + return ( this.transforms.length > 0 ) + ? this.transforms.pop() + : new Ammo.btTransform(); + + } + + freeTransform( t ) { + + this.transforms.push( t ); + + } + + allocQuaternion() { + + return ( this.quaternions.length > 0 ) + ? this.quaternions.pop() + : new Ammo.btQuaternion(); + + } + + freeQuaternion( q ) { + + this.quaternions.push( q ); + + } + + allocVector3() { + + return ( this.vector3s.length > 0 ) + ? this.vector3s.pop() + : new Ammo.btVector3(); + + } + + freeVector3( v ) { + + this.vector3s.push( v ); + + } + + setIdentity( t ) { + + t.setIdentity(); + + } + + getBasis( t ) { + + var q = this.allocQuaternion(); + t.getBasis().getRotation( q ); + return q; + + } + + getBasisAsMatrix3( t ) { + + var q = this.getBasis( t ); + var m = this.quaternionToMatrix3( q ); + this.freeQuaternion( q ); + return m; + + } + + getOrigin( t ) { + + return t.getOrigin(); + + } + + setOrigin( t, v ) { + + t.getOrigin().setValue( v.x(), v.y(), v.z() ); + + } + + copyOrigin( t1, t2 ) { + + var o = t2.getOrigin(); + this.setOrigin( t1, o ); + + } + + setBasis( t, q ) { + + t.setRotation( q ); + + } + + setBasisFromMatrix3( t, m ) { + + var q = this.matrix3ToQuaternion( m ); + this.setBasis( t, q ); + this.freeQuaternion( q ); + + } + + setOriginFromArray3( t, a ) { + + t.getOrigin().setValue( a[ 0 ], a[ 1 ], a[ 2 ] ); + + } + + setOriginFromThreeVector3( t, v ) { + + t.getOrigin().setValue( v.x, v.y, v.z ); + + } + + setBasisFromArray3( t, a ) { + + var thQ = this.allocThreeQuaternion(); + var thE = this.allocThreeEuler(); + thE.set( a[ 0 ], a[ 1 ], a[ 2 ] ); + this.setBasisFromThreeQuaternion( t, thQ.setFromEuler( thE ) ); + + this.freeThreeEuler( thE ); + this.freeThreeQuaternion( thQ ); + + } + + setBasisFromThreeQuaternion( t, a ) { + + var q = this.allocQuaternion(); + + q.setX( a.x ); + q.setY( a.y ); + q.setZ( a.z ); + q.setW( a.w ); + this.setBasis( t, q ); + + this.freeQuaternion( q ); + + } + + multiplyTransforms( t1, t2 ) { + + var t = this.allocTransform(); + this.setIdentity( t ); + + var m1 = this.getBasisAsMatrix3( t1 ); + var m2 = this.getBasisAsMatrix3( t2 ); + + var o1 = this.getOrigin( t1 ); + var o2 = this.getOrigin( t2 ); + + var v1 = this.multiplyMatrix3ByVector3( m1, o2 ); + var v2 = this.addVector3( v1, o1 ); + this.setOrigin( t, v2 ); + + var m3 = this.multiplyMatrices3( m1, m2 ); + this.setBasisFromMatrix3( t, m3 ); + + this.freeVector3( v1 ); + this.freeVector3( v2 ); + + return t; + + } + + inverseTransform( t ) { + + var t2 = this.allocTransform(); + + var m1 = this.getBasisAsMatrix3( t ); + var o = this.getOrigin( t ); + + var m2 = this.transposeMatrix3( m1 ); + var v1 = this.negativeVector3( o ); + var v2 = this.multiplyMatrix3ByVector3( m2, v1 ); + + this.setOrigin( t2, v2 ); + this.setBasisFromMatrix3( t2, m2 ); + + this.freeVector3( v1 ); + this.freeVector3( v2 ); + + return t2; + + } + + multiplyMatrices3( m1, m2 ) { + + var m3 = []; + + var v10 = this.rowOfMatrix3( m1, 0 ); + var v11 = this.rowOfMatrix3( m1, 1 ); + var v12 = this.rowOfMatrix3( m1, 2 ); + + var v20 = this.columnOfMatrix3( m2, 0 ); + var v21 = this.columnOfMatrix3( m2, 1 ); + var v22 = this.columnOfMatrix3( m2, 2 ); + + m3[ 0 ] = this.dotVectors3( v10, v20 ); + m3[ 1 ] = this.dotVectors3( v10, v21 ); + m3[ 2 ] = this.dotVectors3( v10, v22 ); + m3[ 3 ] = this.dotVectors3( v11, v20 ); + m3[ 4 ] = this.dotVectors3( v11, v21 ); + m3[ 5 ] = this.dotVectors3( v11, v22 ); + m3[ 6 ] = this.dotVectors3( v12, v20 ); + m3[ 7 ] = this.dotVectors3( v12, v21 ); + m3[ 8 ] = this.dotVectors3( v12, v22 ); + + this.freeVector3( v10 ); + this.freeVector3( v11 ); + this.freeVector3( v12 ); + this.freeVector3( v20 ); + this.freeVector3( v21 ); + this.freeVector3( v22 ); + + return m3; + + } + + addVector3( v1, v2 ) { + + var v = this.allocVector3(); + v.setValue( v1.x() + v2.x(), v1.y() + v2.y(), v1.z() + v2.z() ); + return v; + + } + + dotVectors3( v1, v2 ) { + + return v1.x() * v2.x() + v1.y() * v2.y() + v1.z() * v2.z(); + + } + + rowOfMatrix3( m, i ) { + + var v = this.allocVector3(); + v.setValue( m[ i * 3 + 0 ], m[ i * 3 + 1 ], m[ i * 3 + 2 ] ); + return v; + + } + + columnOfMatrix3( m, i ) { + + var v = this.allocVector3(); + v.setValue( m[ i + 0 ], m[ i + 3 ], m[ i + 6 ] ); + return v; + + } + + negativeVector3( v ) { + + var v2 = this.allocVector3(); + v2.setValue( - v.x(), - v.y(), - v.z() ); + return v2; + + } + + multiplyMatrix3ByVector3( m, v ) { + + var v4 = this.allocVector3(); + + var v0 = this.rowOfMatrix3( m, 0 ); + var v1 = this.rowOfMatrix3( m, 1 ); + var v2 = this.rowOfMatrix3( m, 2 ); + var x = this.dotVectors3( v0, v ); + var y = this.dotVectors3( v1, v ); + var z = this.dotVectors3( v2, v ); + + v4.setValue( x, y, z ); + + this.freeVector3( v0 ); + this.freeVector3( v1 ); + this.freeVector3( v2 ); + + return v4; + + } + + transposeMatrix3( m ) { + + var m2 = []; + m2[ 0 ] = m[ 0 ]; + m2[ 1 ] = m[ 3 ]; + m2[ 2 ] = m[ 6 ]; + m2[ 3 ] = m[ 1 ]; + m2[ 4 ] = m[ 4 ]; + m2[ 5 ] = m[ 7 ]; + m2[ 6 ] = m[ 2 ]; + m2[ 7 ] = m[ 5 ]; + m2[ 8 ] = m[ 8 ]; + return m2; + + } + + quaternionToMatrix3( q ) { + + var m = []; + + var x = q.x(); + var y = q.y(); + var z = q.z(); + var w = q.w(); + + var xx = x * x; + var yy = y * y; + var zz = z * z; + + var xy = x * y; + var yz = y * z; + var zx = z * x; + + var xw = x * w; + var yw = y * w; + var zw = z * w; + + m[ 0 ] = 1 - 2 * ( yy + zz ); + m[ 1 ] = 2 * ( xy - zw ); + m[ 2 ] = 2 * ( zx + yw ); + m[ 3 ] = 2 * ( xy + zw ); + m[ 4 ] = 1 - 2 * ( zz + xx ); + m[ 5 ] = 2 * ( yz - xw ); + m[ 6 ] = 2 * ( zx - yw ); + m[ 7 ] = 2 * ( yz + xw ); + m[ 8 ] = 1 - 2 * ( xx + yy ); + + return m; + + } + + matrix3ToQuaternion( m ) { + + var t = m[ 0 ] + m[ 4 ] + m[ 8 ]; + var s, x, y, z, w; + + if ( t > 0 ) { + + s = Math.sqrt( t + 1.0 ) * 2; + w = 0.25 * s; + x = ( m[ 7 ] - m[ 5 ] ) / s; + y = ( m[ 2 ] - m[ 6 ] ) / s; + z = ( m[ 3 ] - m[ 1 ] ) / s; + + } else if ( ( m[ 0 ] > m[ 4 ] ) && ( m[ 0 ] > m[ 8 ] ) ) { + + s = Math.sqrt( 1.0 + m[ 0 ] - m[ 4 ] - m[ 8 ] ) * 2; + w = ( m[ 7 ] - m[ 5 ] ) / s; + x = 0.25 * s; + y = ( m[ 1 ] + m[ 3 ] ) / s; + z = ( m[ 2 ] + m[ 6 ] ) / s; + + } else if ( m[ 4 ] > m[ 8 ] ) { + + s = Math.sqrt( 1.0 + m[ 4 ] - m[ 0 ] - m[ 8 ] ) * 2; + w = ( m[ 2 ] - m[ 6 ] ) / s; + x = ( m[ 1 ] + m[ 3 ] ) / s; + y = 0.25 * s; + z = ( m[ 5 ] + m[ 7 ] ) / s; + + } else { + + s = Math.sqrt( 1.0 + m[ 8 ] - m[ 0 ] - m[ 4 ] ) * 2; + w = ( m[ 3 ] - m[ 1 ] ) / s; + x = ( m[ 2 ] + m[ 6 ] ) / s; + y = ( m[ 5 ] + m[ 7 ] ) / s; + z = 0.25 * s; + + } + + var q = this.allocQuaternion(); + q.setX( x ); + q.setY( y ); + q.setZ( z ); + q.setW( w ); + return q; + + } + +} + +/** + * @param {THREE.SkinnedMesh} mesh + * @param {Ammo.btDiscreteDynamicsWorld} world + * @param {Object} params + * @param {ResourceManager} manager + */ +class RigidBody { + + constructor( mesh, world, params, manager ) { + + this.mesh = mesh; + this.world = world; + this.params = params; + this.manager = manager; + + this.body = null; + this.bone = null; + this.boneOffsetForm = null; + this.boneOffsetFormInverse = null; + + this._init(); + + } + + /** + * Resets rigid body transform to the current bone's. + * + * @return {RigidBody} + */ + reset() { + + this._setTransformFromBone(); + return this; + + } + + /** + * Updates rigid body's transform from the current bone. + * + * @return {RidigBody} + */ + updateFromBone() { + + if ( this.params.boneIndex !== - 1 && this.params.type === 0 ) { + + this._setTransformFromBone(); + + } + + return this; + + } + + /** + * Updates bone from the current ridid body's transform. + * + * @return {RidigBody} + */ + updateBone() { + + if ( this.params.type === 0 || this.params.boneIndex === - 1 ) { + + return this; + + } + + this._updateBoneRotation(); + + if ( this.params.type === 1 ) { + + this._updateBonePosition(); + + } + + this.bone.updateMatrixWorld( true ); + + if ( this.params.type === 2 ) { + + this._setPositionFromBone(); + + } + + return this; + + } + + // private methods + + _init() { + + function generateShape( p ) { + + switch ( p.shapeType ) { + + case 0: + return new Ammo.btSphereShape( p.width ); + + case 1: + return new Ammo.btBoxShape( new Ammo.btVector3( p.width, p.height, p.depth ) ); + + case 2: + return new Ammo.btCapsuleShape( p.width, p.height ); + + default: + throw new Error( 'unknown shape type ' + p.shapeType ); + + } + + } + + const manager = this.manager; + const params = this.params; + const bones = this.mesh.skeleton.bones; + const bone = ( params.boneIndex === - 1 ) + ? new Bone() + : bones[ params.boneIndex ]; + + const shape = generateShape( params ); + const weight = ( params.type === 0 ) ? 0 : params.weight; + const localInertia = manager.allocVector3(); + localInertia.setValue( 0, 0, 0 ); + + if ( weight !== 0 ) { + + shape.calculateLocalInertia( weight, localInertia ); + + } + + const boneOffsetForm = manager.allocTransform(); + manager.setIdentity( boneOffsetForm ); + manager.setOriginFromArray3( boneOffsetForm, params.position ); + manager.setBasisFromArray3( boneOffsetForm, params.rotation ); + + const vector = manager.allocThreeVector3(); + const boneForm = manager.allocTransform(); + manager.setIdentity( boneForm ); + manager.setOriginFromThreeVector3( boneForm, bone.getWorldPosition( vector ) ); + + const form = manager.multiplyTransforms( boneForm, boneOffsetForm ); + const state = new Ammo.btDefaultMotionState( form ); + + const info = new Ammo.btRigidBodyConstructionInfo( weight, state, shape, localInertia ); + info.set_m_friction( params.friction ); + info.set_m_restitution( params.restitution ); + + const body = new Ammo.btRigidBody( info ); + + if ( params.type === 0 ) { + + body.setCollisionFlags( body.getCollisionFlags() | 2 ); + + /* + * It'd be better to comment out this line though in general I should call this method + * because I'm not sure why but physics will be more like MMD's + * if I comment out. + */ + body.setActivationState( 4 ); + + } + + body.setDamping( params.positionDamping, params.rotationDamping ); + body.setSleepingThresholds( 0, 0 ); + + this.world.addRigidBody( body, 1 << params.groupIndex, params.groupTarget ); + + this.body = body; + this.bone = bone; + this.boneOffsetForm = boneOffsetForm; + this.boneOffsetFormInverse = manager.inverseTransform( boneOffsetForm ); + + manager.freeVector3( localInertia ); + manager.freeTransform( form ); + manager.freeTransform( boneForm ); + manager.freeThreeVector3( vector ); + + } + + _getBoneTransform() { + + const manager = this.manager; + const p = manager.allocThreeVector3(); + const q = manager.allocThreeQuaternion(); + const s = manager.allocThreeVector3(); + + this.bone.matrixWorld.decompose( p, q, s ); + + const tr = manager.allocTransform(); + manager.setOriginFromThreeVector3( tr, p ); + manager.setBasisFromThreeQuaternion( tr, q ); + + const form = manager.multiplyTransforms( tr, this.boneOffsetForm ); + + manager.freeTransform( tr ); + manager.freeThreeVector3( s ); + manager.freeThreeQuaternion( q ); + manager.freeThreeVector3( p ); + + return form; + + } + + _getWorldTransformForBone() { + + const manager = this.manager; + const tr = this.body.getCenterOfMassTransform(); + return manager.multiplyTransforms( tr, this.boneOffsetFormInverse ); + + } + + _setTransformFromBone() { + + const manager = this.manager; + const form = this._getBoneTransform(); + + // TODO: check the most appropriate way to set + //this.body.setWorldTransform( form ); + this.body.setCenterOfMassTransform( form ); + this.body.getMotionState().setWorldTransform( form ); + + manager.freeTransform( form ); + + } + + _setPositionFromBone() { + + const manager = this.manager; + const form = this._getBoneTransform(); + + const tr = manager.allocTransform(); + this.body.getMotionState().getWorldTransform( tr ); + manager.copyOrigin( tr, form ); + + // TODO: check the most appropriate way to set + //this.body.setWorldTransform( tr ); + this.body.setCenterOfMassTransform( tr ); + this.body.getMotionState().setWorldTransform( tr ); + + manager.freeTransform( tr ); + manager.freeTransform( form ); + + } + + _updateBoneRotation() { + + const manager = this.manager; + + const tr = this._getWorldTransformForBone(); + const q = manager.getBasis( tr ); + + const thQ = manager.allocThreeQuaternion(); + const thQ2 = manager.allocThreeQuaternion(); + const thQ3 = manager.allocThreeQuaternion(); + + thQ.set( q.x(), q.y(), q.z(), q.w() ); + thQ2.setFromRotationMatrix( this.bone.matrixWorld ); + thQ2.conjugate(); + thQ2.multiply( thQ ); + + //this.bone.quaternion.multiply( thQ2 ); + + thQ3.setFromRotationMatrix( this.bone.matrix ); + + // Renormalizing quaternion here because repeatedly transforming + // quaternion continuously accumulates floating point error and + // can end up being overflow. See #15335 + this.bone.quaternion.copy( thQ2.multiply( thQ3 ).normalize() ); + + manager.freeThreeQuaternion( thQ ); + manager.freeThreeQuaternion( thQ2 ); + manager.freeThreeQuaternion( thQ3 ); + + manager.freeQuaternion( q ); + manager.freeTransform( tr ); + + } + + _updateBonePosition() { + + const manager = this.manager; + + const tr = this._getWorldTransformForBone(); + + const thV = manager.allocThreeVector3(); + + const o = manager.getOrigin( tr ); + thV.set( o.x(), o.y(), o.z() ); + + if ( this.bone.parent ) { + + this.bone.parent.worldToLocal( thV ); + + } + + this.bone.position.copy( thV ); + + manager.freeThreeVector3( thV ); + + manager.freeTransform( tr ); + + } + +} + +// + +class Constraint { + + /** + * @param {THREE.SkinnedMesh} mesh + * @param {Ammo.btDiscreteDynamicsWorld} world + * @param {RigidBody} bodyA + * @param {RigidBody} bodyB + * @param {Object} params + * @param {ResourceManager} manager + */ + constructor( mesh, world, bodyA, bodyB, params, manager ) { + + this.mesh = mesh; + this.world = world; + this.bodyA = bodyA; + this.bodyB = bodyB; + this.params = params; + this.manager = manager; + + this.constraint = null; + + this._init(); + + } + + // private method + + _init() { + + const manager = this.manager; + const params = this.params; + const bodyA = this.bodyA; + const bodyB = this.bodyB; + + const form = manager.allocTransform(); + manager.setIdentity( form ); + manager.setOriginFromArray3( form, params.position ); + manager.setBasisFromArray3( form, params.rotation ); + + const formA = manager.allocTransform(); + const formB = manager.allocTransform(); + + bodyA.body.getMotionState().getWorldTransform( formA ); + bodyB.body.getMotionState().getWorldTransform( formB ); + + const formInverseA = manager.inverseTransform( formA ); + const formInverseB = manager.inverseTransform( formB ); + + const formA2 = manager.multiplyTransforms( formInverseA, form ); + const formB2 = manager.multiplyTransforms( formInverseB, form ); + + const constraint = new Ammo.btGeneric6DofSpringConstraint( bodyA.body, bodyB.body, formA2, formB2, true ); + + const lll = manager.allocVector3(); + const lul = manager.allocVector3(); + const all = manager.allocVector3(); + const aul = manager.allocVector3(); + + lll.setValue( params.translationLimitation1[ 0 ], + params.translationLimitation1[ 1 ], + params.translationLimitation1[ 2 ] ); + lul.setValue( params.translationLimitation2[ 0 ], + params.translationLimitation2[ 1 ], + params.translationLimitation2[ 2 ] ); + all.setValue( params.rotationLimitation1[ 0 ], + params.rotationLimitation1[ 1 ], + params.rotationLimitation1[ 2 ] ); + aul.setValue( params.rotationLimitation2[ 0 ], + params.rotationLimitation2[ 1 ], + params.rotationLimitation2[ 2 ] ); + + constraint.setLinearLowerLimit( lll ); + constraint.setLinearUpperLimit( lul ); + constraint.setAngularLowerLimit( all ); + constraint.setAngularUpperLimit( aul ); + + for ( let i = 0; i < 3; i ++ ) { + + if ( params.springPosition[ i ] !== 0 ) { + + constraint.enableSpring( i, true ); + constraint.setStiffness( i, params.springPosition[ i ] ); + + } + + } + + for ( let i = 0; i < 3; i ++ ) { + + if ( params.springRotation[ i ] !== 0 ) { + + constraint.enableSpring( i + 3, true ); + constraint.setStiffness( i + 3, params.springRotation[ i ] ); + + } + + } + + /* + * Currently(10/31/2016) official ammo.js doesn't support + * btGeneric6DofSpringConstraint.setParam method. + * You need custom ammo.js (add the method into idl) if you wanna use. + * By setting this parameter, physics will be more like MMD's + */ + if ( constraint.setParam !== undefined ) { + + for ( let i = 0; i < 6; i ++ ) { + + constraint.setParam( 2, 0.475, i ); + + } + + } + + this.world.addConstraint( constraint, true ); + this.constraint = constraint; + + manager.freeTransform( form ); + manager.freeTransform( formA ); + manager.freeTransform( formB ); + manager.freeTransform( formInverseA ); + manager.freeTransform( formInverseB ); + manager.freeTransform( formA2 ); + manager.freeTransform( formB2 ); + manager.freeVector3( lll ); + manager.freeVector3( lul ); + manager.freeVector3( all ); + manager.freeVector3( aul ); + + } + +} + +// + +const _position = new Vector3(); +const _quaternion = new Quaternion(); +const _scale = new Vector3(); +const _matrixWorldInv = new Matrix4(); + +class MMDPhysicsHelper extends Object3D { + + /** + * Visualize Rigid bodies + * + * @param {THREE.SkinnedMesh} mesh + * @param {Physics} physics + */ + constructor( mesh, physics ) { + + super(); + + this.root = mesh; + this.physics = physics; + + this.matrix.copy( mesh.matrixWorld ); + this.matrixAutoUpdate = false; + + this.materials = []; + + this.materials.push( + new MeshBasicMaterial( { + color: new Color( 0xff8888 ), + wireframe: true, + depthTest: false, + depthWrite: false, + opacity: 0.25, + transparent: true + } ) + ); + + this.materials.push( + new MeshBasicMaterial( { + color: new Color( 0x88ff88 ), + wireframe: true, + depthTest: false, + depthWrite: false, + opacity: 0.25, + transparent: true + } ) + ); + + this.materials.push( + new MeshBasicMaterial( { + color: new Color( 0x8888ff ), + wireframe: true, + depthTest: false, + depthWrite: false, + opacity: 0.25, + transparent: true + } ) + ); + + this._init(); + + } + + /** + * Updates Rigid Bodies visualization. + */ + updateMatrixWorld( force ) { + + var mesh = this.root; + + if ( this.visible ) { + + var bodies = this.physics.bodies; + + _matrixWorldInv + .copy( mesh.matrixWorld ) + .decompose( _position, _quaternion, _scale ) + .compose( _position, _quaternion, _scale.set( 1, 1, 1 ) ) + .invert(); + + for ( var i = 0, il = bodies.length; i < il; i ++ ) { + + var body = bodies[ i ].body; + var child = this.children[ i ]; + + var tr = body.getCenterOfMassTransform(); + var origin = tr.getOrigin(); + var rotation = tr.getRotation(); + + child.position + .set( origin.x(), origin.y(), origin.z() ) + .applyMatrix4( _matrixWorldInv ); + + child.quaternion + .setFromRotationMatrix( _matrixWorldInv ) + .multiply( + _quaternion.set( rotation.x(), rotation.y(), rotation.z(), rotation.w() ) + ); + + } + + } + + this.matrix + .copy( mesh.matrixWorld ) + .decompose( _position, _quaternion, _scale ) + .compose( _position, _quaternion, _scale.set( 1, 1, 1 ) ); + + super.updateMatrixWorld( force ); + + } + + // private method + + _init() { + + var bodies = this.physics.bodies; + + function createGeometry( param ) { + + switch ( param.shapeType ) { + + case 0: + return new SphereGeometry( param.width, 16, 8 ); + + case 1: + return new BoxGeometry( param.width * 2, param.height * 2, param.depth * 2, 8, 8, 8 ); + + case 2: + return new createCapsuleGeometry( param.width, param.height, 16, 8 ); + + default: + return null; + + } + + } + + function createCapsuleGeometry( radius, cylinderHeight, segmentsRadius, segmentsHeight ) { + + var geometry = new CylinderGeometry( radius, radius, cylinderHeight, segmentsRadius, segmentsHeight, true ); + var upperSphere = new Mesh( new SphereGeometry( radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, 0, Math.PI / 2 ) ); + var lowerSphere = new Mesh( new SphereGeometry( radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, Math.PI / 2, Math.PI / 2 ) ); + + upperSphere.position.set( 0, cylinderHeight / 2, 0 ); + lowerSphere.position.set( 0, - cylinderHeight / 2, 0 ); + + upperSphere.updateMatrix(); + lowerSphere.updateMatrix(); + + geometry.merge( upperSphere.geometry, upperSphere.matrix ); + geometry.merge( lowerSphere.geometry, lowerSphere.matrix ); + + return geometry; + + } + + for ( var i = 0, il = bodies.length; i < il; i ++ ) { + + var param = bodies[ i ].params; + this.add( new Mesh( createGeometry( param ), this.materials[ param.type ] ) ); + + } + + } + +} + +export { MMDPhysics }; diff --git a/jsm/cameras/CinematicCamera.js b/jsm/cameras/CinematicCamera.js new file mode 100644 index 0000000..47b94e7 --- /dev/null +++ b/jsm/cameras/CinematicCamera.js @@ -0,0 +1,209 @@ +import { + Mesh, + OrthographicCamera, + PerspectiveCamera, + PlaneGeometry, + Scene, + ShaderMaterial, + UniformsUtils, + WebGLRenderTarget +} from 'three'; + +import { BokehShader } from '../shaders/BokehShader2.js'; +import { BokehDepthShader } from '../shaders/BokehShader2.js'; + +class CinematicCamera extends PerspectiveCamera { + + constructor( fov, aspect, near, far ) { + + super( fov, aspect, near, far ); + + this.type = 'CinematicCamera'; + + this.postprocessing = { enabled: true }; + this.shaderSettings = { + rings: 3, + samples: 4 + }; + + const depthShader = BokehDepthShader; + + this.materialDepth = new ShaderMaterial( { + uniforms: depthShader.uniforms, + vertexShader: depthShader.vertexShader, + fragmentShader: depthShader.fragmentShader + } ); + + this.materialDepth.uniforms[ 'mNear' ].value = near; + this.materialDepth.uniforms[ 'mFar' ].value = far; + + // In case of cinematicCamera, having a default lens set is important + this.setLens(); + + this.initPostProcessing(); + + } + + // providing fnumber and coc(Circle of Confusion) as extra arguments + // In case of cinematicCamera, having a default lens set is important + // if fnumber and coc are not provided, cinematicCamera tries to act as a basic PerspectiveCamera + setLens( focalLength = 35, filmGauge = 35, fNumber = 8, coc = 0.019 ) { + + this.filmGauge = filmGauge; + + this.setFocalLength( focalLength ); + + this.fNumber = fNumber; + this.coc = coc; + + // fNumber is focalLength by aperture + this.aperture = focalLength / this.fNumber; + + // hyperFocal is required to calculate depthOfField when a lens tries to focus at a distance with given fNumber and focalLength + this.hyperFocal = ( focalLength * focalLength ) / ( this.aperture * this.coc ); + + } + + linearize( depth ) { + + const zfar = this.far; + const znear = this.near; + return - zfar * znear / ( depth * ( zfar - znear ) - zfar ); + + } + + smoothstep( near, far, depth ) { + + const x = this.saturate( ( depth - near ) / ( far - near ) ); + return x * x * ( 3 - 2 * x ); + + } + + saturate( x ) { + + return Math.max( 0, Math.min( 1, x ) ); + + } + + // function for focusing at a distance from the camera + focusAt( focusDistance = 20 ) { + + const focalLength = this.getFocalLength(); + + // distance from the camera (normal to frustrum) to focus on + this.focus = focusDistance; + + // the nearest point from the camera which is in focus (unused) + this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) ); + + // the farthest point from the camera which is in focus (unused) + this.farPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal - ( this.focus - focalLength ) ); + + // the gap or width of the space in which is everything is in focus (unused) + this.depthOfField = this.farPoint - this.nearPoint; + + // Considering minimum distance of focus for a standard lens (unused) + if ( this.depthOfField < 0 ) this.depthOfField = 0; + + this.sdistance = this.smoothstep( this.near, this.far, this.focus ); + + this.ldistance = this.linearize( 1 - this.sdistance ); + + this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance; + + } + + initPostProcessing() { + + if ( this.postprocessing.enabled ) { + + this.postprocessing.scene = new Scene(); + + this.postprocessing.camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 ); + + this.postprocessing.scene.add( this.postprocessing.camera ); + + this.postprocessing.rtTextureDepth = new WebGLRenderTarget( window.innerWidth, window.innerHeight ); + this.postprocessing.rtTextureColor = new WebGLRenderTarget( window.innerWidth, window.innerHeight ); + + const bokeh_shader = BokehShader; + + this.postprocessing.bokeh_uniforms = UniformsUtils.clone( bokeh_shader.uniforms ); + + this.postprocessing.bokeh_uniforms[ 'tColor' ].value = this.postprocessing.rtTextureColor.texture; + this.postprocessing.bokeh_uniforms[ 'tDepth' ].value = this.postprocessing.rtTextureDepth.texture; + + this.postprocessing.bokeh_uniforms[ 'manualdof' ].value = 0; + this.postprocessing.bokeh_uniforms[ 'shaderFocus' ].value = 0; + + this.postprocessing.bokeh_uniforms[ 'fstop' ].value = 2.8; + + this.postprocessing.bokeh_uniforms[ 'showFocus' ].value = 1; + + this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = 0.1; + + //console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value ); + + this.postprocessing.bokeh_uniforms[ 'znear' ].value = this.near; + this.postprocessing.bokeh_uniforms[ 'zfar' ].value = this.near; + + + this.postprocessing.bokeh_uniforms[ 'textureWidth' ].value = window.innerWidth; + + this.postprocessing.bokeh_uniforms[ 'textureHeight' ].value = window.innerHeight; + + this.postprocessing.materialBokeh = new ShaderMaterial( { + uniforms: this.postprocessing.bokeh_uniforms, + vertexShader: bokeh_shader.vertexShader, + fragmentShader: bokeh_shader.fragmentShader, + defines: { + RINGS: this.shaderSettings.rings, + SAMPLES: this.shaderSettings.samples, + DEPTH_PACKING: 1 + } + } ); + + this.postprocessing.quad = new Mesh( new PlaneGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh ); + this.postprocessing.quad.position.z = - 500; + this.postprocessing.scene.add( this.postprocessing.quad ); + + } + + } + + renderCinematic( scene, renderer ) { + + if ( this.postprocessing.enabled ) { + + const currentRenderTarget = renderer.getRenderTarget(); + + renderer.clear(); + + // Render scene into texture + + scene.overrideMaterial = null; + renderer.setRenderTarget( this.postprocessing.rtTextureColor ); + renderer.clear(); + renderer.render( scene, this ); + + // Render depth into texture + + scene.overrideMaterial = this.materialDepth; + renderer.setRenderTarget( this.postprocessing.rtTextureDepth ); + renderer.clear(); + renderer.render( scene, this ); + + // Render bokeh composite + + renderer.setRenderTarget( null ); + renderer.render( this.postprocessing.scene, this.postprocessing.camera ); + + renderer.setRenderTarget( currentRenderTarget ); + + } + + } + +} + +export { CinematicCamera }; diff --git a/jsm/capabilities/WebGL.js b/jsm/capabilities/WebGL.js new file mode 100644 index 0000000..08666fe --- /dev/null +++ b/jsm/capabilities/WebGL.js @@ -0,0 +1,91 @@ +class WebGL { + + static isWebGLAvailable() { + + try { + + const canvas = document.createElement( 'canvas' ); + return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); + + } catch ( e ) { + + return false; + + } + + } + + static isWebGL2Available() { + + try { + + const canvas = document.createElement( 'canvas' ); + return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) ); + + } catch ( e ) { + + return false; + + } + + } + + static getWebGLErrorMessage() { + + return this.getErrorMessage( 1 ); + + } + + static getWebGL2ErrorMessage() { + + return this.getErrorMessage( 2 ); + + } + + static getErrorMessage( version ) { + + const names = { + 1: 'WebGL', + 2: 'WebGL 2' + }; + + const contexts = { + 1: window.WebGLRenderingContext, + 2: window.WebGL2RenderingContext + }; + + let message = 'Your $0 does not seem to support $1'; + + const element = document.createElement( 'div' ); + element.id = 'webglmessage'; + element.style.fontFamily = 'monospace'; + element.style.fontSize = '13px'; + element.style.fontWeight = 'normal'; + element.style.textAlign = 'center'; + element.style.background = '#fff'; + element.style.color = '#000'; + element.style.padding = '1.5em'; + element.style.width = '400px'; + element.style.margin = '5em auto 0'; + + if ( contexts[ version ] ) { + + message = message.replace( '$0', 'graphics card' ); + + } else { + + message = message.replace( '$0', 'browser' ); + + } + + message = message.replace( '$1', names[ version ] ); + + element.innerHTML = message; + + return element; + + } + +} + +export default WebGL; diff --git a/jsm/capabilities/WebGPU.js b/jsm/capabilities/WebGPU.js new file mode 100644 index 0000000..5351ea3 --- /dev/null +++ b/jsm/capabilities/WebGPU.js @@ -0,0 +1,33 @@ +class WebGPU { + + static isAvailable() { + + return ( navigator.gpu !== undefined ); + + } + + static getErrorMessage() { + + const message = 'Your browser does not support WebGPU'; + + const element = document.createElement( 'div' ); + element.id = 'webgpumessage'; + element.style.fontFamily = 'monospace'; + element.style.fontSize = '13px'; + element.style.fontWeight = 'normal'; + element.style.textAlign = 'center'; + element.style.background = '#fff'; + element.style.color = '#000'; + element.style.padding = '1.5em'; + element.style.width = '400px'; + element.style.margin = '5em auto 0'; + + element.innerHTML = message; + + return element; + + } + +} + +export default WebGPU; diff --git a/jsm/controls/ArcballControls.js b/jsm/controls/ArcballControls.js new file mode 100644 index 0000000..e7e57c7 --- /dev/null +++ b/jsm/controls/ArcballControls.js @@ -0,0 +1,3201 @@ +import { + GridHelper, + EllipseCurve, + BufferGeometry, + Line, + LineBasicMaterial, + Raycaster, + Group, + Box3, + Sphere, + Quaternion, + Vector2, + Vector3, + Matrix4, + MathUtils, + EventDispatcher +} from 'three'; + +//trackball state +const STATE = { + + IDLE: Symbol(), + ROTATE: Symbol(), + PAN: Symbol(), + SCALE: Symbol(), + FOV: Symbol(), + FOCUS: Symbol(), + ZROTATE: Symbol(), + TOUCH_MULTI: Symbol(), + ANIMATION_FOCUS: Symbol(), + ANIMATION_ROTATE: Symbol() + +}; + +const INPUT = { + + NONE: Symbol(), + ONE_FINGER: Symbol(), + ONE_FINGER_SWITCHED: Symbol(), + TWO_FINGER: Symbol(), + MULT_FINGER: Symbol(), + CURSOR: Symbol() + +}; + +//cursor center coordinates +const _center = { + + x: 0, + y: 0 + +}; + +//transformation matrices for gizmos and camera +const _transformation = { + + camera: new Matrix4(), + gizmos: new Matrix4() + +}; + +//events +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +const _raycaster = new Raycaster(); +const _offset = new Vector3(); + +const _gizmoMatrixStateTemp = new Matrix4(); +const _cameraMatrixStateTemp = new Matrix4(); +const _scalePointTemp = new Vector3(); +/** + * + * @param {Camera} camera Virtual camera used in the scene + * @param {HTMLElement} domElement Renderer's dom element + * @param {Scene} scene The scene to be rendered + */ +class ArcballControls extends EventDispatcher { + + constructor( camera, domElement, scene = null ) { + + super(); + this.camera = null; + this.domElement = domElement; + this.scene = scene; + this.target = new Vector3(); + this._currentTarget = new Vector3(); + this.radiusFactor = 0.67; + + this.mouseActions = []; + this._mouseOp = null; + + + //global vectors and matrices that are used in some operations to avoid creating new objects every time (e.g. every time cursor moves) + this._v2_1 = new Vector2(); + this._v3_1 = new Vector3(); + this._v3_2 = new Vector3(); + + this._m4_1 = new Matrix4(); + this._m4_2 = new Matrix4(); + + this._quat = new Quaternion(); + + //transformation matrices + this._translationMatrix = new Matrix4(); //matrix for translation operation + this._rotationMatrix = new Matrix4(); //matrix for rotation operation + this._scaleMatrix = new Matrix4(); //matrix for scaling operation + + this._rotationAxis = new Vector3(); //axis for rotate operation + + + //camera state + this._cameraMatrixState = new Matrix4(); + this._cameraProjectionState = new Matrix4(); + + this._fovState = 1; + this._upState = new Vector3(); + this._zoomState = 1; + this._nearPos = 0; + this._farPos = 0; + + this._gizmoMatrixState = new Matrix4(); + + //initial values + this._up0 = new Vector3(); + this._zoom0 = 1; + this._fov0 = 0; + this._initialNear = 0; + this._nearPos0 = 0; + this._initialFar = 0; + this._farPos0 = 0; + this._cameraMatrixState0 = new Matrix4(); + this._gizmoMatrixState0 = new Matrix4(); + + //pointers array + this._button = - 1; + this._touchStart = []; + this._touchCurrent = []; + this._input = INPUT.NONE; + + //two fingers touch interaction + this._switchSensibility = 32; //minimum movement to be performed to fire single pan start after the second finger has been released + this._startFingerDistance = 0; //distance between two fingers + this._currentFingerDistance = 0; + this._startFingerRotation = 0; //amount of rotation performed with two fingers + this._currentFingerRotation = 0; + + //double tap + this._devPxRatio = 0; + this._downValid = true; + this._nclicks = 0; + this._downEvents = []; + this._downStart = 0; //pointerDown time + this._clickStart = 0; //first click time + this._maxDownTime = 250; + this._maxInterval = 300; + this._posThreshold = 24; + this._movementThreshold = 24; + + //cursor positions + this._currentCursorPosition = new Vector3(); + this._startCursorPosition = new Vector3(); + + //grid + this._grid = null; //grid to be visualized during pan operation + this._gridPosition = new Vector3(); + + //gizmos + this._gizmos = new Group(); + this._curvePts = 128; + + + //animations + this._timeStart = - 1; //initial time + this._animationId = - 1; + + //focus animation + this.focusAnimationTime = 500; //duration of focus animation in ms + + //rotate animation + this._timePrev = 0; //time at which previous rotate operation has been detected + this._timeCurrent = 0; //time at which current rotate operation has been detected + this._anglePrev = 0; //angle of previous rotation + this._angleCurrent = 0; //angle of current rotation + this._cursorPosPrev = new Vector3(); //cursor position when previous rotate operation has been detected + this._cursorPosCurr = new Vector3();//cursor position when current rotate operation has been detected + this._wPrev = 0; //angular velocity of the previous rotate operation + this._wCurr = 0; //angular velocity of the current rotate operation + + + //parameters + this.adjustNearFar = false; + this.scaleFactor = 1.1; //zoom/distance multiplier + this.dampingFactor = 25; + this.wMax = 20; //maximum angular velocity allowed + this.enableAnimations = true; //if animations should be performed + this.enableGrid = false; //if grid should be showed during pan operation + this.cursorZoom = false; //if wheel zoom should be cursor centered + this.minFov = 5; + this.maxFov = 90; + + this.enabled = true; + this.enablePan = true; + this.enableRotate = true; + this.enableZoom = true; + this.enableGizmos = true; + + this.minDistance = 0; + this.maxDistance = Infinity; + this.minZoom = 0; + this.maxZoom = Infinity; + + //trackball parameters + this._tbRadius = 1; + + //FSA + this._state = STATE.IDLE; + + this.setCamera( camera ); + + if ( this.scene != null ) { + + this.scene.add( this._gizmos ); + + } + + this.domElement.style.touchAction = 'none'; + this._devPxRatio = window.devicePixelRatio; + + this.initializeMouseActions(); + + this.domElement.addEventListener( 'contextmenu', this.onContextMenu ); + this.domElement.addEventListener( 'wheel', this.onWheel ); + this.domElement.addEventListener( 'pointerdown', this.onPointerDown ); + this.domElement.addEventListener( 'pointercancel', this.onPointerCancel ); + + window.addEventListener( 'resize', this.onWindowResize ); + + } + + //listeners + + onWindowResize = () => { + + const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3; + this._tbRadius = this.calculateTbRadius( this.camera ); + + const newRadius = this._tbRadius / scale; + const curve = new EllipseCurve( 0, 0, newRadius, newRadius ); + const points = curve.getPoints( this._curvePts ); + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + + for ( const gizmo in this._gizmos.children ) { + + this._gizmos.children[ gizmo ].geometry = curveGeometry; + + } + + this.dispatchEvent( _changeEvent ); + + }; + + onContextMenu = ( event ) => { + + if ( ! this.enabled ) { + + return; + + } + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + if ( this.mouseActions[ i ].mouse == 2 ) { + + //prevent only if button 2 is actually used + event.preventDefault(); + break; + + } + + } + + }; + + onPointerCancel = () => { + + this._touchStart.splice( 0, this._touchStart.length ); + this._touchCurrent.splice( 0, this._touchCurrent.length ); + this._input = INPUT.NONE; + + }; + + onPointerDown = ( event ) => { + + if ( event.button == 0 && event.isPrimary ) { + + this._downValid = true; + this._downEvents.push( event ); + this._downStart = performance.now(); + + } else { + + this._downValid = false; + + } + + if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) { + + this._touchStart.push( event ); + this._touchCurrent.push( event ); + + switch ( this._input ) { + + case INPUT.NONE: + + //singleStart + this._input = INPUT.ONE_FINGER; + this.onSinglePanStart( event, 'ROTATE' ); + + window.addEventListener( 'pointermove', this.onPointerMove ); + window.addEventListener( 'pointerup', this.onPointerUp ); + + break; + + case INPUT.ONE_FINGER: + case INPUT.ONE_FINGER_SWITCHED: + + //doubleStart + this._input = INPUT.TWO_FINGER; + + this.onRotateStart(); + this.onPinchStart(); + this.onDoublePanStart(); + + break; + + case INPUT.TWO_FINGER: + + //multipleStart + this._input = INPUT.MULT_FINGER; + this.onTriplePanStart( event ); + break; + + } + + } else if ( event.pointerType != 'touch' && this._input == INPUT.NONE ) { + + let modifier = null; + + if ( event.ctrlKey || event.metaKey ) { + + modifier = 'CTRL'; + + } else if ( event.shiftKey ) { + + modifier = 'SHIFT'; + + } + + this._mouseOp = this.getOpFromAction( event.button, modifier ); + if ( this._mouseOp != null ) { + + window.addEventListener( 'pointermove', this.onPointerMove ); + window.addEventListener( 'pointerup', this.onPointerUp ); + + //singleStart + this._input = INPUT.CURSOR; + this._button = event.button; + this.onSinglePanStart( event, this._mouseOp ); + + } + + } + + }; + + onPointerMove = ( event ) => { + + if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) { + + switch ( this._input ) { + + case INPUT.ONE_FINGER: + + //singleMove + this.updateTouchEvent( event ); + + this.onSinglePanMove( event, STATE.ROTATE ); + break; + + case INPUT.ONE_FINGER_SWITCHED: + + const movement = this.calculatePointersDistance( this._touchCurrent[ 0 ], event ) * this._devPxRatio; + + if ( movement >= this._switchSensibility ) { + + //singleMove + this._input = INPUT.ONE_FINGER; + this.updateTouchEvent( event ); + + this.onSinglePanStart( event, 'ROTATE' ); + break; + + } + + break; + + case INPUT.TWO_FINGER: + + //rotate/pan/pinchMove + this.updateTouchEvent( event ); + + this.onRotateMove(); + this.onPinchMove(); + this.onDoublePanMove(); + + break; + + case INPUT.MULT_FINGER: + + //multMove + this.updateTouchEvent( event ); + + this.onTriplePanMove( event ); + break; + + } + + } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) { + + let modifier = null; + + if ( event.ctrlKey || event.metaKey ) { + + modifier = 'CTRL'; + + } else if ( event.shiftKey ) { + + modifier = 'SHIFT'; + + } + + const mouseOpState = this.getOpStateFromAction( this._button, modifier ); + + if ( mouseOpState != null ) { + + this.onSinglePanMove( event, mouseOpState ); + + } + + } + + //checkDistance + if ( this._downValid ) { + + const movement = this.calculatePointersDistance( this._downEvents[ this._downEvents.length - 1 ], event ) * this._devPxRatio; + if ( movement > this._movementThreshold ) { + + this._downValid = false; + + } + + } + + }; + + onPointerUp = ( event ) => { + + if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) { + + const nTouch = this._touchCurrent.length; + + for ( let i = 0; i < nTouch; i ++ ) { + + if ( this._touchCurrent[ i ].pointerId == event.pointerId ) { + + this._touchCurrent.splice( i, 1 ); + this._touchStart.splice( i, 1 ); + break; + + } + + } + + switch ( this._input ) { + + case INPUT.ONE_FINGER: + case INPUT.ONE_FINGER_SWITCHED: + + //singleEnd + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + this._input = INPUT.NONE; + this.onSinglePanEnd(); + + break; + + case INPUT.TWO_FINGER: + + //doubleEnd + this.onDoublePanEnd( event ); + this.onPinchEnd( event ); + this.onRotateEnd( event ); + + //switching to singleStart + this._input = INPUT.ONE_FINGER_SWITCHED; + + break; + + case INPUT.MULT_FINGER: + + if ( this._touchCurrent.length == 0 ) { + + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + //multCancel + this._input = INPUT.NONE; + this.onTriplePanEnd(); + + } + + break; + + } + + } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) { + + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + this._input = INPUT.NONE; + this.onSinglePanEnd(); + this._button = - 1; + + } + + if ( event.isPrimary ) { + + if ( this._downValid ) { + + const downTime = event.timeStamp - this._downEvents[ this._downEvents.length - 1 ].timeStamp; + + if ( downTime <= this._maxDownTime ) { + + if ( this._nclicks == 0 ) { + + //first valid click detected + this._nclicks = 1; + this._clickStart = performance.now(); + + } else { + + const clickInterval = event.timeStamp - this._clickStart; + const movement = this.calculatePointersDistance( this._downEvents[ 1 ], this._downEvents[ 0 ] ) * this._devPxRatio; + + if ( clickInterval <= this._maxInterval && movement <= this._posThreshold ) { + + //second valid click detected + //fire double tap and reset values + this._nclicks = 0; + this._downEvents.splice( 0, this._downEvents.length ); + this.onDoubleTap( event ); + + } else { + + //new 'first click' + this._nclicks = 1; + this._downEvents.shift(); + this._clickStart = performance.now(); + + } + + } + + } else { + + this._downValid = false; + this._nclicks = 0; + this._downEvents.splice( 0, this._downEvents.length ); + + } + + } else { + + this._nclicks = 0; + this._downEvents.splice( 0, this._downEvents.length ); + + } + + } + + }; + + onWheel = ( event ) => { + + if ( this.enabled && this.enableZoom ) { + + let modifier = null; + + if ( event.ctrlKey || event.metaKey ) { + + modifier = 'CTRL'; + + } else if ( event.shiftKey ) { + + modifier = 'SHIFT'; + + } + + const mouseOp = this.getOpFromAction( 'WHEEL', modifier ); + + if ( mouseOp != null ) { + + event.preventDefault(); + this.dispatchEvent( _startEvent ); + + const notchDeltaY = 125; //distance of one notch of mouse wheel + let sgn = event.deltaY / notchDeltaY; + + let size = 1; + + if ( sgn > 0 ) { + + size = 1 / this.scaleFactor; + + } else if ( sgn < 0 ) { + + size = this.scaleFactor; + + } + + switch ( mouseOp ) { + + case 'ZOOM': + + this.updateTbState( STATE.SCALE, true ); + + if ( sgn > 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, sgn ) ); + + } else if ( sgn < 0 ) { + + size = Math.pow( this.scaleFactor, - sgn ); + + } + + if ( this.cursorZoom && this.enablePan ) { + + let scalePoint; + + if ( this.camera.isOrthographicCamera ) { + + scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._gizmos.position ); + + } else if ( this.camera.isPerspectiveCamera ) { + + scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).add( this._gizmos.position ); + + } + + this.applyTransformMatrix( this.scale( size, scalePoint ) ); + + } else { + + this.applyTransformMatrix( this.scale( size, this._gizmos.position ) ); + + } + + if ( this._grid != null ) { + + this.disposeGrid(); + this.drawGrid(); + + } + + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _endEvent ); + + break; + + case 'FOV': + + if ( this.camera.isPerspectiveCamera ) { + + this.updateTbState( STATE.FOV, true ); + + + //Vertigo effect + + // fov / 2 + // |\ + // | \ + // | \ + // x | \ + // | \ + // | \ + // | _ _ _\ + // y + + //check for iOs shift shortcut + if ( event.deltaX != 0 ) { + + sgn = event.deltaX / notchDeltaY; + + size = 1; + + if ( sgn > 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, sgn ) ); + + } else if ( sgn < 0 ) { + + size = Math.pow( this.scaleFactor, - sgn ); + + } + + } + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + const x = this._v3_1.distanceTo( this._gizmos.position ); + let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed + + //check min and max distance + xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance ); + + const y = x * Math.tan( MathUtils.DEG2RAD * this.camera.fov * 0.5 ); + + //calculate new fov + let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); + + //check min and max fov + if ( newFov > this.maxFov ) { + + newFov = this.maxFov; + + } else if ( newFov < this.minFov ) { + + newFov = this.minFov; + + } + + const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) ); + size = x / newDistance; + + this.setFov( newFov ); + this.applyTransformMatrix( this.scale( size, this._gizmos.position, false ) ); + + } + + if ( this._grid != null ) { + + this.disposeGrid(); + this.drawGrid(); + + } + + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _endEvent ); + + break; + + } + + } + + } + + }; + + onSinglePanStart = ( event, operation ) => { + + if ( this.enabled ) { + + this.dispatchEvent( _startEvent ); + + this.setCenter( event.clientX, event.clientY ); + + switch ( operation ) { + + case 'PAN': + + if ( ! this.enablePan ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + this.updateTbState( STATE.PAN, true ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + if ( this.enableGrid ) { + + this.drawGrid(); + this.dispatchEvent( _changeEvent ); + + } + + break; + + case 'ROTATE': + + if ( ! this.enableRotate ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + } + + this.updateTbState( STATE.ROTATE, true ); + this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + this.activateGizmos( true ); + if ( this.enableAnimations ) { + + this._timePrev = this._timeCurrent = performance.now(); + this._angleCurrent = this._anglePrev = 0; + this._cursorPosPrev.copy( this._startCursorPosition ); + this._cursorPosCurr.copy( this._cursorPosPrev ); + this._wCurr = 0; + this._wPrev = this._wCurr; + + } + + this.dispatchEvent( _changeEvent ); + break; + + case 'FOV': + + if ( ! this.camera.isPerspectiveCamera || ! this.enableZoom ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + this.updateTbState( STATE.FOV, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + break; + + case 'ZOOM': + + if ( ! this.enableZoom ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + this.updateTbState( STATE.SCALE, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + break; + + } + + } + + }; + + onSinglePanMove = ( event, opState ) => { + + if ( this.enabled ) { + + const restart = opState != this._state; + this.setCenter( event.clientX, event.clientY ); + + switch ( opState ) { + + case STATE.PAN: + + if ( this.enablePan ) { + + if ( restart ) { + + //switch to pan operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + if ( this.enableGrid ) { + + this.drawGrid(); + + } + + this.activateGizmos( false ); + + } else { + + //continue with pan operation + this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition ) ); + + } + + } + + break; + + case STATE.ROTATE: + + if ( this.enableRotate ) { + + if ( restart ) { + + //switch to rotate operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( true ); + + } else { + + //continue with rotate operation + this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + + const distance = this._startCursorPosition.distanceTo( this._currentCursorPosition ); + const angle = this._startCursorPosition.angleTo( this._currentCursorPosition ); + const amount = Math.max( distance / this._tbRadius, angle ); //effective rotation angle + + this.applyTransformMatrix( this.rotate( this.calculateRotationAxis( this._startCursorPosition, this._currentCursorPosition ), amount ) ); + + if ( this.enableAnimations ) { + + this._timePrev = this._timeCurrent; + this._timeCurrent = performance.now(); + this._anglePrev = this._angleCurrent; + this._angleCurrent = amount; + this._cursorPosPrev.copy( this._cursorPosCurr ); + this._cursorPosCurr.copy( this._currentCursorPosition ); + this._wPrev = this._wCurr; + this._wCurr = this.calculateAngularSpeed( this._anglePrev, this._angleCurrent, this._timePrev, this._timeCurrent ); + + } + + } + + } + + break; + + case STATE.SCALE: + + if ( this.enableZoom ) { + + if ( restart ) { + + //switch to zoom operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( false ); + + } else { + + //continue with zoom operation + const screenNotches = 8; //how many wheel notches corresponds to a full screen pan + this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + + const movement = this._currentCursorPosition.y - this._startCursorPosition.y; + + let size = 1; + + if ( movement < 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) ); + + } else if ( movement > 0 ) { + + size = Math.pow( this.scaleFactor, movement * screenNotches ); + + } + + this._v3_1.setFromMatrixPosition(this._gizmoMatrixState); + + this.applyTransformMatrix( this.scale( size, this._v3_1 ) ); + + } + + } + + break; + + case STATE.FOV: + + if ( this.enableZoom && this.camera.isPerspectiveCamera ) { + + if ( restart ) { + + //switch to fov operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( false ); + + } else { + + //continue with fov operation + const screenNotches = 8; //how many wheel notches corresponds to a full screen pan + this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + + const movement = this._currentCursorPosition.y - this._startCursorPosition.y; + + let size = 1; + + if ( movement < 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) ); + + } else if ( movement > 0 ) { + + size = Math.pow( this.scaleFactor, movement * screenNotches ); + + } + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + const x = this._v3_1.distanceTo( this._gizmos.position ); + let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed + + //check min and max distance + xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance ); + + const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 ); + + //calculate new fov + let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); + + //check min and max fov + newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov ); + + const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) ); + size = x / newDistance; + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + + this.setFov( newFov ); + this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); + + //adjusting distance + _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x ); + this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); + + } + + } + + break; + + } + + this.dispatchEvent( _changeEvent ); + + } + + }; + + onSinglePanEnd = () => { + + if ( this._state == STATE.ROTATE ) { + + + if ( ! this.enableRotate ) { + + return; + + } + + if ( this.enableAnimations ) { + + //perform rotation animation + const deltaTime = ( performance.now() - this._timeCurrent ); + if ( deltaTime < 120 ) { + + const w = Math.abs( ( this._wPrev + this._wCurr ) / 2 ); + + const self = this; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.updateTbState( STATE.ANIMATION_ROTATE, true ); + const rotationAxis = self.calculateRotationAxis( self._cursorPosPrev, self._cursorPosCurr ); + + self.onRotationAnim( t, rotationAxis, Math.min( w, self.wMax ) ); + + } ); + + } else { + + //cursor has been standing still for over 120 ms since last movement + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + } else { + + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + } else if ( this._state == STATE.PAN || this._state == STATE.IDLE ) { + + this.updateTbState( STATE.IDLE, false ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + + } + + this.dispatchEvent( _endEvent ); + + }; + + onDoubleTap = ( event ) => { + + if ( this.enabled && this.enablePan && this.scene != null ) { + + this.dispatchEvent( _startEvent ); + + this.setCenter( event.clientX, event.clientY ); + const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.camera ); + + if ( hitP != null && this.enableAnimations ) { + + const self = this; + if ( this._animationId != - 1 ) { + + window.cancelAnimationFrame( this._animationId ); + + } + + this._timeStart = - 1; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.updateTbState( STATE.ANIMATION_FOCUS, true ); + self.onFocusAnim( t, hitP, self._cameraMatrixState, self._gizmoMatrixState ); + + } ); + + } else if ( hitP != null && ! this.enableAnimations ) { + + this.updateTbState( STATE.FOCUS, true ); + this.focus( hitP, this.scaleFactor ); + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _changeEvent ); + + } + + } + + this.dispatchEvent( _endEvent ); + + }; + + onDoublePanStart = () => { + + if ( this.enabled && this.enablePan ) { + + this.dispatchEvent( _startEvent ); + + this.updateTbState( STATE.PAN, true ); + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + this.activateGizmos( false ); + + } + + }; + + onDoublePanMove = () => { + + if ( this.enabled && this.enablePan ) { + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + + if ( this._state != STATE.PAN ) { + + this.updateTbState( STATE.PAN, true ); + this._startCursorPosition.copy( this._currentCursorPosition ); + + } + + this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) ); + this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition, true ) ); + this.dispatchEvent( _changeEvent ); + + } + + }; + + onDoublePanEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _endEvent ); + + }; + + + onRotateStart = () => { + + if ( this.enabled && this.enableRotate ) { + + this.dispatchEvent( _startEvent ); + + this.updateTbState( STATE.ZROTATE, true ); + + //this._startFingerRotation = event.rotation; + + this._startFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] ); + this._currentFingerRotation = this._startFingerRotation; + + this.camera.getWorldDirection( this._rotationAxis ); //rotation axis + + if ( ! this.enablePan && ! this.enableZoom ) { + + this.activateGizmos( true ); + + } + + } + + }; + + onRotateMove = () => { + + if ( this.enabled && this.enableRotate ) { + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + let rotationPoint; + + if ( this._state != STATE.ZROTATE ) { + + this.updateTbState( STATE.ZROTATE, true ); + this._startFingerRotation = this._currentFingerRotation; + + } + + //this._currentFingerRotation = event.rotation; + this._currentFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] ); + + if ( ! this.enablePan ) { + + rotationPoint = new Vector3().setFromMatrixPosition( this._gizmoMatrixState ); + + } else { + + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + rotationPoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._v3_2 ); + + } + + const amount = MathUtils.DEG2RAD * ( this._startFingerRotation - this._currentFingerRotation ); + + this.applyTransformMatrix( this.zRotate( rotationPoint, amount ) ); + this.dispatchEvent( _changeEvent ); + + } + + }; + + onRotateEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + this.dispatchEvent( _endEvent ); + + }; + + onPinchStart = () => { + + if ( this.enabled && this.enableZoom ) { + + this.dispatchEvent( _startEvent ); + this.updateTbState( STATE.SCALE, true ); + + this._startFingerDistance = this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ); + this._currentFingerDistance = this._startFingerDistance; + + this.activateGizmos( false ); + + } + + }; + + onPinchMove = () => { + + if ( this.enabled && this.enableZoom ) { + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + const minDistance = 12; //minimum distance between fingers (in css pixels) + + if ( this._state != STATE.SCALE ) { + + this._startFingerDistance = this._currentFingerDistance; + this.updateTbState( STATE.SCALE, true ); + + } + + this._currentFingerDistance = Math.max( this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ), minDistance * this._devPxRatio ); + const amount = this._currentFingerDistance / this._startFingerDistance; + + let scalePoint; + + if ( ! this.enablePan ) { + + scalePoint = this._gizmos.position; + + } else { + + if ( this.camera.isOrthographicCamera ) { + + scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) + .applyQuaternion( this.camera.quaternion ) + .multiplyScalar( 1 / this.camera.zoom ) + .add( this._gizmos.position ); + + } else if ( this.camera.isPerspectiveCamera ) { + + scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) + .applyQuaternion( this.camera.quaternion ) + .add( this._gizmos.position ); + + } + + } + + this.applyTransformMatrix( this.scale( amount, scalePoint ) ); + this.dispatchEvent( _changeEvent ); + + } + + }; + + onPinchEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _endEvent ); + + }; + + onTriplePanStart = () => { + + if ( this.enabled && this.enableZoom ) { + + this.dispatchEvent( _startEvent ); + + this.updateTbState( STATE.SCALE, true ); + + //const center = event.center; + let clientX = 0; + let clientY = 0; + const nFingers = this._touchCurrent.length; + + for ( let i = 0; i < nFingers; i ++ ) { + + clientX += this._touchCurrent[ i ].clientX; + clientY += this._touchCurrent[ i ].clientY; + + } + + this.setCenter( clientX / nFingers, clientY / nFingers ); + + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + } + + }; + + onTriplePanMove = () => { + + if ( this.enabled && this.enableZoom ) { + + // fov / 2 + // |\ + // | \ + // | \ + // x | \ + // | \ + // | \ + // | _ _ _\ + // y + + //const center = event.center; + let clientX = 0; + let clientY = 0; + const nFingers = this._touchCurrent.length; + + for ( let i = 0; i < nFingers; i ++ ) { + + clientX += this._touchCurrent[ i ].clientX; + clientY += this._touchCurrent[ i ].clientY; + + } + + this.setCenter( clientX / nFingers, clientY / nFingers ); + + const screenNotches = 8; //how many wheel notches corresponds to a full screen pan + this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + + const movement = this._currentCursorPosition.y - this._startCursorPosition.y; + + let size = 1; + + if ( movement < 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) ); + + } else if ( movement > 0 ) { + + size = Math.pow( this.scaleFactor, movement * screenNotches ); + + } + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + const x = this._v3_1.distanceTo( this._gizmos.position ); + let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed + + //check min and max distance + xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance ); + + const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 ); + + //calculate new fov + let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); + + //check min and max fov + newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov ); + + const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) ); + size = x / newDistance; + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + + this.setFov( newFov ); + this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); + + //adjusting distance + _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x ); + this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); + + this.dispatchEvent( _changeEvent ); + + } + + }; + + onTriplePanEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _endEvent ); + //this.dispatchEvent( _changeEvent ); + + }; + + /** + * Set _center's x/y coordinates + * @param {Number} clientX + * @param {Number} clientY + */ + setCenter = ( clientX, clientY ) => { + + _center.x = clientX; + _center.y = clientY; + + }; + + /** + * Set default mouse actions + */ + initializeMouseActions = () => { + + this.setMouseAction( 'PAN', 0, 'CTRL' ); + this.setMouseAction( 'PAN', 2 ); + + this.setMouseAction( 'ROTATE', 0 ); + + this.setMouseAction( 'ZOOM', 'WHEEL' ); + this.setMouseAction( 'ZOOM', 1 ); + + this.setMouseAction( 'FOV', 'WHEEL', 'SHIFT' ); + this.setMouseAction( 'FOV', 1, 'SHIFT' ); + + + }; + + /** + * Compare two mouse actions + * @param {Object} action1 + * @param {Object} action2 + * @returns {Boolean} True if action1 and action 2 are the same mouse action, false otherwise + */ + compareMouseAction = ( action1, action2 ) => { + + if ( action1.operation == action2.operation ) { + + if ( action1.mouse == action2.mouse && action1.key == action2.key ) { + + return true; + + } else { + + return false; + + } + + } else { + + return false; + + } + + }; + + /** + * Set a new mouse action by specifying the operation to be performed and a mouse/key combination. In case of conflict, replaces the existing one + * @param {String} operation The operation to be performed ('PAN', 'ROTATE', 'ZOOM', 'FOV) + * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches + * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed + * @returns {Boolean} True if the mouse action has been successfully added, false otherwise + */ + setMouseAction = ( operation, mouse, key = null ) => { + + const operationInput = [ 'PAN', 'ROTATE', 'ZOOM', 'FOV' ]; + const mouseInput = [ 0, 1, 2, 'WHEEL' ]; + const keyInput = [ 'CTRL', 'SHIFT', null ]; + let state; + + if ( ! operationInput.includes( operation ) || ! mouseInput.includes( mouse ) || ! keyInput.includes( key ) ) { + + //invalid parameters + return false; + + } + + if ( mouse == 'WHEEL' ) { + + if ( operation != 'ZOOM' && operation != 'FOV' ) { + + //cannot associate 2D operation to 1D input + return false; + + } + + } + + switch ( operation ) { + + case 'PAN': + + state = STATE.PAN; + break; + + case 'ROTATE': + + state = STATE.ROTATE; + break; + + case 'ZOOM': + + state = STATE.SCALE; + break; + + case 'FOV': + + state = STATE.FOV; + break; + + } + + const action = { + + operation: operation, + mouse: mouse, + key: key, + state: state + + }; + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + if ( this.mouseActions[ i ].mouse == action.mouse && this.mouseActions[ i ].key == action.key ) { + + this.mouseActions.splice( i, 1, action ); + return true; + + } + + } + + this.mouseActions.push( action ); + return true; + + }; + + /** + * Remove a mouse action by specifying its mouse/key combination + * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches + * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed + * @returns {Boolean} True if the operation has been succesfully removed, false otherwise + */ + unsetMouseAction = ( mouse, key = null ) => { + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + if ( this.mouseActions[ i ].mouse == mouse && this.mouseActions[ i ].key == key ) { + + this.mouseActions.splice( i, 1 ); + return true; + + } + + } + + return false; + + }; + + /** + * Return the operation associated to a mouse/keyboard combination + * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches + * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed + * @returns The operation if it has been found, null otherwise + */ + getOpFromAction = ( mouse, key ) => { + + let action; + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == key ) { + + return action.operation; + + } + + } + + if ( key != null ) { + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == null ) { + + return action.operation; + + } + + } + + } + + return null; + + }; + + /** + * Get the operation associated to mouse and key combination and returns the corresponding FSA state + * @param {Number} mouse Mouse button + * @param {String} key Keyboard modifier + * @returns The FSA state obtained from the operation associated to mouse/keyboard combination + */ + getOpStateFromAction = ( mouse, key ) => { + + let action; + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == key ) { + + return action.state; + + } + + } + + if ( key != null ) { + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == null ) { + + return action.state; + + } + + } + + } + + return null; + + }; + + /** + * Calculate the angle between two pointers + * @param {PointerEvent} p1 + * @param {PointerEvent} p2 + * @returns {Number} The angle between two pointers in degrees + */ + getAngle = ( p1, p2 ) => { + + return Math.atan2( p2.clientY - p1.clientY, p2.clientX - p1.clientX ) * 180 / Math.PI; + + }; + + /** + * Update a PointerEvent inside current pointerevents array + * @param {PointerEvent} event + */ + updateTouchEvent = ( event ) => { + + for ( let i = 0; i < this._touchCurrent.length; i ++ ) { + + if ( this._touchCurrent[ i ].pointerId == event.pointerId ) { + + this._touchCurrent.splice( i, 1, event ); + break; + + } + + } + + }; + + /** + * Apply a transformation matrix, to the camera and gizmos + * @param {Object} transformation Object containing matrices to apply to camera and gizmos + */ + applyTransformMatrix( transformation ) { + + if ( transformation.camera != null ) { + + this._m4_1.copy( this._cameraMatrixState ).premultiply( transformation.camera ); + this._m4_1.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + this.camera.updateMatrix(); + + //update camera up vector + if ( this._state == STATE.ROTATE || this._state == STATE.ZROTATE || this._state == STATE.ANIMATION_ROTATE ) { + + this.camera.up.copy( this._upState ).applyQuaternion( this.camera.quaternion ); + + } + + } + + if ( transformation.gizmos != null ) { + + this._m4_1.copy( this._gizmoMatrixState ).premultiply( transformation.gizmos ); + this._m4_1.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + this._gizmos.updateMatrix(); + + } + + if ( this._state == STATE.SCALE || this._state == STATE.FOCUS || this._state == STATE.ANIMATION_FOCUS ) { + + this._tbRadius = this.calculateTbRadius( this.camera ); + + if ( this.adjustNearFar ) { + + const cameraDistance = this.camera.position.distanceTo( this._gizmos.position ); + + const bb = new Box3(); + bb.setFromObject( this._gizmos ); + const sphere = new Sphere(); + bb.getBoundingSphere( sphere ); + + const adjustedNearPosition = Math.max( this._nearPos0, sphere.radius + sphere.center.length() ); + const regularNearPosition = cameraDistance - this._initialNear; + + const minNearPos = Math.min( adjustedNearPosition, regularNearPosition ); + this.camera.near = cameraDistance - minNearPos; + + + const adjustedFarPosition = Math.min( this._farPos0, - sphere.radius + sphere.center.length() ); + const regularFarPosition = cameraDistance - this._initialFar; + + const minFarPos = Math.min( adjustedFarPosition, regularFarPosition ); + this.camera.far = cameraDistance - minFarPos; + + this.camera.updateProjectionMatrix(); + + } else { + + let update = false; + + if ( this.camera.near != this._initialNear ) { + + this.camera.near = this._initialNear; + update = true; + + } + + if ( this.camera.far != this._initialFar ) { + + this.camera.far = this._initialFar; + update = true; + + } + + if ( update ) { + + this.camera.updateProjectionMatrix(); + + } + + } + + } + + } + + /** + * Calculate the angular speed + * @param {Number} p0 Position at t0 + * @param {Number} p1 Position at t1 + * @param {Number} t0 Initial time in milliseconds + * @param {Number} t1 Ending time in milliseconds + */ + calculateAngularSpeed = ( p0, p1, t0, t1 ) => { + + const s = p1 - p0; + const t = ( t1 - t0 ) / 1000; + if ( t == 0 ) { + + return 0; + + } + + return s / t; + + }; + + /** + * Calculate the distance between two pointers + * @param {PointerEvent} p0 The first pointer + * @param {PointerEvent} p1 The second pointer + * @returns {number} The distance between the two pointers + */ + calculatePointersDistance = ( p0, p1 ) => { + + return Math.sqrt( Math.pow( p1.clientX - p0.clientX, 2 ) + Math.pow( p1.clientY - p0.clientY, 2 ) ); + + }; + + /** + * Calculate the rotation axis as the vector perpendicular between two vectors + * @param {Vector3} vec1 The first vector + * @param {Vector3} vec2 The second vector + * @returns {Vector3} The normalized rotation axis + */ + calculateRotationAxis = ( vec1, vec2 ) => { + + this._rotationMatrix.extractRotation( this._cameraMatrixState ); + this._quat.setFromRotationMatrix( this._rotationMatrix ); + + this._rotationAxis.crossVectors( vec1, vec2 ).applyQuaternion( this._quat ); + return this._rotationAxis.normalize().clone(); + + }; + + /** + * Calculate the trackball radius so that gizmo's diamater will be 2/3 of the minimum side of the camera frustum + * @param {Camera} camera + * @returns {Number} The trackball radius + */ + calculateTbRadius = ( camera ) => { + + const distance = camera.position.distanceTo( this._gizmos.position ); + + if ( camera.type == 'PerspectiveCamera' ) { + + const halfFovV = MathUtils.DEG2RAD * camera.fov * 0.5; //vertical fov/2 in radians + const halfFovH = Math.atan( ( camera.aspect ) * Math.tan( halfFovV ) ); //horizontal fov/2 in radians + return Math.tan( Math.min( halfFovV, halfFovH ) ) * distance * this.radiusFactor; + + } else if ( camera.type == 'OrthographicCamera' ) { + + return Math.min( camera.top, camera.right ) * this.radiusFactor; + + } + + }; + + /** + * Focus operation consist of positioning the point of interest in front of the camera and a slightly zoom in + * @param {Vector3} point The point of interest + * @param {Number} size Scale factor + * @param {Number} amount Amount of operation to be completed (used for focus animations, default is complete full operation) + */ + focus = ( point, size, amount = 1 ) => { + + //move center of camera (along with gizmos) towards point of interest + _offset.copy( point ).sub( this._gizmos.position ).multiplyScalar( amount ); + this._translationMatrix.makeTranslation( _offset.x, _offset.y, _offset.z ); + + _gizmoMatrixStateTemp.copy( this._gizmoMatrixState ); + this._gizmoMatrixState.premultiply( this._translationMatrix ); + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + _cameraMatrixStateTemp.copy( this._cameraMatrixState ); + this._cameraMatrixState.premultiply( this._translationMatrix ); + this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + + //apply zoom + if ( this.enableZoom ) { + + this.applyTransformMatrix( this.scale( size, this._gizmos.position ) ); + + } + + this._gizmoMatrixState.copy( _gizmoMatrixStateTemp ); + this._cameraMatrixState.copy( _cameraMatrixStateTemp ); + + }; + + /** + * Draw a grid and add it to the scene + */ + drawGrid = () => { + + if ( this.scene != null ) { + + const color = 0x888888; + const multiplier = 3; + let size, divisions, maxLength, tick; + + if ( this.camera.isOrthographicCamera ) { + + const width = this.camera.right - this.camera.left; + const height = this.camera.bottom - this.camera.top; + + maxLength = Math.max( width, height ); + tick = maxLength / 20; + + size = maxLength / this.camera.zoom * multiplier; + divisions = size / tick * this.camera.zoom; + + } else if ( this.camera.isPerspectiveCamera ) { + + const distance = this.camera.position.distanceTo( this._gizmos.position ); + const halfFovV = MathUtils.DEG2RAD * this.camera.fov * 0.5; + const halfFovH = Math.atan( ( this.camera.aspect ) * Math.tan( halfFovV ) ); + + maxLength = Math.tan( Math.max( halfFovV, halfFovH ) ) * distance * 2; + tick = maxLength / 20; + + size = maxLength * multiplier; + divisions = size / tick; + + } + + if ( this._grid == null ) { + + this._grid = new GridHelper( size, divisions, color, color ); + this._grid.position.copy( this._gizmos.position ); + this._gridPosition.copy( this._grid.position ); + this._grid.quaternion.copy( this.camera.quaternion ); + this._grid.rotateX( Math.PI * 0.5 ); + + this.scene.add( this._grid ); + + } + + } + + }; + + /** + * Remove all listeners, stop animations and clean scene + */ + dispose = () => { + + if ( this._animationId != - 1 ) { + + window.cancelAnimationFrame( this._animationId ); + + } + + this.domElement.removeEventListener( 'pointerdown', this.onPointerDown ); + this.domElement.removeEventListener( 'pointercancel', this.onPointerCancel ); + this.domElement.removeEventListener( 'wheel', this.onWheel ); + this.domElement.removeEventListener( 'contextmenu', this.onContextMenu ); + + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + window.removeEventListener( 'resize', this.onWindowResize ); + + if ( this.scene !== null ) this.scene.remove( this._gizmos ); + this.disposeGrid(); + + }; + + /** + * remove the grid from the scene + */ + disposeGrid = () => { + + if ( this._grid != null && this.scene != null ) { + + this.scene.remove( this._grid ); + this._grid = null; + + } + + }; + + /** + * Compute the easing out cubic function for ease out effect in animation + * @param {Number} t The absolute progress of the animation in the bound of 0 (beginning of the) and 1 (ending of animation) + * @returns {Number} Result of easing out cubic at time t + */ + easeOutCubic = ( t ) => { + + return 1 - Math.pow( 1 - t, 3 ); + + }; + + /** + * Make rotation gizmos more or less visible + * @param {Boolean} isActive If true, make gizmos more visible + */ + activateGizmos = ( isActive ) => { + + const gizmoX = this._gizmos.children[ 0 ]; + const gizmoY = this._gizmos.children[ 1 ]; + const gizmoZ = this._gizmos.children[ 2 ]; + + if ( isActive ) { + + gizmoX.material.setValues( { opacity: 1 } ); + gizmoY.material.setValues( { opacity: 1 } ); + gizmoZ.material.setValues( { opacity: 1 } ); + + } else { + + gizmoX.material.setValues( { opacity: 0.6 } ); + gizmoY.material.setValues( { opacity: 0.6 } ); + gizmoZ.material.setValues( { opacity: 0.6 } ); + + } + + }; + + /** + * Calculate the cursor position in NDC + * @param {number} x Cursor horizontal coordinate within the canvas + * @param {number} y Cursor vertical coordinate within the canvas + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @returns {Vector2} Cursor normalized position inside the canvas + */ + getCursorNDC = ( cursorX, cursorY, canvas ) => { + + const canvasRect = canvas.getBoundingClientRect(); + this._v2_1.setX( ( ( cursorX - canvasRect.left ) / canvasRect.width ) * 2 - 1 ); + this._v2_1.setY( ( ( canvasRect.bottom - cursorY ) / canvasRect.height ) * 2 - 1 ); + return this._v2_1.clone(); + + }; + + /** + * Calculate the cursor position inside the canvas x/y coordinates with the origin being in the center of the canvas + * @param {Number} x Cursor horizontal coordinate within the canvas + * @param {Number} y Cursor vertical coordinate within the canvas + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @returns {Vector2} Cursor position inside the canvas + */ + getCursorPosition = ( cursorX, cursorY, canvas ) => { + + this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); + this._v2_1.x *= ( this.camera.right - this.camera.left ) * 0.5; + this._v2_1.y *= ( this.camera.top - this.camera.bottom ) * 0.5; + return this._v2_1.clone(); + + }; + + /** + * Set the camera to be controlled + * @param {Camera} camera The virtual camera to be controlled + */ + setCamera = ( camera ) => { + + camera.lookAt( this.target ); + camera.updateMatrix(); + + //setting state + if ( camera.type == 'PerspectiveCamera' ) { + + this._fov0 = camera.fov; + this._fovState = camera.fov; + + } + + this._cameraMatrixState0.copy( camera.matrix ); + this._cameraMatrixState.copy( this._cameraMatrixState0 ); + this._cameraProjectionState.copy( camera.projectionMatrix ); + this._zoom0 = camera.zoom; + this._zoomState = this._zoom0; + + this._initialNear = camera.near; + this._nearPos0 = camera.position.distanceTo( this.target ) - camera.near; + this._nearPos = this._initialNear; + + this._initialFar = camera.far; + this._farPos0 = camera.position.distanceTo( this.target ) - camera.far; + this._farPos = this._initialFar; + + this._up0.copy( camera.up ); + this._upState.copy( camera.up ); + + this.camera = camera; + this.camera.updateProjectionMatrix(); + + //making gizmos + this._tbRadius = this.calculateTbRadius( camera ); + this.makeGizmos( this.target, this._tbRadius ); + + }; + + /** + * Set gizmos visibility + * @param {Boolean} value Value of gizmos visibility + */ + setGizmosVisible( value ) { + + this._gizmos.visible = value; + this.dispatchEvent( _changeEvent ); + + } + + /** + * Set gizmos radius factor and redraws gizmos + * @param {Float} value Value of radius factor + */ + setTbRadius( value ) { + + this.radiusFactor = value; + this._tbRadius = this.calculateTbRadius( this.camera ); + + const curve = new EllipseCurve( 0, 0, this._tbRadius, this._tbRadius ); + const points = curve.getPoints( this._curvePts ); + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + + for ( const gizmo in this._gizmos.children ) { + + this._gizmos.children[ gizmo ].geometry = curveGeometry; + + } + + this.dispatchEvent( _changeEvent ); + + } + + /** + * Creates the rotation gizmos matching trackball center and radius + * @param {Vector3} tbCenter The trackball center + * @param {number} tbRadius The trackball radius + */ + makeGizmos = ( tbCenter, tbRadius ) => { + + const curve = new EllipseCurve( 0, 0, tbRadius, tbRadius ); + const points = curve.getPoints( this._curvePts ); + + //geometry + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + //material + const curveMaterialX = new LineBasicMaterial( { color: 0xff8080, fog: false, transparent: true, opacity: 0.6 } ); + const curveMaterialY = new LineBasicMaterial( { color: 0x80ff80, fog: false, transparent: true, opacity: 0.6 } ); + const curveMaterialZ = new LineBasicMaterial( { color: 0x8080ff, fog: false, transparent: true, opacity: 0.6 } ); + + //line + const gizmoX = new Line( curveGeometry, curveMaterialX ); + const gizmoY = new Line( curveGeometry, curveMaterialY ); + const gizmoZ = new Line( curveGeometry, curveMaterialZ ); + + const rotation = Math.PI * 0.5; + gizmoX.rotation.x = rotation; + gizmoY.rotation.y = rotation; + + + //setting state + this._gizmoMatrixState0.identity().setPosition( tbCenter ); + this._gizmoMatrixState.copy( this._gizmoMatrixState0 ); + + if ( this.camera.zoom != 1 ) { + + //adapt gizmos size to camera zoom + const size = 1 / this.camera.zoom; + this._scaleMatrix.makeScale( size, size, size ); + this._translationMatrix.makeTranslation( - tbCenter.x, - tbCenter.y, - tbCenter.z ); + + this._gizmoMatrixState.premultiply( this._translationMatrix ).premultiply( this._scaleMatrix ); + this._translationMatrix.makeTranslation( tbCenter.x, tbCenter.y, tbCenter.z ); + this._gizmoMatrixState.premultiply( this._translationMatrix ); + + } + + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + this._gizmos.clear(); + + this._gizmos.add( gizmoX ); + this._gizmos.add( gizmoY ); + this._gizmos.add( gizmoZ ); + + }; + + /** + * Perform animation for focus operation + * @param {Number} time Instant in which this function is called as performance.now() + * @param {Vector3} point Point of interest for focus operation + * @param {Matrix4} cameraMatrix Camera matrix + * @param {Matrix4} gizmoMatrix Gizmos matrix + */ + onFocusAnim = ( time, point, cameraMatrix, gizmoMatrix ) => { + + if ( this._timeStart == - 1 ) { + + //animation start + this._timeStart = time; + + } + + if ( this._state == STATE.ANIMATION_FOCUS ) { + + const deltaTime = time - this._timeStart; + const animTime = deltaTime / this.focusAnimationTime; + + this._gizmoMatrixState.copy( gizmoMatrix ); + + if ( animTime >= 1 ) { + + //animation end + + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + this.focus( point, this.scaleFactor ); + + this._timeStart = - 1; + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + + this.dispatchEvent( _changeEvent ); + + } else { + + const amount = this.easeOutCubic( animTime ); + const size = ( ( 1 - amount ) + ( this.scaleFactor * amount ) ); + + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + this.focus( point, size, amount ); + + this.dispatchEvent( _changeEvent ); + const self = this; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.onFocusAnim( t, point, cameraMatrix, gizmoMatrix.clone() ); + + } ); + + } + + } else { + + //interrupt animation + + this._animationId = - 1; + this._timeStart = - 1; + + } + + }; + + /** + * Perform animation for rotation operation + * @param {Number} time Instant in which this function is called as performance.now() + * @param {Vector3} rotationAxis Rotation axis + * @param {number} w0 Initial angular velocity + */ + onRotationAnim = ( time, rotationAxis, w0 ) => { + + if ( this._timeStart == - 1 ) { + + //animation start + this._anglePrev = 0; + this._angleCurrent = 0; + this._timeStart = time; + + } + + if ( this._state == STATE.ANIMATION_ROTATE ) { + + //w = w0 + alpha * t + const deltaTime = ( time - this._timeStart ) / 1000; + const w = w0 + ( ( - this.dampingFactor ) * deltaTime ); + + if ( w > 0 ) { + + //tetha = 0.5 * alpha * t^2 + w0 * t + tetha0 + this._angleCurrent = 0.5 * ( - this.dampingFactor ) * Math.pow( deltaTime, 2 ) + w0 * deltaTime + 0; + this.applyTransformMatrix( this.rotate( rotationAxis, this._angleCurrent ) ); + this.dispatchEvent( _changeEvent ); + const self = this; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.onRotationAnim( t, rotationAxis, w0 ); + + } ); + + } else { + + this._animationId = - 1; + this._timeStart = - 1; + + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + + this.dispatchEvent( _changeEvent ); + + } + + } else { + + //interrupt animation + + this._animationId = - 1; + this._timeStart = - 1; + + if ( this._state != STATE.ROTATE ) { + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + } + + }; + + + /** + * Perform pan operation moving camera between two points + * @param {Vector3} p0 Initial point + * @param {Vector3} p1 Ending point + * @param {Boolean} adjust If movement should be adjusted considering camera distance (Perspective only) + */ + pan = ( p0, p1, adjust = false ) => { + + const movement = p0.clone().sub( p1 ); + + if ( this.camera.isOrthographicCamera ) { + + //adjust movement amount + movement.multiplyScalar( 1 / this.camera.zoom ); + + } else if ( this.camera.isPerspectiveCamera && adjust ) { + + //adjust movement amount + this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ); //camera's initial position + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ); //gizmo's initial position + const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.camera.position.distanceTo( this._gizmos.position ); + movement.multiplyScalar( 1 / distanceFactor ); + + } + + this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.camera.quaternion ); + + this._m4_1.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ); + + this.setTransformationMatrices( this._m4_1, this._m4_1 ); + return _transformation; + + }; + + /** + * Reset trackball + */ + reset = () => { + + this.camera.zoom = this._zoom0; + + if ( this.camera.isPerspectiveCamera ) { + + this.camera.fov = this._fov0; + + } + + this.camera.near = this._nearPos; + this.camera.far = this._farPos; + this._cameraMatrixState.copy( this._cameraMatrixState0 ); + this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + this.camera.up.copy( this._up0 ); + + this.camera.updateMatrix(); + this.camera.updateProjectionMatrix(); + + this._gizmoMatrixState.copy( this._gizmoMatrixState0 ); + this._gizmoMatrixState0.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + this._gizmos.updateMatrix(); + + this._tbRadius = this.calculateTbRadius( this.camera ); + this.makeGizmos( this._gizmos.position, this._tbRadius ); + + this.camera.lookAt( this._gizmos.position ); + + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + + }; + + /** + * Rotate the camera around an axis passing by trackball's center + * @param {Vector3} axis Rotation axis + * @param {number} angle Angle in radians + * @returns {Object} Object with 'camera' field containing transformation matrix resulting from the operation to be applied to the camera + */ + rotate = ( axis, angle ) => { + + const point = this._gizmos.position; //rotation center + this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z ); + this._rotationMatrix.makeRotationAxis( axis, - angle ); + + //rotate camera + this._m4_1.makeTranslation( point.x, point.y, point.z ); + this._m4_1.multiply( this._rotationMatrix ); + this._m4_1.multiply( this._translationMatrix ); + + this.setTransformationMatrices( this._m4_1 ); + + return _transformation; + + }; + + copyState = () => { + + let state; + if ( this.camera.isOrthographicCamera ) { + + state = JSON.stringify( { arcballState: { + + cameraFar: this.camera.far, + cameraMatrix: this.camera.matrix, + cameraNear: this.camera.near, + cameraUp: this.camera.up, + cameraZoom: this.camera.zoom, + gizmoMatrix: this._gizmos.matrix + + } } ); + + } else if ( this.camera.isPerspectiveCamera ) { + + state = JSON.stringify( { arcballState: { + cameraFar: this.camera.far, + cameraFov: this.camera.fov, + cameraMatrix: this.camera.matrix, + cameraNear: this.camera.near, + cameraUp: this.camera.up, + cameraZoom: this.camera.zoom, + gizmoMatrix: this._gizmos.matrix + + } } ); + + } + + navigator.clipboard.writeText( state ); + + }; + + pasteState = () => { + + const self = this; + navigator.clipboard.readText().then( function resolved( value ) { + + self.setStateFromJSON( value ); + + } ); + + }; + + /** + * Save the current state of the control. This can later be recover with .reset + */ + saveState = () => { + + this._cameraMatrixState0.copy( this.camera.matrix ); + this._gizmoMatrixState0.copy( this._gizmos.matrix ); + this._nearPos = this.camera.near; + this._farPos = this.camera.far; + this._zoom0 = this.camera.zoom; + this._up0.copy( this.camera.up ); + + if ( this.camera.isPerspectiveCamera ) { + + this._fov0 = this.camera.fov; + + } + + }; + + /** + * Perform uniform scale operation around a given point + * @param {Number} size Scale factor + * @param {Vector3} point Point around which scale + * @param {Boolean} scaleGizmos If gizmos should be scaled (Perspective only) + * @returns {Object} Object with 'camera' and 'gizmo' fields containing transformation matrices resulting from the operation to be applied to the camera and gizmos + */ + scale = ( size, point, scaleGizmos = true ) => { + + _scalePointTemp.copy( point ); + let sizeInverse = 1 / size; + + if ( this.camera.isOrthographicCamera ) { + + //camera zoom + this.camera.zoom = this._zoomState; + this.camera.zoom *= size; + + //check min and max zoom + if ( this.camera.zoom > this.maxZoom ) { + + this.camera.zoom = this.maxZoom; + sizeInverse = this._zoomState / this.maxZoom; + + } else if ( this.camera.zoom < this.minZoom ) { + + this.camera.zoom = this.minZoom; + sizeInverse = this._zoomState / this.minZoom; + + } + + this.camera.updateProjectionMatrix(); + + this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ); //gizmos position + + //scale gizmos so they appear in the same spot having the same dimension + this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse ); + this._translationMatrix.makeTranslation( - this._v3_1.x, - this._v3_1.y, - this._v3_1.z ); + + this._m4_2.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ).multiply( this._scaleMatrix ); + this._m4_2.multiply( this._translationMatrix ); + + + //move camera and gizmos to obtain pinch effect + _scalePointTemp.sub( this._v3_1 ); + + const amount = _scalePointTemp.clone().multiplyScalar( sizeInverse ); + _scalePointTemp.sub( amount ); + + this._m4_1.makeTranslation( _scalePointTemp.x, _scalePointTemp.y, _scalePointTemp.z ); + this._m4_2.premultiply( this._m4_1 ); + + this.setTransformationMatrices( this._m4_1, this._m4_2 ); + return _transformation; + + } else if ( this.camera.isPerspectiveCamera ) { + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + + //move camera + let distance = this._v3_1.distanceTo( _scalePointTemp ); + let amount = distance - ( distance * sizeInverse ); + + //check min and max distance + const newDistance = distance - amount; + if ( newDistance < this.minDistance ) { + + sizeInverse = this.minDistance / distance; + amount = distance - ( distance * sizeInverse ); + + } else if ( newDistance > this.maxDistance ) { + + sizeInverse = this.maxDistance / distance; + amount = distance - ( distance * sizeInverse ); + + } + + _offset.copy( _scalePointTemp ).sub( this._v3_1 ).normalize().multiplyScalar( amount ); + + this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); + + + if ( scaleGizmos ) { + + //scale gizmos so they appear in the same spot having the same dimension + const pos = this._v3_2; + + distance = pos.distanceTo( _scalePointTemp ); + amount = distance - ( distance * sizeInverse ); + _offset.copy( _scalePointTemp ).sub( this._v3_2 ).normalize().multiplyScalar( amount ); + + this._translationMatrix.makeTranslation( pos.x, pos.y, pos.z ); + this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse ); + + this._m4_2.makeTranslation( _offset.x, _offset.y, _offset.z ).multiply( this._translationMatrix ); + this._m4_2.multiply( this._scaleMatrix ); + + this._translationMatrix.makeTranslation( - pos.x, - pos.y, - pos.z ); + + this._m4_2.multiply( this._translationMatrix ); + this.setTransformationMatrices( this._m4_1, this._m4_2 ); + + + } else { + + this.setTransformationMatrices( this._m4_1 ); + + } + + return _transformation; + + } + + }; + + /** + * Set camera fov + * @param {Number} value fov to be setted + */ + setFov = ( value ) => { + + if ( this.camera.isPerspectiveCamera ) { + + this.camera.fov = MathUtils.clamp( value, this.minFov, this.maxFov ); + this.camera.updateProjectionMatrix(); + + } + + }; + + /** + * Set values in transformation object + * @param {Matrix4} camera Transformation to be applied to the camera + * @param {Matrix4} gizmos Transformation to be applied to gizmos + */ + setTransformationMatrices( camera = null, gizmos = null ) { + + if ( camera != null ) { + + if ( _transformation.camera != null ) { + + _transformation.camera.copy( camera ); + + } else { + + _transformation.camera = camera.clone(); + + } + + } else { + + _transformation.camera = null; + + } + + if ( gizmos != null ) { + + if ( _transformation.gizmos != null ) { + + _transformation.gizmos.copy( gizmos ); + + } else { + + _transformation.gizmos = gizmos.clone(); + + } + + } else { + + _transformation.gizmos = null; + + } + + } + + /** + * Rotate camera around its direction axis passing by a given point by a given angle + * @param {Vector3} point The point where the rotation axis is passing trough + * @param {Number} angle Angle in radians + * @returns The computed transormation matix + */ + zRotate = ( point, angle ) => { + + this._rotationMatrix.makeRotationAxis( this._rotationAxis, angle ); + this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z ); + + this._m4_1.makeTranslation( point.x, point.y, point.z ); + this._m4_1.multiply( this._rotationMatrix ); + this._m4_1.multiply( this._translationMatrix ); + + this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ).sub( point ); //vector from rotation center to gizmos position + this._v3_2.copy( this._v3_1 ).applyAxisAngle( this._rotationAxis, angle ); //apply rotation + this._v3_2.sub( this._v3_1 ); + + this._m4_2.makeTranslation( this._v3_2.x, this._v3_2.y, this._v3_2.z ); + + this.setTransformationMatrices( this._m4_1, this._m4_2 ); + return _transformation; + + }; + + + getRaycaster() { + + return _raycaster; + + } + + + /** + * Unproject the cursor on the 3D object surface + * @param {Vector2} cursor Cursor coordinates in NDC + * @param {Camera} camera Virtual camera + * @returns {Vector3} The point of intersection with the model, if exist, null otherwise + */ + unprojectOnObj = ( cursor, camera ) => { + + const raycaster = this.getRaycaster(); + raycaster.near = camera.near; + raycaster.far = camera.far; + raycaster.setFromCamera( cursor, camera ); + + const intersect = raycaster.intersectObjects( this.scene.children, true ); + + for ( let i = 0; i < intersect.length; i ++ ) { + + if ( intersect[ i ].object.uuid != this._gizmos.uuid && intersect[ i ].face != null ) { + + return intersect[ i ].point.clone(); + + } + + } + + return null; + + }; + + /** + * Unproject the cursor on the trackball surface + * @param {Camera} camera The virtual camera + * @param {Number} cursorX Cursor horizontal coordinate on screen + * @param {Number} cursorY Cursor vertical coordinate on screen + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @param {number} tbRadius The trackball radius + * @returns {Vector3} The unprojected point on the trackball surface + */ + unprojectOnTbSurface = ( camera, cursorX, cursorY, canvas, tbRadius ) => { + + if ( camera.type == 'OrthographicCamera' ) { + + this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) ); + this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 ); + + const x2 = Math.pow( this._v2_1.x, 2 ); + const y2 = Math.pow( this._v2_1.y, 2 ); + const r2 = Math.pow( this._tbRadius, 2 ); + + if ( x2 + y2 <= r2 * 0.5 ) { + + //intersection with sphere + this._v3_1.setZ( Math.sqrt( r2 - ( x2 + y2 ) ) ); + + } else { + + //intersection with hyperboloid + this._v3_1.setZ( ( r2 * 0.5 ) / ( Math.sqrt( x2 + y2 ) ) ); + + } + + return this._v3_1; + + } else if ( camera.type == 'PerspectiveCamera' ) { + + //unproject cursor on the near plane + this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); + + this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 ); + this._v3_1.applyMatrix4( camera.projectionMatrixInverse ); + + const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction + const cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position ); + const radius2 = Math.pow( tbRadius, 2 ); + + // camera + // |\ + // | \ + // | \ + // h | \ + // | \ + // | \ + // _ _ | _ _ _\ _ _ near plane + // l + + const h = this._v3_1.z; + const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) ); + + if ( l == 0 ) { + + //ray aligned with camera + rayDir.set( this._v3_1.x, this._v3_1.y, tbRadius ); + return rayDir; + + } + + const m = h / l; + const q = cameraGizmoDistance; + + /* + * calculate intersection point between unprojected ray and trackball surface + *|y = m * x + q + *|x^2 + y^2 = r^2 + * + * (m^2 + 1) * x^2 + (2 * m * q) * x + q^2 - r^2 = 0 + */ + let a = Math.pow( m, 2 ) + 1; + let b = 2 * m * q; + let c = Math.pow( q, 2 ) - radius2; + let delta = Math.pow( b, 2 ) - ( 4 * a * c ); + + if ( delta >= 0 ) { + + //intersection with sphere + this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) ); + this._v2_1.setY( m * this._v2_1.x + q ); + + const angle = MathUtils.RAD2DEG * this._v2_1.angle(); + + if ( angle >= 45 ) { + + //if angle between intersection point and X' axis is >= 45°, return that point + //otherwise, calculate intersection point with hyperboloid + + const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) ); + rayDir.multiplyScalar( rayLength ); + rayDir.z += cameraGizmoDistance; + return rayDir; + + } + + } + + //intersection with hyperboloid + /* + *|y = m * x + q + *|y = (1 / x) * (r^2 / 2) + * + * m * x^2 + q * x - r^2 / 2 = 0 + */ + + a = m; + b = q; + c = - radius2 * 0.5; + delta = Math.pow( b, 2 ) - ( 4 * a * c ); + this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) ); + this._v2_1.setY( m * this._v2_1.x + q ); + + const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) ); + + rayDir.multiplyScalar( rayLength ); + rayDir.z += cameraGizmoDistance; + return rayDir; + + } + + }; + + + /** + * Unproject the cursor on the plane passing through the center of the trackball orthogonal to the camera + * @param {Camera} camera The virtual camera + * @param {Number} cursorX Cursor horizontal coordinate on screen + * @param {Number} cursorY Cursor vertical coordinate on screen + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @param {Boolean} initialDistance If initial distance between camera and gizmos should be used for calculations instead of current (Perspective only) + * @returns {Vector3} The unprojected point on the trackball plane + */ + unprojectOnTbPlane = ( camera, cursorX, cursorY, canvas, initialDistance = false ) => { + + if ( camera.type == 'OrthographicCamera' ) { + + this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) ); + this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 ); + + return this._v3_1.clone(); + + } else if ( camera.type == 'PerspectiveCamera' ) { + + this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); + + //unproject cursor on the near plane + this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 ); + this._v3_1.applyMatrix4( camera.projectionMatrixInverse ); + + const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction + + // camera + // |\ + // | \ + // | \ + // h | \ + // | \ + // | \ + // _ _ | _ _ _\ _ _ near plane + // l + + const h = this._v3_1.z; + const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) ); + let cameraGizmoDistance; + + if ( initialDistance ) { + + cameraGizmoDistance = this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ).distanceTo( this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ) ); + + } else { + + cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position ); + + } + + /* + * calculate intersection point between unprojected ray and the plane + *|y = mx + q + *|y = 0 + * + * x = -q/m + */ + if ( l == 0 ) { + + //ray aligned with camera + rayDir.set( 0, 0, 0 ); + return rayDir; + + } + + const m = h / l; + const q = cameraGizmoDistance; + const x = - q / m; + + const rayLength = Math.sqrt( Math.pow( q, 2 ) + Math.pow( x, 2 ) ); + rayDir.multiplyScalar( rayLength ); + rayDir.z = 0; + return rayDir; + + } + + }; + + /** + * Update camera and gizmos state + */ + updateMatrixState = () => { + + //update camera and gizmos state + this._cameraMatrixState.copy( this.camera.matrix ); + this._gizmoMatrixState.copy( this._gizmos.matrix ); + + if ( this.camera.isOrthographicCamera ) { + + this._cameraProjectionState.copy( this.camera.projectionMatrix ); + this.camera.updateProjectionMatrix(); + this._zoomState = this.camera.zoom; + + } else if ( this.camera.isPerspectiveCamera ) { + + this._fovState = this.camera.fov; + + } + + }; + + /** + * Update the trackball FSA + * @param {STATE} newState New state of the FSA + * @param {Boolean} updateMatrices If matriices state should be updated + */ + updateTbState = ( newState, updateMatrices ) => { + + this._state = newState; + if ( updateMatrices ) { + + this.updateMatrixState(); + + } + + }; + + update = () => { + + const EPS = 0.000001; + + if ( this.target.equals( this._currentTarget ) === false ) { + + this._gizmos.position.copy( this.target ); //for correct radius calculation + this._tbRadius = this.calculateTbRadius( this.camera ); + this.makeGizmos( this.target, this._tbRadius ); + this._currentTarget.copy( this.target ); + + } + + //check min/max parameters + if ( this.camera.isOrthographicCamera ) { + + //check zoom + if ( this.camera.zoom > this.maxZoom || this.camera.zoom < this.minZoom ) { + + const newZoom = MathUtils.clamp( this.camera.zoom, this.minZoom, this.maxZoom ); + this.applyTransformMatrix( this.scale( newZoom / this.camera.zoom, this._gizmos.position, true ) ); + + } + + } else if ( this.camera.isPerspectiveCamera ) { + + //check distance + const distance = this.camera.position.distanceTo( this._gizmos.position ); + + if ( distance > this.maxDistance + EPS || distance < this.minDistance - EPS ) { + + const newDistance = MathUtils.clamp( distance, this.minDistance, this.maxDistance ); + this.applyTransformMatrix( this.scale( newDistance / distance, this._gizmos.position ) ); + this.updateMatrixState(); + + } + + //check fov + if ( this.camera.fov < this.minFov || this.camera.fov > this.maxFov ) { + + this.camera.fov = MathUtils.clamp( this.camera.fov, this.minFov, this.maxFov ); + this.camera.updateProjectionMatrix(); + + } + + const oldRadius = this._tbRadius; + this._tbRadius = this.calculateTbRadius( this.camera ); + + if ( oldRadius < this._tbRadius - EPS || oldRadius > this._tbRadius + EPS ) { + + const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3; + const newRadius = this._tbRadius / scale; + const curve = new EllipseCurve( 0, 0, newRadius, newRadius ); + const points = curve.getPoints( this._curvePts ); + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + for ( const gizmo in this._gizmos.children ) { + + this._gizmos.children[ gizmo ].geometry = curveGeometry; + + } + + } + + } + + this.camera.lookAt( this._gizmos.position ); + + }; + + setStateFromJSON = ( json ) => { + + const state = JSON.parse( json ); + + if ( state.arcballState != undefined ) { + + this._cameraMatrixState.fromArray( state.arcballState.cameraMatrix.elements ); + this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + + this.camera.up.copy( state.arcballState.cameraUp ); + this.camera.near = state.arcballState.cameraNear; + this.camera.far = state.arcballState.cameraFar; + + this.camera.zoom = state.arcballState.cameraZoom; + + if ( this.camera.isPerspectiveCamera ) { + + this.camera.fov = state.arcballState.cameraFov; + + } + + this._gizmoMatrixState.fromArray( state.arcballState.gizmoMatrix.elements ); + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + this.camera.updateMatrix(); + this.camera.updateProjectionMatrix(); + + this._gizmos.updateMatrix(); + + this._tbRadius = this.calculateTbRadius( this.camera ); + const gizmoTmp = new Matrix4().copy( this._gizmoMatrixState0 ); + this.makeGizmos( this._gizmos.position, this._tbRadius ); + this._gizmoMatrixState0.copy( gizmoTmp ); + + this.camera.lookAt( this._gizmos.position ); + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + + } + + }; + +} + +export { ArcballControls }; diff --git a/jsm/controls/DragControls.js b/jsm/controls/DragControls.js new file mode 100644 index 0000000..4db4813 --- /dev/null +++ b/jsm/controls/DragControls.js @@ -0,0 +1,220 @@ +import { + EventDispatcher, + Matrix4, + Plane, + Raycaster, + Vector2, + Vector3 +} from 'three'; + +const _plane = new Plane(); +const _raycaster = new Raycaster(); + +const _pointer = new Vector2(); +const _offset = new Vector3(); +const _intersection = new Vector3(); +const _worldPosition = new Vector3(); +const _inverseMatrix = new Matrix4(); + +class DragControls extends EventDispatcher { + + constructor( _objects, _camera, _domElement ) { + + super(); + + _domElement.style.touchAction = 'none'; // disable touch scroll + + let _selected = null, _hovered = null; + + const _intersections = []; + + // + + const scope = this; + + function activate() { + + _domElement.addEventListener( 'pointermove', onPointerMove ); + _domElement.addEventListener( 'pointerdown', onPointerDown ); + _domElement.addEventListener( 'pointerup', onPointerCancel ); + _domElement.addEventListener( 'pointerleave', onPointerCancel ); + + } + + function deactivate() { + + _domElement.removeEventListener( 'pointermove', onPointerMove ); + _domElement.removeEventListener( 'pointerdown', onPointerDown ); + _domElement.removeEventListener( 'pointerup', onPointerCancel ); + _domElement.removeEventListener( 'pointerleave', onPointerCancel ); + + _domElement.style.cursor = ''; + + } + + function dispose() { + + deactivate(); + + } + + function getObjects() { + + return _objects; + + } + + function getRaycaster() { + + return _raycaster; + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + updatePointer( event ); + + _raycaster.setFromCamera( _pointer, _camera ); + + if ( _selected ) { + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) ); + + } + + scope.dispatchEvent( { type: 'drag', object: _selected } ); + + return; + + } + + // hover support + + if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) { + + _intersections.length = 0; + + _raycaster.setFromCamera( _pointer, _camera ); + _raycaster.intersectObjects( _objects, true, _intersections ); + + if ( _intersections.length > 0 ) { + + const object = _intersections[ 0 ].object; + + _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) ); + + if ( _hovered !== object && _hovered !== null ) { + + scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + + _domElement.style.cursor = 'auto'; + _hovered = null; + + } + + if ( _hovered !== object ) { + + scope.dispatchEvent( { type: 'hoveron', object: object } ); + + _domElement.style.cursor = 'pointer'; + _hovered = object; + + } + + } else { + + if ( _hovered !== null ) { + + scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + + _domElement.style.cursor = 'auto'; + _hovered = null; + + } + + } + + } + + } + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + updatePointer( event ); + + _intersections.length = 0; + + _raycaster.setFromCamera( _pointer, _camera ); + _raycaster.intersectObjects( _objects, true, _intersections ); + + if ( _intersections.length > 0 ) { + + _selected = ( scope.transformGroup === true ) ? _objects[ 0 ] : _intersections[ 0 ].object; + + _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _inverseMatrix.copy( _selected.parent.matrixWorld ).invert(); + _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); + + } + + _domElement.style.cursor = 'move'; + + scope.dispatchEvent( { type: 'dragstart', object: _selected } ); + + } + + + } + + function onPointerCancel() { + + if ( scope.enabled === false ) return; + + if ( _selected ) { + + scope.dispatchEvent( { type: 'dragend', object: _selected } ); + + _selected = null; + + } + + _domElement.style.cursor = _hovered ? 'pointer' : 'auto'; + + } + + function updatePointer( event ) { + + const rect = _domElement.getBoundingClientRect(); + + _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1; + _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1; + + } + + activate(); + + // API + + this.enabled = true; + this.transformGroup = false; + + this.activate = activate; + this.deactivate = deactivate; + this.dispose = dispose; + this.getObjects = getObjects; + this.getRaycaster = getRaycaster; + + } + +} + +export { DragControls }; diff --git a/jsm/controls/FirstPersonControls.js b/jsm/controls/FirstPersonControls.js new file mode 100644 index 0000000..143e139 --- /dev/null +++ b/jsm/controls/FirstPersonControls.js @@ -0,0 +1,332 @@ +import { + MathUtils, + Spherical, + Vector3 +} from 'three'; + +const _lookDirection = new Vector3(); +const _spherical = new Spherical(); +const _target = new Vector3(); + +class FirstPersonControls { + + constructor( object, domElement ) { + + if ( domElement === undefined ) { + + console.warn( 'THREE.FirstPersonControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.object = object; + this.domElement = domElement; + + // API + + this.enabled = true; + + this.movementSpeed = 1.0; + this.lookSpeed = 0.005; + + this.lookVertical = true; + this.autoForward = false; + + this.activeLook = true; + + this.heightSpeed = false; + this.heightCoef = 1.0; + this.heightMin = 0.0; + this.heightMax = 1.0; + + this.constrainVertical = false; + this.verticalMin = 0; + this.verticalMax = Math.PI; + + this.mouseDragOn = false; + + // internals + + this.autoSpeedFactor = 0.0; + + this.mouseX = 0; + this.mouseY = 0; + + this.moveForward = false; + this.moveBackward = false; + this.moveLeft = false; + this.moveRight = false; + + this.viewHalfX = 0; + this.viewHalfY = 0; + + // private variables + + let lat = 0; + let lon = 0; + + // + + this.handleResize = function () { + + if ( this.domElement === document ) { + + this.viewHalfX = window.innerWidth / 2; + this.viewHalfY = window.innerHeight / 2; + + } else { + + this.viewHalfX = this.domElement.offsetWidth / 2; + this.viewHalfY = this.domElement.offsetHeight / 2; + + } + + }; + + this.onMouseDown = function ( event ) { + + if ( this.domElement !== document ) { + + this.domElement.focus(); + + } + + if ( this.activeLook ) { + + switch ( event.button ) { + + case 0: this.moveForward = true; break; + case 2: this.moveBackward = true; break; + + } + + } + + this.mouseDragOn = true; + + }; + + this.onMouseUp = function ( event ) { + + if ( this.activeLook ) { + + switch ( event.button ) { + + case 0: this.moveForward = false; break; + case 2: this.moveBackward = false; break; + + } + + } + + this.mouseDragOn = false; + + }; + + this.onMouseMove = function ( event ) { + + if ( this.domElement === document ) { + + this.mouseX = event.pageX - this.viewHalfX; + this.mouseY = event.pageY - this.viewHalfY; + + } else { + + this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX; + this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY; + + } + + }; + + this.onKeyDown = function ( event ) { + + switch ( event.code ) { + + case 'ArrowUp': + case 'KeyW': this.moveForward = true; break; + + case 'ArrowLeft': + case 'KeyA': this.moveLeft = true; break; + + case 'ArrowDown': + case 'KeyS': this.moveBackward = true; break; + + case 'ArrowRight': + case 'KeyD': this.moveRight = true; break; + + case 'KeyR': this.moveUp = true; break; + case 'KeyF': this.moveDown = true; break; + + } + + }; + + this.onKeyUp = function ( event ) { + + switch ( event.code ) { + + case 'ArrowUp': + case 'KeyW': this.moveForward = false; break; + + case 'ArrowLeft': + case 'KeyA': this.moveLeft = false; break; + + case 'ArrowDown': + case 'KeyS': this.moveBackward = false; break; + + case 'ArrowRight': + case 'KeyD': this.moveRight = false; break; + + case 'KeyR': this.moveUp = false; break; + case 'KeyF': this.moveDown = false; break; + + } + + }; + + this.lookAt = function ( x, y, z ) { + + if ( x.isVector3 ) { + + _target.copy( x ); + + } else { + + _target.set( x, y, z ); + + } + + this.object.lookAt( _target ); + + setOrientation( this ); + + return this; + + }; + + this.update = function () { + + const targetPosition = new Vector3(); + + return function update( delta ) { + + if ( this.enabled === false ) return; + + if ( this.heightSpeed ) { + + const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax ); + const heightDelta = y - this.heightMin; + + this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef ); + + } else { + + this.autoSpeedFactor = 0.0; + + } + + const actualMoveSpeed = delta * this.movementSpeed; + + if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) ); + if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed ); + + if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed ); + if ( this.moveRight ) this.object.translateX( actualMoveSpeed ); + + if ( this.moveUp ) this.object.translateY( actualMoveSpeed ); + if ( this.moveDown ) this.object.translateY( - actualMoveSpeed ); + + let actualLookSpeed = delta * this.lookSpeed; + + if ( ! this.activeLook ) { + + actualLookSpeed = 0; + + } + + let verticalLookRatio = 1; + + if ( this.constrainVertical ) { + + verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin ); + + } + + lon -= this.mouseX * actualLookSpeed; + if ( this.lookVertical ) lat -= this.mouseY * actualLookSpeed * verticalLookRatio; + + lat = Math.max( - 85, Math.min( 85, lat ) ); + + let phi = MathUtils.degToRad( 90 - lat ); + const theta = MathUtils.degToRad( lon ); + + if ( this.constrainVertical ) { + + phi = MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax ); + + } + + const position = this.object.position; + + targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position ); + + this.object.lookAt( targetPosition ); + + }; + + }(); + + this.dispose = function () { + + this.domElement.removeEventListener( 'contextmenu', contextmenu ); + this.domElement.removeEventListener( 'mousedown', _onMouseDown ); + this.domElement.removeEventListener( 'mousemove', _onMouseMove ); + this.domElement.removeEventListener( 'mouseup', _onMouseUp ); + + window.removeEventListener( 'keydown', _onKeyDown ); + window.removeEventListener( 'keyup', _onKeyUp ); + + }; + + const _onMouseMove = this.onMouseMove.bind( this ); + const _onMouseDown = this.onMouseDown.bind( this ); + const _onMouseUp = this.onMouseUp.bind( this ); + const _onKeyDown = this.onKeyDown.bind( this ); + const _onKeyUp = this.onKeyUp.bind( this ); + + this.domElement.addEventListener( 'contextmenu', contextmenu ); + this.domElement.addEventListener( 'mousemove', _onMouseMove ); + this.domElement.addEventListener( 'mousedown', _onMouseDown ); + this.domElement.addEventListener( 'mouseup', _onMouseUp ); + + window.addEventListener( 'keydown', _onKeyDown ); + window.addEventListener( 'keyup', _onKeyUp ); + + function setOrientation( controls ) { + + const quaternion = controls.object.quaternion; + + _lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion ); + _spherical.setFromVector3( _lookDirection ); + + lat = 90 - MathUtils.radToDeg( _spherical.phi ); + lon = MathUtils.radToDeg( _spherical.theta ); + + } + + this.handleResize(); + + setOrientation( this ); + + } + +} + +function contextmenu( event ) { + + event.preventDefault(); + +} + +export { FirstPersonControls }; diff --git a/jsm/controls/FlyControls.js b/jsm/controls/FlyControls.js new file mode 100644 index 0000000..69a29a2 --- /dev/null +++ b/jsm/controls/FlyControls.js @@ -0,0 +1,292 @@ +import { + EventDispatcher, + Quaternion, + Vector3 +} from 'three'; + +const _changeEvent = { type: 'change' }; + +class FlyControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.FlyControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.object = object; + this.domElement = domElement; + + // API + + this.movementSpeed = 1.0; + this.rollSpeed = 0.005; + + this.dragToLook = false; + this.autoForward = false; + + // disable default target object behavior + + // internals + + const scope = this; + + const EPS = 0.000001; + + const lastQuaternion = new Quaternion(); + const lastPosition = new Vector3(); + + this.tmpQuaternion = new Quaternion(); + + this.mouseStatus = 0; + + this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; + this.moveVector = new Vector3( 0, 0, 0 ); + this.rotationVector = new Vector3( 0, 0, 0 ); + + this.keydown = function ( event ) { + + if ( event.altKey ) { + + return; + + } + + switch ( event.code ) { + + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = .1; break; + + case 'KeyW': this.moveState.forward = 1; break; + case 'KeyS': this.moveState.back = 1; break; + + case 'KeyA': this.moveState.left = 1; break; + case 'KeyD': this.moveState.right = 1; break; + + case 'KeyR': this.moveState.up = 1; break; + case 'KeyF': this.moveState.down = 1; break; + + case 'ArrowUp': this.moveState.pitchUp = 1; break; + case 'ArrowDown': this.moveState.pitchDown = 1; break; + + case 'ArrowLeft': this.moveState.yawLeft = 1; break; + case 'ArrowRight': this.moveState.yawRight = 1; break; + + case 'KeyQ': this.moveState.rollLeft = 1; break; + case 'KeyE': this.moveState.rollRight = 1; break; + + } + + this.updateMovementVector(); + this.updateRotationVector(); + + }; + + this.keyup = function ( event ) { + + switch ( event.code ) { + + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = 1; break; + + case 'KeyW': this.moveState.forward = 0; break; + case 'KeyS': this.moveState.back = 0; break; + + case 'KeyA': this.moveState.left = 0; break; + case 'KeyD': this.moveState.right = 0; break; + + case 'KeyR': this.moveState.up = 0; break; + case 'KeyF': this.moveState.down = 0; break; + + case 'ArrowUp': this.moveState.pitchUp = 0; break; + case 'ArrowDown': this.moveState.pitchDown = 0; break; + + case 'ArrowLeft': this.moveState.yawLeft = 0; break; + case 'ArrowRight': this.moveState.yawRight = 0; break; + + case 'KeyQ': this.moveState.rollLeft = 0; break; + case 'KeyE': this.moveState.rollRight = 0; break; + + } + + this.updateMovementVector(); + this.updateRotationVector(); + + }; + + this.mousedown = function ( event ) { + + if ( this.dragToLook ) { + + this.mouseStatus ++; + + } else { + + switch ( event.button ) { + + case 0: this.moveState.forward = 1; break; + case 2: this.moveState.back = 1; break; + + } + + this.updateMovementVector(); + + } + + }; + + this.mousemove = function ( event ) { + + if ( ! this.dragToLook || this.mouseStatus > 0 ) { + + const container = this.getContainerDimensions(); + const halfWidth = container.size[ 0 ] / 2; + const halfHeight = container.size[ 1 ] / 2; + + this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; + this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; + + this.updateRotationVector(); + + } + + }; + + this.mouseup = function ( event ) { + + if ( this.dragToLook ) { + + this.mouseStatus --; + + this.moveState.yawLeft = this.moveState.pitchDown = 0; + + } else { + + switch ( event.button ) { + + case 0: this.moveState.forward = 0; break; + case 2: this.moveState.back = 0; break; + + } + + this.updateMovementVector(); + + } + + this.updateRotationVector(); + + }; + + this.update = function ( delta ) { + + const moveMult = delta * scope.movementSpeed; + const rotMult = delta * scope.rollSpeed; + + scope.object.translateX( scope.moveVector.x * moveMult ); + scope.object.translateY( scope.moveVector.y * moveMult ); + scope.object.translateZ( scope.moveVector.z * moveMult ); + + scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize(); + scope.object.quaternion.multiply( scope.tmpQuaternion ); + + if ( + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS + ) { + + scope.dispatchEvent( _changeEvent ); + lastQuaternion.copy( scope.object.quaternion ); + lastPosition.copy( scope.object.position ); + + } + + }; + + this.updateMovementVector = function () { + + const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0; + + this.moveVector.x = ( - this.moveState.left + this.moveState.right ); + this.moveVector.y = ( - this.moveState.down + this.moveState.up ); + this.moveVector.z = ( - forward + this.moveState.back ); + + //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] ); + + }; + + this.updateRotationVector = function () { + + this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp ); + this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft ); + this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft ); + + //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] ); + + }; + + this.getContainerDimensions = function () { + + if ( this.domElement != document ) { + + return { + size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ], + offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ] + }; + + } else { + + return { + size: [ window.innerWidth, window.innerHeight ], + offset: [ 0, 0 ] + }; + + } + + }; + + this.dispose = function () { + + this.domElement.removeEventListener( 'contextmenu', contextmenu ); + this.domElement.removeEventListener( 'mousedown', _mousedown ); + this.domElement.removeEventListener( 'mousemove', _mousemove ); + this.domElement.removeEventListener( 'mouseup', _mouseup ); + + window.removeEventListener( 'keydown', _keydown ); + window.removeEventListener( 'keyup', _keyup ); + + }; + + const _mousemove = this.mousemove.bind( this ); + const _mousedown = this.mousedown.bind( this ); + const _mouseup = this.mouseup.bind( this ); + const _keydown = this.keydown.bind( this ); + const _keyup = this.keyup.bind( this ); + + this.domElement.addEventListener( 'contextmenu', contextmenu ); + + this.domElement.addEventListener( 'mousemove', _mousemove ); + this.domElement.addEventListener( 'mousedown', _mousedown ); + this.domElement.addEventListener( 'mouseup', _mouseup ); + + window.addEventListener( 'keydown', _keydown ); + window.addEventListener( 'keyup', _keyup ); + + this.updateMovementVector(); + this.updateRotationVector(); + + } + +} + +function contextmenu( event ) { + + event.preventDefault(); + +} + +export { FlyControls }; diff --git a/jsm/controls/OrbitControls.js b/jsm/controls/OrbitControls.js new file mode 100644 index 0000000..6281fb9 --- /dev/null +++ b/jsm/controls/OrbitControls.js @@ -0,0 +1,1252 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from '../../src/Three.js'; + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +class OrbitControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + + // The four arrow keys + this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; + + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // the target DOM element for key events + this._domElementKeyEvents = null; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.getDistance = function () { + + return this.object.position.distanceTo( this.target ); + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( _changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + const offset = new Vector3(); + + // so camera.up is the orbit axis + const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + const quatInverse = quat.clone().invert(); + + const lastPosition = new Vector3(); + const lastQuaternion = new Quaternion(); + + const twoPI = 2 * Math.PI; + + return function update() { + + const position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + let min = scope.minAzimuthAngle; + let max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + const scope = this; + + const STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + let state = STATE.NONE; + + const EPS = 0.000001; + + // current position in spherical coordinates + const spherical = new Spherical(); + const sphericalDelta = new Spherical(); + + let scale = 1; + const panOffset = new Vector3(); + let zoomChanged = false; + + const rotateStart = new Vector2(); + const rotateEnd = new Vector2(); + const rotateDelta = new Vector2(); + + const panStart = new Vector2(); + const panEnd = new Vector2(); + const panDelta = new Vector2(); + + const dollyStart = new Vector2(); + const dollyEnd = new Vector2(); + const dollyDelta = new Vector2(); + + const pointers = []; + const pointerPositions = {}; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + const panLeft = function () { + + const v = new Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + const panUp = function () { + + const v = new Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + const pan = function () { + + const offset = new Vector3(); + + return function pan( deltaX, deltaY ) { + + const element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + const position = scope.object.position; + offset.copy( position ).sub( scope.target ); + let targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + let needsUpdate = false; + + switch ( event.code ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate() { + + if ( pointers.length === 1 ) { + + rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan() { + + if ( pointers.length === 1 ) { + + panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly() { + + const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; + const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enablePan ) handleTouchStartPan(); + + } + + function handleTouchStartDollyRotate() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enableRotate ) handleTouchStartRotate(); + + } + + function handleTouchMoveRotate( event ) { + + if ( pointers.length == 1 ) { + + rotateEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( pointers.length === 1 ) { + + panEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + const position = getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + if ( pointers.length === 0 ) { + + scope.domElement.setPointerCapture( event.pointerId ); + + scope.domElement.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.addEventListener( 'pointerup', onPointerUp ); + + } + + // + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { + + onMouseDown( event ); + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchMove( event ); + + } else { + + onMouseMove( event ); + + } + + } + + function onPointerUp( event ) { + + removePointer( event ); + + if ( pointers.length === 0 ) { + + scope.domElement.releasePointerCapture( event.pointerId ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + } + + scope.dispatchEvent( _endEvent ); + + state = STATE.NONE; + + } + + function onPointerCancel( event ) { + + removePointer( event ); + + } + + function onMouseDown( event ) { + + let mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; + + event.preventDefault(); + + scope.dispatchEvent( _startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( _endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + trackPointer( event ); + + switch ( pointers.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate(); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan(); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan(); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate(); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onTouchMove( event ) { + + trackPointer( event ); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + function addPointer( event ) { + + pointers.push( event ); + + } + + function removePointer( event ) { + + delete pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < pointers.length; i ++ ) { + + if ( pointers[ i ].pointerId == event.pointerId ) { + + pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; + + return pointerPositions[ pointer.pointerId ]; + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + + // force an update at start + + this.update(); + + } + +} + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +class MapControls extends OrbitControls { + + constructor( object, domElement ) { + + super( object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + + } + +} + +export { OrbitControls, MapControls }; diff --git a/jsm/controls/PointerLockControls.js b/jsm/controls/PointerLockControls.js new file mode 100644 index 0000000..884a69c --- /dev/null +++ b/jsm/controls/PointerLockControls.js @@ -0,0 +1,164 @@ +import { + Euler, + EventDispatcher, + Vector3 +} from 'three'; + +const _euler = new Euler( 0, 0, 0, 'YXZ' ); +const _vector = new Vector3(); + +const _changeEvent = { type: 'change' }; +const _lockEvent = { type: 'lock' }; +const _unlockEvent = { type: 'unlock' }; + +const _PI_2 = Math.PI / 2; + +class PointerLockControls extends EventDispatcher { + + constructor( camera, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' ); + domElement = document.body; + + } + + this.domElement = domElement; + this.isLocked = false; + + // Set to constrain the pitch of the camera + // Range is 0 to Math.PI radians + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + this.pointerSpeed = 1.0; + + const scope = this; + + function onMouseMove( event ) { + + if ( scope.isLocked === false ) return; + + const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; + const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; + + _euler.setFromQuaternion( camera.quaternion ); + + _euler.y -= movementX * 0.002 * scope.pointerSpeed; + _euler.x -= movementY * 0.002 * scope.pointerSpeed; + + _euler.x = Math.max( _PI_2 - scope.maxPolarAngle, Math.min( _PI_2 - scope.minPolarAngle, _euler.x ) ); + + camera.quaternion.setFromEuler( _euler ); + + scope.dispatchEvent( _changeEvent ); + + } + + function onPointerlockChange() { + + if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) { + + scope.dispatchEvent( _lockEvent ); + + scope.isLocked = true; + + } else { + + scope.dispatchEvent( _unlockEvent ); + + scope.isLocked = false; + + } + + } + + function onPointerlockError() { + + console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' ); + + } + + this.connect = function () { + + scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerlockchange', onPointerlockChange ); + scope.domElement.ownerDocument.addEventListener( 'pointerlockerror', onPointerlockError ); + + }; + + this.disconnect = function () { + + scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerlockchange', onPointerlockChange ); + scope.domElement.ownerDocument.removeEventListener( 'pointerlockerror', onPointerlockError ); + + }; + + this.dispose = function () { + + this.disconnect(); + + }; + + this.getObject = function () { // retaining this method for backward compatibility + + return camera; + + }; + + this.getDirection = function () { + + const direction = new Vector3( 0, 0, - 1 ); + + return function ( v ) { + + return v.copy( direction ).applyQuaternion( camera.quaternion ); + + }; + + }(); + + this.moveForward = function ( distance ) { + + // move forward parallel to the xz-plane + // assumes camera.up is y-up + + _vector.setFromMatrixColumn( camera.matrix, 0 ); + + _vector.crossVectors( camera.up, _vector ); + + camera.position.addScaledVector( _vector, distance ); + + }; + + this.moveRight = function ( distance ) { + + _vector.setFromMatrixColumn( camera.matrix, 0 ); + + camera.position.addScaledVector( _vector, distance ); + + }; + + this.lock = function () { + + this.domElement.requestPointerLock(); + + }; + + this.unlock = function () { + + scope.domElement.ownerDocument.exitPointerLock(); + + }; + + this.connect(); + + } + +} + +export { PointerLockControls }; diff --git a/jsm/controls/TrackballControls.js b/jsm/controls/TrackballControls.js new file mode 100644 index 0000000..2710f36 --- /dev/null +++ b/jsm/controls/TrackballControls.js @@ -0,0 +1,805 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Vector2, + Vector3 +} from 'three'; + +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +class TrackballControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + const scope = this; + const STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; + + this.object = object; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + // API + + this.enabled = true; + + this.screen = { left: 0, top: 0, width: 0, height: 0 }; + + this.rotateSpeed = 1.0; + this.zoomSpeed = 1.2; + this.panSpeed = 0.3; + + this.noRotate = false; + this.noZoom = false; + this.noPan = false; + + this.staticMoving = false; + this.dynamicDampingFactor = 0.2; + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ]; + + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // internals + + this.target = new Vector3(); + + const EPS = 0.000001; + + const lastPosition = new Vector3(); + let lastZoom = 1; + + let _state = STATE.NONE, + _keyState = STATE.NONE, + + _touchZoomDistanceStart = 0, + _touchZoomDistanceEnd = 0, + + _lastAngle = 0; + + const _eye = new Vector3(), + + _movePrev = new Vector2(), + _moveCurr = new Vector2(), + + _lastAxis = new Vector3(), + + _zoomStart = new Vector2(), + _zoomEnd = new Vector2(), + + _panStart = new Vector2(), + _panEnd = new Vector2(), + + _pointers = [], + _pointerPositions = {}; + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.up0 = this.object.up.clone(); + this.zoom0 = this.object.zoom; + + // methods + + this.handleResize = function () { + + const box = scope.domElement.getBoundingClientRect(); + // adjustments come from similar code in the jquery offset() function + const d = scope.domElement.ownerDocument.documentElement; + scope.screen.left = box.left + window.pageXOffset - d.clientLeft; + scope.screen.top = box.top + window.pageYOffset - d.clientTop; + scope.screen.width = box.width; + scope.screen.height = box.height; + + }; + + const getMouseOnScreen = ( function () { + + const vector = new Vector2(); + + return function getMouseOnScreen( pageX, pageY ) { + + vector.set( + ( pageX - scope.screen.left ) / scope.screen.width, + ( pageY - scope.screen.top ) / scope.screen.height + ); + + return vector; + + }; + + }() ); + + const getMouseOnCircle = ( function () { + + const vector = new Vector2(); + + return function getMouseOnCircle( pageX, pageY ) { + + vector.set( + ( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ), + ( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional + ); + + return vector; + + }; + + }() ); + + this.rotateCamera = ( function () { + + const axis = new Vector3(), + quaternion = new Quaternion(), + eyeDirection = new Vector3(), + objectUpDirection = new Vector3(), + objectSidewaysDirection = new Vector3(), + moveDirection = new Vector3(); + + return function rotateCamera() { + + moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); + let angle = moveDirection.length(); + + if ( angle ) { + + _eye.copy( scope.object.position ).sub( scope.target ); + + eyeDirection.copy( _eye ).normalize(); + objectUpDirection.copy( scope.object.up ).normalize(); + objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); + + objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); + objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); + + moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); + + axis.crossVectors( moveDirection, _eye ).normalize(); + + angle *= scope.rotateSpeed; + quaternion.setFromAxisAngle( axis, angle ); + + _eye.applyQuaternion( quaternion ); + scope.object.up.applyQuaternion( quaternion ); + + _lastAxis.copy( axis ); + _lastAngle = angle; + + } else if ( ! scope.staticMoving && _lastAngle ) { + + _lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor ); + _eye.copy( scope.object.position ).sub( scope.target ); + quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); + _eye.applyQuaternion( quaternion ); + scope.object.up.applyQuaternion( quaternion ); + + } + + _movePrev.copy( _moveCurr ); + + }; + + }() ); + + + this.zoomCamera = function () { + + let factor; + + if ( _state === STATE.TOUCH_ZOOM_PAN ) { + + factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; + _touchZoomDistanceStart = _touchZoomDistanceEnd; + + if ( scope.object.isPerspectiveCamera ) { + + _eye.multiplyScalar( factor ); + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom /= factor; + scope.object.updateProjectionMatrix(); + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + } else { + + factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed; + + if ( factor !== 1.0 && factor > 0.0 ) { + + if ( scope.object.isPerspectiveCamera ) { + + _eye.multiplyScalar( factor ); + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom /= factor; + scope.object.updateProjectionMatrix(); + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + } + + if ( scope.staticMoving ) { + + _zoomStart.copy( _zoomEnd ); + + } else { + + _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; + + } + + } + + }; + + this.panCamera = ( function () { + + const mouseChange = new Vector2(), + objectUp = new Vector3(), + pan = new Vector3(); + + return function panCamera() { + + mouseChange.copy( _panEnd ).sub( _panStart ); + + if ( mouseChange.lengthSq() ) { + + if ( scope.object.isOrthographicCamera ) { + + const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth; + const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth; + + mouseChange.x *= scale_x; + mouseChange.y *= scale_y; + + } + + mouseChange.multiplyScalar( _eye.length() * scope.panSpeed ); + + pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x ); + pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) ); + + scope.object.position.add( pan ); + scope.target.add( pan ); + + if ( scope.staticMoving ) { + + _panStart.copy( _panEnd ); + + } else { + + _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) ); + + } + + } + + }; + + }() ); + + this.checkDistances = function () { + + if ( ! scope.noZoom || ! scope.noPan ) { + + if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) { + + scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) ); + _zoomStart.copy( _zoomEnd ); + + } + + if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) { + + scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) ); + _zoomStart.copy( _zoomEnd ); + + } + + } + + }; + + this.update = function () { + + _eye.subVectors( scope.object.position, scope.target ); + + if ( ! scope.noRotate ) { + + scope.rotateCamera(); + + } + + if ( ! scope.noZoom ) { + + scope.zoomCamera(); + + } + + if ( ! scope.noPan ) { + + scope.panCamera(); + + } + + scope.object.position.addVectors( scope.target, _eye ); + + if ( scope.object.isPerspectiveCamera ) { + + scope.checkDistances(); + + scope.object.lookAt( scope.target ); + + if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + + } + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.lookAt( scope.target ); + + if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastZoom = scope.object.zoom; + + } + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + }; + + this.reset = function () { + + _state = STATE.NONE; + _keyState = STATE.NONE; + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.up.copy( scope.up0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + + _eye.subVectors( scope.object.position, scope.target ); + + scope.object.lookAt( scope.target ); + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastZoom = scope.object.zoom; + + }; + + // listeners + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + if ( _pointers.length === 0 ) { + + scope.domElement.setPointerCapture( event.pointerId ); + + scope.domElement.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.addEventListener( 'pointerup', onPointerUp ); + + } + + // + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { + + onMouseDown( event ); + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchMove( event ); + + } else { + + onMouseMove( event ); + + } + + } + + function onPointerUp( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchEnd( event ); + + } else { + + onMouseUp(); + + } + + // + + removePointer( event ); + + if ( _pointers.length === 0 ) { + + scope.domElement.releasePointerCapture( event.pointerId ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + } + + + } + + function onPointerCancel( event ) { + + removePointer( event ); + + } + + function keydown( event ) { + + if ( scope.enabled === false ) return; + + window.removeEventListener( 'keydown', keydown ); + + if ( _keyState !== STATE.NONE ) { + + return; + + } else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) { + + _keyState = STATE.ROTATE; + + } else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) { + + _keyState = STATE.ZOOM; + + } else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) { + + _keyState = STATE.PAN; + + } + + } + + function keyup() { + + if ( scope.enabled === false ) return; + + _keyState = STATE.NONE; + + window.addEventListener( 'keydown', keydown ); + + } + + function onMouseDown( event ) { + + if ( _state === STATE.NONE ) { + + switch ( event.button ) { + + case scope.mouseButtons.LEFT: + _state = STATE.ROTATE; + break; + + case scope.mouseButtons.MIDDLE: + _state = STATE.ZOOM; + break; + + case scope.mouseButtons.RIGHT: + _state = STATE.PAN; + break; + + } + + } + + const state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + + if ( state === STATE.ROTATE && ! scope.noRotate ) { + + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + _movePrev.copy( _moveCurr ); + + } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + + _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + _zoomEnd.copy( _zoomStart ); + + } else if ( state === STATE.PAN && ! scope.noPan ) { + + _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + _panEnd.copy( _panStart ); + + } + + scope.dispatchEvent( _startEvent ); + + } + + function onMouseMove( event ) { + + const state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + + if ( state === STATE.ROTATE && ! scope.noRotate ) { + + _movePrev.copy( _moveCurr ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + + } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + + _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + + } else if ( state === STATE.PAN && ! scope.noPan ) { + + _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + + } + + } + + function onMouseUp() { + + _state = STATE.NONE; + + scope.dispatchEvent( _endEvent ); + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false ) return; + + if ( scope.noZoom === true ) return; + + event.preventDefault(); + + switch ( event.deltaMode ) { + + case 2: + // Zoom in pages + _zoomStart.y -= event.deltaY * 0.025; + break; + + case 1: + // Zoom in lines + _zoomStart.y -= event.deltaY * 0.01; + break; + + default: + // undefined, 0, assume pixels + _zoomStart.y -= event.deltaY * 0.00025; + break; + + } + + scope.dispatchEvent( _startEvent ); + scope.dispatchEvent( _endEvent ); + + } + + function onTouchStart( event ) { + + trackPointer( event ); + + switch ( _pointers.length ) { + + case 1: + _state = STATE.TOUCH_ROTATE; + _moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) ); + _movePrev.copy( _moveCurr ); + break; + + default: // 2 or more + _state = STATE.TOUCH_ZOOM_PAN; + const dx = _pointers[ 0 ].pageX - _pointers[ 1 ].pageX; + const dy = _pointers[ 0 ].pageY - _pointers[ 1 ].pageY; + _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); + + const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2; + const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2; + _panStart.copy( getMouseOnScreen( x, y ) ); + _panEnd.copy( _panStart ); + break; + + } + + scope.dispatchEvent( _startEvent ); + + } + + function onTouchMove( event ) { + + trackPointer( event ); + + switch ( _pointers.length ) { + + case 1: + _movePrev.copy( _moveCurr ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + break; + + default: // 2 or more + + const position = getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); + + const x = ( event.pageX + position.x ) / 2; + const y = ( event.pageY + position.y ) / 2; + _panEnd.copy( getMouseOnScreen( x, y ) ); + break; + + } + + } + + function onTouchEnd( event ) { + + switch ( _pointers.length ) { + + case 0: + _state = STATE.NONE; + break; + + case 1: + _state = STATE.TOUCH_ROTATE; + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + _movePrev.copy( _moveCurr ); + break; + + case 2: + _state = STATE.TOUCH_ZOOM_PAN; + _moveCurr.copy( getMouseOnCircle( event.pageX - _movePrev.x, event.pageY - _movePrev.y ) ); + _movePrev.copy( _moveCurr ); + break; + + } + + scope.dispatchEvent( _endEvent ); + + } + + function contextmenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + function addPointer( event ) { + + _pointers.push( event ); + + } + + function removePointer( event ) { + + delete _pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < _pointers.length; i ++ ) { + + if ( _pointers[ i ].pointerId == event.pointerId ) { + + _pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = _pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + _pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === _pointers[ 0 ].pointerId ) ? _pointers[ 1 ] : _pointers[ 0 ]; + + return _pointerPositions[ pointer.pointerId ]; + + } + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', contextmenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + window.removeEventListener( 'keydown', keydown ); + window.removeEventListener( 'keyup', keyup ); + + }; + + this.domElement.addEventListener( 'contextmenu', contextmenu ); + + this.domElement.addEventListener( 'pointerdown', onPointerDown ); + this.domElement.addEventListener( 'pointercancel', onPointerCancel ); + this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + + + window.addEventListener( 'keydown', keydown ); + window.addEventListener( 'keyup', keyup ); + + this.handleResize(); + + // force an update at start + this.update(); + + } + +} + +export { TrackballControls }; diff --git a/jsm/controls/TransformControls.js b/jsm/controls/TransformControls.js new file mode 100644 index 0000000..16fe1c1 --- /dev/null +++ b/jsm/controls/TransformControls.js @@ -0,0 +1,1556 @@ +import { + BoxGeometry, + BufferGeometry, + CylinderGeometry, + DoubleSide, + Euler, + Float32BufferAttribute, + Line, + LineBasicMaterial, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + OctahedronGeometry, + PlaneGeometry, + Quaternion, + Raycaster, + SphereGeometry, + TorusGeometry, + Vector3 +} from 'three'; + +const _raycaster = new Raycaster(); + +const _tempVector = new Vector3(); +const _tempVector2 = new Vector3(); +const _tempQuaternion = new Quaternion(); +const _unit = { + X: new Vector3( 1, 0, 0 ), + Y: new Vector3( 0, 1, 0 ), + Z: new Vector3( 0, 0, 1 ) +}; + +const _changeEvent = { type: 'change' }; +const _mouseDownEvent = { type: 'mouseDown' }; +const _mouseUpEvent = { type: 'mouseUp', mode: null }; +const _objectChangeEvent = { type: 'objectChange' }; + +class TransformControls extends Object3D { + + constructor( camera, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.visible = false; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + const _gizmo = new TransformControlsGizmo(); + this._gizmo = _gizmo; + this.add( _gizmo ); + + const _plane = new TransformControlsPlane(); + this._plane = _plane; + this.add( _plane ); + + const scope = this; + + // Defined getter, setter and store for a property + function defineProperty( propName, defaultValue ) { + + let propValue = defaultValue; + + Object.defineProperty( scope, propName, { + + get: function () { + + return propValue !== undefined ? propValue : defaultValue; + + }, + + set: function ( value ) { + + if ( propValue !== value ) { + + propValue = value; + _plane[ propName ] = value; + _gizmo[ propName ] = value; + + scope.dispatchEvent( { type: propName + '-changed', value: value } ); + scope.dispatchEvent( _changeEvent ); + + } + + } + + } ); + + scope[ propName ] = defaultValue; + _plane[ propName ] = defaultValue; + _gizmo[ propName ] = defaultValue; + + } + + // Define properties with getters/setter + // Setting the defined property will automatically trigger change event + // Defined properties are passed down to gizmo and plane + + defineProperty( 'camera', camera ); + defineProperty( 'object', undefined ); + defineProperty( 'enabled', true ); + defineProperty( 'axis', null ); + defineProperty( 'mode', 'translate' ); + defineProperty( 'translationSnap', null ); + defineProperty( 'rotationSnap', null ); + defineProperty( 'scaleSnap', null ); + defineProperty( 'space', 'world' ); + defineProperty( 'size', 1 ); + defineProperty( 'dragging', false ); + defineProperty( 'showX', true ); + defineProperty( 'showY', true ); + defineProperty( 'showZ', true ); + + // Reusable utility variables + + const worldPosition = new Vector3(); + const worldPositionStart = new Vector3(); + const worldQuaternion = new Quaternion(); + const worldQuaternionStart = new Quaternion(); + const cameraPosition = new Vector3(); + const cameraQuaternion = new Quaternion(); + const pointStart = new Vector3(); + const pointEnd = new Vector3(); + const rotationAxis = new Vector3(); + const rotationAngle = 0; + const eye = new Vector3(); + + // TODO: remove properties unused in plane and gizmo + + defineProperty( 'worldPosition', worldPosition ); + defineProperty( 'worldPositionStart', worldPositionStart ); + defineProperty( 'worldQuaternion', worldQuaternion ); + defineProperty( 'worldQuaternionStart', worldQuaternionStart ); + defineProperty( 'cameraPosition', cameraPosition ); + defineProperty( 'cameraQuaternion', cameraQuaternion ); + defineProperty( 'pointStart', pointStart ); + defineProperty( 'pointEnd', pointEnd ); + defineProperty( 'rotationAxis', rotationAxis ); + defineProperty( 'rotationAngle', rotationAngle ); + defineProperty( 'eye', eye ); + + this._offset = new Vector3(); + this._startNorm = new Vector3(); + this._endNorm = new Vector3(); + this._cameraScale = new Vector3(); + + this._parentPosition = new Vector3(); + this._parentQuaternion = new Quaternion(); + this._parentQuaternionInv = new Quaternion(); + this._parentScale = new Vector3(); + + this._worldScaleStart = new Vector3(); + this._worldQuaternionInv = new Quaternion(); + this._worldScale = new Vector3(); + + this._positionStart = new Vector3(); + this._quaternionStart = new Quaternion(); + this._scaleStart = new Vector3(); + + this._getPointer = getPointer.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerHover = onPointerHover.bind( this ); + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointermove', this._onPointerHover ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + + } + + // updateMatrixWorld updates key transformation variables + updateMatrixWorld() { + + if ( this.object !== undefined ) { + + this.object.updateMatrixWorld(); + + if ( this.object.parent === null ) { + + console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); + + } else { + + this.object.parent.matrixWorld.decompose( this._parentPosition, this._parentQuaternion, this._parentScale ); + + } + + this.object.matrixWorld.decompose( this.worldPosition, this.worldQuaternion, this._worldScale ); + + this._parentQuaternionInv.copy( this._parentQuaternion ).invert(); + this._worldQuaternionInv.copy( this.worldQuaternion ).invert(); + + } + + this.camera.updateMatrixWorld(); + this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale ); + + this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize(); + + super.updateMatrixWorld( this ); + + } + + pointerHover( pointer ) { + + if ( this.object === undefined || this.dragging === true ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster ); + + if ( intersect ) { + + this.axis = intersect.object.name; + + } else { + + this.axis = null; + + } + + } + + pointerDown( pointer ) { + + if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return; + + if ( this.axis !== null ) { + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + + if ( planeIntersect ) { + + this.object.updateMatrixWorld(); + this.object.parent.updateMatrixWorld(); + + this._positionStart.copy( this.object.position ); + this._quaternionStart.copy( this.object.quaternion ); + this._scaleStart.copy( this.object.scale ); + + this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart ); + + this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + } + + this.dragging = true; + _mouseDownEvent.mode = this.mode; + this.dispatchEvent( _mouseDownEvent ); + + } + + } + + pointerMove( pointer ) { + + const axis = this.axis; + const mode = this.mode; + const object = this.object; + let space = this.space; + + if ( mode === 'scale' ) { + + space = 'local'; + + } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) { + + space = 'world'; + + } + + if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + + if ( ! planeIntersect ) return; + + this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + if ( mode === 'translate' ) { + + // Apply translate + + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._worldQuaternionInv ); + + } + + if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0; + if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0; + if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0; + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale ); + + } else { + + this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale ); + + } + + object.position.copy( this._offset ).add( this._positionStart ); + + // Apply translation snap + + if ( this.translationSnap ) { + + if ( space === 'local' ) { + + object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() ); + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + object.position.applyQuaternion( this._quaternionStart ); + + } + + if ( space === 'world' ) { + + if ( object.parent ) { + + object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + if ( object.parent ) { + + object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + } + + } + + } else if ( mode === 'scale' ) { + + if ( axis.search( 'XYZ' ) !== - 1 ) { + + let d = this.pointEnd.length() / this.pointStart.length(); + + if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1; + + _tempVector2.set( d, d, d ); + + } else { + + _tempVector.copy( this.pointStart ); + _tempVector2.copy( this.pointEnd ); + + _tempVector.applyQuaternion( this._worldQuaternionInv ); + _tempVector2.applyQuaternion( this._worldQuaternionInv ); + + _tempVector2.divide( _tempVector ); + + if ( axis.search( 'X' ) === - 1 ) { + + _tempVector2.x = 1; + + } + + if ( axis.search( 'Y' ) === - 1 ) { + + _tempVector2.y = 1; + + } + + if ( axis.search( 'Z' ) === - 1 ) { + + _tempVector2.z = 1; + + } + + } + + // Apply scale + + object.scale.copy( this._scaleStart ).multiply( _tempVector2 ); + + if ( this.scaleSnap ) { + + if ( axis.search( 'X' ) !== - 1 ) { + + object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + } + + } else if ( mode === 'rotate' ) { + + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) ); + + if ( axis === 'E' ) { + + this.rotationAxis.copy( this.eye ); + this.rotationAngle = this.pointEnd.angleTo( this.pointStart ); + + this._startNorm.copy( this.pointStart ).normalize(); + this._endNorm.copy( this.pointEnd ).normalize(); + + this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 ); + + } else if ( axis === 'XYZE' ) { + + this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize(); + this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED; + + } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) { + + this.rotationAxis.copy( _unit[ axis ] ); + + _tempVector.copy( _unit[ axis ] ); + + if ( space === 'local' ) { + + _tempVector.applyQuaternion( this.worldQuaternion ); + + } + + this.rotationAngle = this._offset.dot( _tempVector.cross( this.eye ).normalize() ) * ROTATION_SPEED; + + } + + // Apply rotation snap + + if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap; + + // Apply rotate + if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) { + + object.quaternion.copy( this._quaternionStart ); + object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize(); + + } else { + + this.rotationAxis.applyQuaternion( this._parentQuaternionInv ); + object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ); + object.quaternion.multiply( this._quaternionStart ).normalize(); + + } + + } + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _objectChangeEvent ); + + } + + pointerUp( pointer ) { + + if ( pointer.button !== 0 ) return; + + if ( this.dragging && ( this.axis !== null ) ) { + + _mouseUpEvent.mode = this.mode; + this.dispatchEvent( _mouseUpEvent ); + + } + + this.dragging = false; + this.axis = null; + + } + + dispose() { + + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerHover ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + + this.traverse( function ( child ) { + + if ( child.geometry ) child.geometry.dispose(); + if ( child.material ) child.material.dispose(); + + } ); + + } + + // Set current object + attach( object ) { + + this.object = object; + this.visible = true; + + return this; + + } + + // Detatch from object + detach() { + + this.object = undefined; + this.visible = false; + this.axis = null; + + return this; + + } + + reset() { + + if ( ! this.enabled ) return; + + if ( this.dragging ) { + + this.object.position.copy( this._positionStart ); + this.object.quaternion.copy( this._quaternionStart ); + this.object.scale.copy( this._scaleStart ); + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _objectChangeEvent ); + + this.pointStart.copy( this.pointEnd ); + + } + + } + + getRaycaster() { + + return _raycaster; + + } + + // TODO: deprecate + + getMode() { + + return this.mode; + + } + + setMode( mode ) { + + this.mode = mode; + + } + + setTranslationSnap( translationSnap ) { + + this.translationSnap = translationSnap; + + } + + setRotationSnap( rotationSnap ) { + + this.rotationSnap = rotationSnap; + + } + + setScaleSnap( scaleSnap ) { + + this.scaleSnap = scaleSnap; + + } + + setSize( size ) { + + this.size = size; + + } + + setSpace( space ) { + + this.space = space; + + } + + update() { + + console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' ); + + } + +} + +TransformControls.prototype.isTransformControls = true; + +// mouse / touch event handlers + +function getPointer( event ) { + + if ( this.domElement.ownerDocument.pointerLockElement ) { + + return { + x: 0, + y: 0, + button: event.button + }; + + } else { + + const rect = this.domElement.getBoundingClientRect(); + + return { + x: ( event.clientX - rect.left ) / rect.width * 2 - 1, + y: - ( event.clientY - rect.top ) / rect.height * 2 + 1, + button: event.button + }; + + } + +} + +function onPointerHover( event ) { + + if ( ! this.enabled ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + this.pointerHover( this._getPointer( event ) ); + break; + + } + +} + +function onPointerDown( event ) { + + if ( ! this.enabled ) return; + + if ( ! document.pointerLockElement ) { + + this.domElement.setPointerCapture( event.pointerId ); + + } + + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + + this.pointerHover( this._getPointer( event ) ); + this.pointerDown( this._getPointer( event ) ); + +} + +function onPointerMove( event ) { + + if ( ! this.enabled ) return; + + this.pointerMove( this._getPointer( event ) ); + +} + +function onPointerUp( event ) { + + if ( ! this.enabled ) return; + + this.domElement.releasePointerCapture( event.pointerId ); + + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + + this.pointerUp( this._getPointer( event ) ); + +} + +function intersectObjectWithRay( object, raycaster, includeInvisible ) { + + const allIntersections = raycaster.intersectObject( object, true ); + + for ( let i = 0; i < allIntersections.length; i ++ ) { + + if ( allIntersections[ i ].object.visible || includeInvisible ) { + + return allIntersections[ i ]; + + } + + } + + return false; + +} + +// + +// Reusable utility variables + +const _tempEuler = new Euler(); +const _alignVector = new Vector3( 0, 1, 0 ); +const _zeroVector = new Vector3( 0, 0, 0 ); +const _lookAtMatrix = new Matrix4(); +const _tempQuaternion2 = new Quaternion(); +const _identityQuaternion = new Quaternion(); +const _dirVector = new Vector3(); +const _tempMatrix = new Matrix4(); + +const _unitX = new Vector3( 1, 0, 0 ); +const _unitY = new Vector3( 0, 1, 0 ); +const _unitZ = new Vector3( 0, 0, 1 ); + +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _v3 = new Vector3(); + +class TransformControlsGizmo extends Object3D { + + constructor() { + + super(); + + this.type = 'TransformControlsGizmo'; + + // shared materials + + const gizmoMaterial = new MeshBasicMaterial( { + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true + } ); + + const gizmoLineMaterial = new LineBasicMaterial( { + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true + } ); + + // Make unique material for each axis/color + + const matInvisible = gizmoMaterial.clone(); + matInvisible.opacity = 0.15; + + const matHelper = gizmoLineMaterial.clone(); + matHelper.opacity = 0.5; + + const matRed = gizmoMaterial.clone(); + matRed.color.setHex( 0xff0000 ); + + const matGreen = gizmoMaterial.clone(); + matGreen.color.setHex( 0x00ff00 ); + + const matBlue = gizmoMaterial.clone(); + matBlue.color.setHex( 0x0000ff ); + + const matRedTransparent = gizmoMaterial.clone(); + matRedTransparent.color.setHex( 0xff0000 ); + matRedTransparent.opacity = 0.5; + + const matGreenTransparent = gizmoMaterial.clone(); + matGreenTransparent.color.setHex( 0x00ff00 ); + matGreenTransparent.opacity = 0.5; + + const matBlueTransparent = gizmoMaterial.clone(); + matBlueTransparent.color.setHex( 0x0000ff ); + matBlueTransparent.opacity = 0.5; + + const matWhiteTransparent = gizmoMaterial.clone(); + matWhiteTransparent.opacity = 0.25; + + const matYellowTransparent = gizmoMaterial.clone(); + matYellowTransparent.color.setHex( 0xffff00 ); + matYellowTransparent.opacity = 0.25; + + const matYellow = gizmoMaterial.clone(); + matYellow.color.setHex( 0xffff00 ); + + const matGray = gizmoMaterial.clone(); + matGray.color.setHex( 0x787878 ); + + // reusable geometry + + const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 ); + arrowGeometry.translate( 0, 0.05, 0 ); + + const scaleHandleGeometry = new BoxGeometry( 0.08, 0.08, 0.08 ); + scaleHandleGeometry.translate( 0, 0.04, 0 ); + + const lineGeometry = new BufferGeometry(); + lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); + + const lineGeometry2 = new CylinderGeometry( 0.0075, 0.0075, 0.5, 3 ); + lineGeometry2.translate( 0, 0.25, 0 ); + + function CircleGeometry( radius, arc ) { + + const geometry = new TorusGeometry( radius, 0.0075, 3, 64, arc * Math.PI * 2 ); + geometry.rotateY( Math.PI / 2 ); + geometry.rotateX( Math.PI / 2 ); + return geometry; + + } + + // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position + + function TranslateHelperGeometry() { + + const geometry = new BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) ); + + return geometry; + + } + + // Gizmo definitions - custom hierarchy definitions for setupGizmo() function + + const gizmoTranslate = { + X: [ + [ new Mesh( arrowGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( arrowGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], + [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( arrowGeometry, matGreen ), [ 0, 0.5, 0 ]], + [ new Mesh( arrowGeometry, matGreen ), [ 0, - 0.5, 0 ], [ Math.PI, 0, 0 ]], + [ new Mesh( lineGeometry2, matGreen ) ] + ], + Z: [ + [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]], + [ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ] + }; + + const pickerTranslate = { + X: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] + ], + Z: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new OctahedronGeometry( 0.2, 0 ), matInvisible ) ] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ] + }; + + const helperTranslate = { + START: [ + [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] + ], + END: [ + [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] + ], + DELTA: [ + [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ] + ], + X: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ], + Y: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + ], + Z: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + const gizmoRotate = { + XYZE: [ + [ new Mesh( CircleGeometry( 0.5, 1 ), matGray ), null, [ 0, Math.PI / 2, 0 ]] + ], + X: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matRed ) ] + ], + Y: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matGreen ), null, [ 0, 0, - Math.PI / 2 ]] + ], + Z: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matBlue ), null, [ 0, Math.PI / 2, 0 ]] + ], + E: [ + [ new Mesh( CircleGeometry( 0.75, 1 ), matYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]] + ] + }; + + const helperRotate = { + AXIS: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + const pickerRotate = { + XYZE: [ + [ new Mesh( new SphereGeometry( 0.25, 10, 8 ), matInvisible ) ] + ], + X: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]], + ], + Y: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], + ], + Z: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + ], + E: [ + [ new Mesh( new TorusGeometry( 0.75, 0.1, 2, 24 ), matInvisible ) ] + ] + }; + + const gizmoScale = { + X: [ + [ new Mesh( scaleHandleGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( scaleHandleGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], + ], + Y: [ + [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.5, 0 ]], + [ new Mesh( lineGeometry2, matGreen ) ], + [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, - 0.5, 0 ], [ 0, 0, Math.PI ]], + ], + Z: [ + [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( lineGeometry2, matBlue ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ], + ] + }; + + const pickerScale = { + X: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] + ], + Z: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]], + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]], + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]], + ], + XYZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 0 ]], + ] + }; + + const helperScale = { + X: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ], + Y: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + ], + Z: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + // Creates an Object3D with gizmos described in custom hierarchy definition. + + function setupGizmo( gizmoMap ) { + + const gizmo = new Object3D(); + + for ( const name in gizmoMap ) { + + for ( let i = gizmoMap[ name ].length; i --; ) { + + const object = gizmoMap[ name ][ i ][ 0 ].clone(); + const position = gizmoMap[ name ][ i ][ 1 ]; + const rotation = gizmoMap[ name ][ i ][ 2 ]; + const scale = gizmoMap[ name ][ i ][ 3 ]; + const tag = gizmoMap[ name ][ i ][ 4 ]; + + // name and tag properties are essential for picking and updating logic. + object.name = name; + object.tag = tag; + + if ( position ) { + + object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] ); + + } + + if ( rotation ) { + + object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] ); + + } + + if ( scale ) { + + object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] ); + + } + + object.updateMatrix(); + + const tempGeometry = object.geometry.clone(); + tempGeometry.applyMatrix4( object.matrix ); + object.geometry = tempGeometry; + object.renderOrder = Infinity; + + object.position.set( 0, 0, 0 ); + object.rotation.set( 0, 0, 0 ); + object.scale.set( 1, 1, 1 ); + + gizmo.add( object ); + + } + + } + + return gizmo; + + } + + // Gizmo creation + + this.gizmo = {}; + this.picker = {}; + this.helper = {}; + + this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) ); + this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) ); + this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) ); + this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) ); + this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) ); + this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) ); + this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) ); + this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) ); + this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) ); + + // Pickers should be hidden always + + this.picker[ 'translate' ].visible = false; + this.picker[ 'rotate' ].visible = false; + this.picker[ 'scale' ].visible = false; + + } + + // updateMatrixWorld will update transformations and appearance of individual handles + + updateMatrixWorld( force ) { + + const space = ( this.mode === 'scale' ) ? 'local' : this.space; // scale always oriented to local rotation + + const quaternion = ( space === 'local' ) ? this.worldQuaternion : _identityQuaternion; + + // Show only gizmos for current transform mode + + this.gizmo[ 'translate' ].visible = this.mode === 'translate'; + this.gizmo[ 'rotate' ].visible = this.mode === 'rotate'; + this.gizmo[ 'scale' ].visible = this.mode === 'scale'; + + this.helper[ 'translate' ].visible = this.mode === 'translate'; + this.helper[ 'rotate' ].visible = this.mode === 'rotate'; + this.helper[ 'scale' ].visible = this.mode === 'scale'; + + + let handles = []; + handles = handles.concat( this.picker[ this.mode ].children ); + handles = handles.concat( this.gizmo[ this.mode ].children ); + handles = handles.concat( this.helper[ this.mode ].children ); + + for ( let i = 0; i < handles.length; i ++ ) { + + const handle = handles[ i ]; + + // hide aligned to camera + + handle.visible = true; + handle.rotation.set( 0, 0, 0 ); + handle.position.copy( this.worldPosition ); + + let factor; + + if ( this.camera.isOrthographicCamera ) { + + factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom; + + } else { + + factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 ); + + } + + handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 4 ); + + // TODO: simplify helpers and consider decoupling from gizmo + + if ( handle.tag === 'helper' ) { + + handle.visible = false; + + if ( handle.name === 'AXIS' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = !! this.axis; + + if ( this.axis === 'X' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'Y' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, Math.PI / 2 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'Z' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'XYZE' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); + _alignVector.copy( this.rotationAxis ); + handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) ); + handle.quaternion.multiply( _tempQuaternion ); + handle.visible = this.dragging; + + } + + if ( this.axis === 'E' ) { + + handle.visible = false; + + } + + + } else if ( handle.name === 'START' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = this.dragging; + + } else if ( handle.name === 'END' ) { + + handle.position.copy( this.worldPosition ); + handle.visible = this.dragging; + + } else if ( handle.name === 'DELTA' ) { + + handle.position.copy( this.worldPositionStart ); + handle.quaternion.copy( this.worldQuaternionStart ); + _tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 ); + _tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() ); + handle.scale.copy( _tempVector ); + handle.visible = this.dragging; + + } else { + + handle.quaternion.copy( quaternion ); + + if ( this.dragging ) { + + handle.position.copy( this.worldPositionStart ); + + } else { + + handle.position.copy( this.worldPosition ); + + } + + if ( this.axis ) { + + handle.visible = this.axis.search( handle.name ) !== - 1; + + } + + } + + // If updating helper, skip rest of the loop + continue; + + } + + // Align handles to current local or world rotation + + handle.quaternion.copy( quaternion ); + + if ( this.mode === 'translate' || this.mode === 'scale' ) { + + // Hide translate and scale axis facing the camera + + const AXIS_HIDE_TRESHOLD = 0.99; + const PLANE_HIDE_TRESHOLD = 0.2; + + if ( handle.name === 'X' ) { + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Y' ) { + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Z' ) { + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'XY' ) { + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'YZ' ) { + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'XZ' ) { + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + } else if ( this.mode === 'rotate' ) { + + // Align handles to current local or world rotation + + _tempQuaternion2.copy( quaternion ); + _alignVector.copy( this.eye ).applyQuaternion( _tempQuaternion.copy( quaternion ).invert() ); + + if ( handle.name.search( 'E' ) !== - 1 ) { + + handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) ); + + } + + if ( handle.name === 'X' ) { + + _tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Y' ) { + + _tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Z' ) { + + _tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + } + + // Hide disabled axes + handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX ); + handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY ); + handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ ); + handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) ); + + // highlight selected axis + + handle.material._color = handle.material._color || handle.material.color.clone(); + handle.material._opacity = handle.material._opacity || handle.material.opacity; + + handle.material.color.copy( handle.material._color ); + handle.material.opacity = handle.material._opacity; + + if ( this.enabled && this.axis ) { + + if ( handle.name === this.axis ) { + + handle.material.color.setHex( 0xffff00 ); + handle.material.opacity = 1.0; + + } else if ( this.axis.split( '' ).some( function ( a ) { + + return handle.name === a; + + } ) ) { + + handle.material.color.setHex( 0xffff00 ); + handle.material.opacity = 1.0; + + } + + } + + } + + super.updateMatrixWorld( force ); + + } + +} + +TransformControlsGizmo.prototype.isTransformControlsGizmo = true; + +// + +class TransformControlsPlane extends Mesh { + + constructor() { + + super( + new PlaneGeometry( 100000, 100000, 2, 2 ), + new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } ) + ); + + this.type = 'TransformControlsPlane'; + + } + + updateMatrixWorld( force ) { + + let space = this.space; + + this.position.copy( this.worldPosition ); + + if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation + + _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + + // Align the plane for current transform mode, axis and space. + + _alignVector.copy( _v2 ); + + switch ( this.mode ) { + + case 'translate': + case 'scale': + switch ( this.axis ) { + + case 'X': + _alignVector.copy( this.eye ).cross( _v1 ); + _dirVector.copy( _v1 ).cross( _alignVector ); + break; + case 'Y': + _alignVector.copy( this.eye ).cross( _v2 ); + _dirVector.copy( _v2 ).cross( _alignVector ); + break; + case 'Z': + _alignVector.copy( this.eye ).cross( _v3 ); + _dirVector.copy( _v3 ).cross( _alignVector ); + break; + case 'XY': + _dirVector.copy( _v3 ); + break; + case 'YZ': + _dirVector.copy( _v1 ); + break; + case 'XZ': + _alignVector.copy( _v3 ); + _dirVector.copy( _v2 ); + break; + case 'XYZ': + case 'E': + _dirVector.set( 0, 0, 0 ); + break; + + } + + break; + case 'rotate': + default: + // special case for rotate + _dirVector.set( 0, 0, 0 ); + + } + + if ( _dirVector.length() === 0 ) { + + // If in rotate mode, make the plane parallel to camera + this.quaternion.copy( this.cameraQuaternion ); + + } else { + + _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector ); + + this.quaternion.setFromRotationMatrix( _tempMatrix ); + + } + + super.updateMatrixWorld( force ); + + } + +} + +TransformControlsPlane.prototype.isTransformControlsPlane = true; + +export { TransformControls, TransformControlsGizmo, TransformControlsPlane }; diff --git a/jsm/controls/experimental/CameraControls.js b/jsm/controls/experimental/CameraControls.js new file mode 100644 index 0000000..25375af --- /dev/null +++ b/jsm/controls/experimental/CameraControls.js @@ -0,0 +1,1248 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from 'three'; + +var CameraControls = function ( object, domElement ) { + + if ( domElement === undefined ) console.warn( 'THREE.CameraControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.CameraControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3(); + + // Set to true to enable trackball behavior + this.trackball = false; + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option enables dollying in and out; property named as "zoom" for backwards compatibility + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = false; // if true, pan in screen-space + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + // auto-rotate is not supported for trackball behavior + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // Set to false to disable use of the keys + this.enableKeys = true; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.quaternion0 = this.object.quaternion.clone(); + this.zoom0 = this.object.zoom; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.quaternion0.copy( scope.object.quaternion ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.quaternion.copy( scope.quaternion0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new Vector3(); + + // so camera.up is the orbit axis + var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().invert(); + + var lastPosition = new Vector3(); + var lastQuaternion = new Quaternion(); + + var q = new Quaternion(); + var vec = new Vector3(); + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + if ( scope.trackball ) { + + // rotate around screen-space y-axis + + if ( sphericalDelta.theta ) { + + vec.set( 0, 1, 0 ).applyQuaternion( scope.object.quaternion ); + + const factor = ( scope.enableDamping ) ? scope.dampingFactor : 1; + + q.setFromAxisAngle( vec, sphericalDelta.theta * factor ); + + scope.object.quaternion.premultiply( q ); + offset.applyQuaternion( q ); + + } + + // rotate around screen-space x-axis + + if ( sphericalDelta.phi ) { + + vec.set( 1, 0, 0 ).applyQuaternion( scope.object.quaternion ); + + const factor = ( scope.enableDamping ) ? scope.dampingFactor : 1; + + q.setFromAxisAngle( vec, sphericalDelta.phi * factor ); + + scope.object.quaternion.premultiply( q ); + offset.applyQuaternion( q ); + + } + + offset.multiplyScalar( scale ); + offset.clampLength( scope.minDistance, scope.maxDistance ); + + } else { + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + spherical.setFromVector3( offset ); + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + } + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + position.copy( scope.target ).add( offset ); + + if ( scope.trackball === false ) { + + scope.object.lookAt( scope.target ); + + } + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); + scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new Spherical(); + var sphericalDelta = new Spherical(); + + var scale = 1; + var panOffset = new Vector3(); + var zoomChanged = false; + + var rotateStart = new Vector2(); + var rotateEnd = new Vector2(); + var rotateDelta = new Vector2(); + + var panStart = new Vector2(); + var panEnd = new Vector2(); + var panDelta = new Vector2(); + + var dollyStart = new Vector2(); + var dollyEnd = new Vector2(); + var dollyDelta = new Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyOut( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( /*event*/ ) { + + // no-op + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyIn( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + var needsUpdate = false; + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan( event ) { + + if ( event.touches.length == 1 ) { + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enablePan ) handleTouchStartPan( event ); + + } + + function handleTouchStartDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enableRotate ) handleTouchStartRotate( event ); + + } + + function handleTouchMoveRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( event.touches.length == 1 ) { + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyIn( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( /*event*/ ) { + + // no-op + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + + // Prevent the browser from scrolling. + + event.preventDefault(); + + // Manually set the focus since calling preventDefault above + // prevents the browser from setting it automatically. + + scope.domElement.focus ? scope.domElement.focus() : window.focus(); + + var mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + event.stopPropagation(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.touches.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan( event ); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate( event ); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); + + scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); + + scope.domElement.addEventListener( 'keydown', onKeyDown, false ); + + // make sure element can receive keys. + + if ( scope.domElement.tabIndex === - 1 ) { + + scope.domElement.tabIndex = 0; + + } + + // force an update at start + + this.object.lookAt( scope.target ); + this.update(); + this.saveState(); + +}; + +CameraControls.prototype = Object.create( EventDispatcher.prototype ); +CameraControls.prototype.constructor = CameraControls; + + +// OrbitControls maintains the "up" direction, camera.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +var OrbitControls = function ( object, domElement ) { + + CameraControls.call( this, object, domElement ); + + this.mouseButtons.LEFT = MOUSE.ROTATE; + this.mouseButtons.RIGHT = MOUSE.PAN; + + this.touches.ONE = TOUCH.ROTATE; + this.touches.TWO = TOUCH.DOLLY_PAN; + +}; + +OrbitControls.prototype = Object.create( EventDispatcher.prototype ); +OrbitControls.prototype.constructor = OrbitControls; + + +// MapControls maintains the "up" direction, camera.up (+Y by default) +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or left right + ctrl/meta/shiftKey, or arrow keys / touch: one-finger move + + +var MapControls = function ( object, domElement ) { + + CameraControls.call( this, object, domElement ); + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + +}; + +MapControls.prototype = Object.create( EventDispatcher.prototype ); +MapControls.prototype.constructor = MapControls; + + +// TrackballControls allows the camera to rotate over the polls and does not maintain camera.up +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +var TrackballControls = function ( object, domElement ) { + + CameraControls.call( this, object, domElement ); + + this.trackball = true; + this.screenSpacePanning = true; + this.autoRotate = false; + + this.mouseButtons.LEFT = MOUSE.ROTATE; + this.mouseButtons.RIGHT = MOUSE.PAN; + + this.touches.ONE = TOUCH.ROTATE; + this.touches.TWO = TOUCH.DOLLY_PAN; + +}; + +TrackballControls.prototype = Object.create( EventDispatcher.prototype ); +TrackballControls.prototype.constructor = TrackballControls; + + +export { CameraControls, OrbitControls, MapControls, TrackballControls }; diff --git a/jsm/csm/CSM.js b/jsm/csm/CSM.js new file mode 100644 index 0000000..c96e627 --- /dev/null +++ b/jsm/csm/CSM.js @@ -0,0 +1,377 @@ +import { + Vector2, + Vector3, + DirectionalLight, + MathUtils, + ShaderChunk, + Matrix4, + Box3 +} from 'three'; +import { CSMFrustum } from './CSMFrustum.js'; +import { CSMShader } from './CSMShader.js'; + +const _cameraToLightMatrix = new Matrix4(); +const _lightSpaceFrustum = new CSMFrustum(); +const _center = new Vector3(); +const _bbox = new Box3(); +const _uniformArray = []; +const _logArray = []; + +export class CSM { + + constructor( data ) { + + data = data || {}; + + this.camera = data.camera; + this.parent = data.parent; + this.cascades = data.cascades || 3; + this.maxFar = data.maxFar || 100000; + this.mode = data.mode || 'practical'; + this.shadowMapSize = data.shadowMapSize || 2048; + this.shadowBias = data.shadowBias || 0.000001; + this.lightDirection = data.lightDirection || new Vector3( 1, - 1, 1 ).normalize(); + this.lightIntensity = data.lightIntensity || 1; + this.lightNear = data.lightNear || 1; + this.lightFar = data.lightFar || 2000; + this.lightMargin = data.lightMargin || 200; + this.customSplitsCallback = data.customSplitsCallback; + this.fade = false; + this.mainFrustum = new CSMFrustum(); + this.frustums = []; + this.breaks = []; + + this.lights = []; + this.shaders = new Map(); + + this.createLights(); + this.updateFrustums(); + this.injectInclude(); + + } + + createLights() { + + for ( let i = 0; i < this.cascades; i ++ ) { + + const light = new DirectionalLight( 0xffffff, this.lightIntensity ); + light.castShadow = true; + light.shadow.mapSize.width = this.shadowMapSize; + light.shadow.mapSize.height = this.shadowMapSize; + + light.shadow.camera.near = this.lightNear; + light.shadow.camera.far = this.lightFar; + light.shadow.bias = this.shadowBias; + + this.parent.add( light ); + this.parent.add( light.target ); + this.lights.push( light ); + + } + + } + + initCascades() { + + const camera = this.camera; + camera.updateProjectionMatrix(); + this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar ); + this.mainFrustum.split( this.breaks, this.frustums ); + + } + + updateShadowBounds() { + + const frustums = this.frustums; + for ( let i = 0; i < frustums.length; i ++ ) { + + const light = this.lights[ i ]; + const shadowCam = light.shadow.camera; + const frustum = this.frustums[ i ]; + + // Get the two points that represent that furthest points on the frustum assuming + // that's either the diagonal across the far plane or the diagonal across the whole + // frustum itself. + const nearVerts = frustum.vertices.near; + const farVerts = frustum.vertices.far; + const point1 = farVerts[ 0 ]; + let point2; + if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) { + + point2 = farVerts[ 2 ]; + + } else { + + point2 = nearVerts[ 2 ]; + + } + + let squaredBBWidth = point1.distanceTo( point2 ); + if ( this.fade ) { + + // expand the shadow extents by the fade margin if fade is enabled. + const camera = this.camera; + const far = Math.max( camera.far, this.maxFar ); + const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near ); + const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near ); + + squaredBBWidth += margin; + + } + + shadowCam.left = - squaredBBWidth / 2; + shadowCam.right = squaredBBWidth / 2; + shadowCam.top = squaredBBWidth / 2; + shadowCam.bottom = - squaredBBWidth / 2; + shadowCam.updateProjectionMatrix(); + + } + + } + + getBreaks() { + + const camera = this.camera; + const far = Math.min( camera.far, this.maxFar ); + this.breaks.length = 0; + + switch ( this.mode ) { + + case 'uniform': + uniformSplit( this.cascades, camera.near, far, this.breaks ); + break; + case 'logarithmic': + logarithmicSplit( this.cascades, camera.near, far, this.breaks ); + break; + case 'practical': + practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks ); + break; + case 'custom': + if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' ); + this.customSplitsCallback( this.cascades, camera.near, far, this.breaks ); + break; + + } + + function uniformSplit( amount, near, far, target ) { + + for ( let i = 1; i < amount; i ++ ) { + + target.push( ( near + ( far - near ) * i / amount ) / far ); + + } + + target.push( 1 ); + + } + + function logarithmicSplit( amount, near, far, target ) { + + for ( let i = 1; i < amount; i ++ ) { + + target.push( ( near * ( far / near ) ** ( i / amount ) ) / far ); + + } + + target.push( 1 ); + + } + + function practicalSplit( amount, near, far, lambda, target ) { + + _uniformArray.length = 0; + _logArray.length = 0; + logarithmicSplit( amount, near, far, _logArray ); + uniformSplit( amount, near, far, _uniformArray ); + + for ( let i = 1; i < amount; i ++ ) { + + target.push( MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) ); + + } + + target.push( 1 ); + + } + + } + + update() { + + const camera = this.camera; + const frustums = this.frustums; + for ( let i = 0; i < frustums.length; i ++ ) { + + const light = this.lights[ i ]; + const shadowCam = light.shadow.camera; + const texelWidth = ( shadowCam.right - shadowCam.left ) / this.shadowMapSize; + const texelHeight = ( shadowCam.top - shadowCam.bottom ) / this.shadowMapSize; + light.shadow.camera.updateMatrixWorld( true ); + _cameraToLightMatrix.multiplyMatrices( light.shadow.camera.matrixWorldInverse, camera.matrixWorld ); + frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum ); + + const nearVerts = _lightSpaceFrustum.vertices.near; + const farVerts = _lightSpaceFrustum.vertices.far; + _bbox.makeEmpty(); + for ( let j = 0; j < 4; j ++ ) { + + _bbox.expandByPoint( nearVerts[ j ] ); + _bbox.expandByPoint( farVerts[ j ] ); + + } + + _bbox.getCenter( _center ); + _center.z = _bbox.max.z + this.lightMargin; + _center.x = Math.floor( _center.x / texelWidth ) * texelWidth; + _center.y = Math.floor( _center.y / texelHeight ) * texelHeight; + _center.applyMatrix4( light.shadow.camera.matrixWorld ); + + light.position.copy( _center ); + light.target.position.copy( _center ); + + light.target.position.x += this.lightDirection.x; + light.target.position.y += this.lightDirection.y; + light.target.position.z += this.lightDirection.z; + + } + + } + + injectInclude() { + + ShaderChunk.lights_fragment_begin = CSMShader.lights_fragment_begin; + ShaderChunk.lights_pars_begin = CSMShader.lights_pars_begin; + + } + + setupMaterial( material ) { + + material.defines = material.defines || {}; + material.defines.USE_CSM = 1; + material.defines.CSM_CASCADES = this.cascades; + + if ( this.fade ) { + + material.defines.CSM_FADE = ''; + + } + + const breaksVec2 = []; + const scope = this; + const shaders = this.shaders; + + material.onBeforeCompile = function ( shader ) { + + const far = Math.min( scope.camera.far, scope.maxFar ); + scope.getExtendedBreaks( breaksVec2 ); + + shader.uniforms.CSM_cascades = { value: breaksVec2 }; + shader.uniforms.cameraNear = { value: scope.camera.near }; + shader.uniforms.shadowFar = { value: far }; + + shaders.set( material, shader ); + + }; + + shaders.set( material, null ); + + } + + updateUniforms() { + + const far = Math.min( this.camera.far, this.maxFar ); + const shaders = this.shaders; + + shaders.forEach( function ( shader, material ) { + + if ( shader !== null ) { + + const uniforms = shader.uniforms; + this.getExtendedBreaks( uniforms.CSM_cascades.value ); + uniforms.cameraNear.value = this.camera.near; + uniforms.shadowFar.value = far; + + } + + if ( ! this.fade && 'CSM_FADE' in material.defines ) { + + delete material.defines.CSM_FADE; + material.needsUpdate = true; + + } else if ( this.fade && ! ( 'CSM_FADE' in material.defines ) ) { + + material.defines.CSM_FADE = ''; + material.needsUpdate = true; + + } + + }, this ); + + } + + getExtendedBreaks( target ) { + + while ( target.length < this.breaks.length ) { + + target.push( new Vector2() ); + + } + + target.length = this.breaks.length; + + for ( let i = 0; i < this.cascades; i ++ ) { + + const amount = this.breaks[ i ]; + const prev = this.breaks[ i - 1 ] || 0; + target[ i ].x = prev; + target[ i ].y = amount; + + } + + } + + updateFrustums() { + + this.getBreaks(); + this.initCascades(); + this.updateShadowBounds(); + this.updateUniforms(); + + } + + remove() { + + for ( let i = 0; i < this.lights.length; i ++ ) { + + this.parent.remove( this.lights[ i ] ); + + } + + } + + dispose() { + + const shaders = this.shaders; + shaders.forEach( function ( shader, material ) { + + delete material.onBeforeCompile; + delete material.defines.USE_CSM; + delete material.defines.CSM_CASCADES; + delete material.defines.CSM_FADE; + + if ( shader !== null ) { + + delete shader.uniforms.CSM_cascades; + delete shader.uniforms.cameraNear; + delete shader.uniforms.shadowFar; + + } + + material.needsUpdate = true; + + } ); + shaders.clear(); + + } + +} diff --git a/jsm/csm/CSMFrustum.js b/jsm/csm/CSMFrustum.js new file mode 100644 index 0000000..2d968be --- /dev/null +++ b/jsm/csm/CSMFrustum.js @@ -0,0 +1,152 @@ +import { Vector3, Matrix4 } from 'three'; + +const inverseProjectionMatrix = new Matrix4(); + +class CSMFrustum { + + constructor( data ) { + + data = data || {}; + + this.vertices = { + near: [ + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3() + ], + far: [ + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3() + ] + }; + + if ( data.projectionMatrix !== undefined ) { + + this.setFromProjectionMatrix( data.projectionMatrix, data.maxFar || 10000 ); + + } + + } + + setFromProjectionMatrix( projectionMatrix, maxFar ) { + + const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0; + + inverseProjectionMatrix.copy( projectionMatrix ).invert(); + + // 3 --- 0 vertices.near/far order + // | | + // 2 --- 1 + // clip space spans from [-1, 1] + + this.vertices.near[ 0 ].set( 1, 1, - 1 ); + this.vertices.near[ 1 ].set( 1, - 1, - 1 ); + this.vertices.near[ 2 ].set( - 1, - 1, - 1 ); + this.vertices.near[ 3 ].set( - 1, 1, - 1 ); + this.vertices.near.forEach( function ( v ) { + + v.applyMatrix4( inverseProjectionMatrix ); + + } ); + + this.vertices.far[ 0 ].set( 1, 1, 1 ); + this.vertices.far[ 1 ].set( 1, - 1, 1 ); + this.vertices.far[ 2 ].set( - 1, - 1, 1 ); + this.vertices.far[ 3 ].set( - 1, 1, 1 ); + this.vertices.far.forEach( function ( v ) { + + v.applyMatrix4( inverseProjectionMatrix ); + + const absZ = Math.abs( v.z ); + if ( isOrthographic ) { + + v.z *= Math.min( maxFar / absZ, 1.0 ); + + } else { + + v.multiplyScalar( Math.min( maxFar / absZ, 1.0 ) ); + + } + + } ); + + return this.vertices; + + } + + split( breaks, target ) { + + while ( breaks.length > target.length ) { + + target.push( new CSMFrustum() ); + + } + + target.length = breaks.length; + + for ( let i = 0; i < breaks.length; i ++ ) { + + const cascade = target[ i ]; + + if ( i === 0 ) { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.near[ j ].copy( this.vertices.near[ j ] ); + + } + + } else { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.near[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i - 1 ] ); + + } + + } + + if ( i === breaks.length - 1 ) { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.far[ j ].copy( this.vertices.far[ j ] ); + + } + + } else { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.far[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i ] ); + + } + + } + + } + + } + + toSpace( cameraMatrix, target ) { + + for ( let i = 0; i < 4; i ++ ) { + + target.vertices.near[ i ] + .copy( this.vertices.near[ i ] ) + .applyMatrix4( cameraMatrix ); + + target.vertices.far[ i ] + .copy( this.vertices.far[ i ] ) + .applyMatrix4( cameraMatrix ); + + } + + } + +} + +export { CSMFrustum }; diff --git a/jsm/csm/CSMHelper.js b/jsm/csm/CSMHelper.js new file mode 100644 index 0000000..7ea0a81 --- /dev/null +++ b/jsm/csm/CSMHelper.js @@ -0,0 +1,163 @@ +import { + Group, + Mesh, + LineSegments, + BufferGeometry, + LineBasicMaterial, + Box3Helper, + Box3, + PlaneGeometry, + MeshBasicMaterial, + BufferAttribute, + DoubleSide +} from 'three'; + +class CSMHelper extends Group { + + constructor( csm ) { + + super(); + this.csm = csm; + this.displayFrustum = true; + this.displayPlanes = true; + this.displayShadowBounds = true; + + const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + const positions = new Float32Array( 24 ); + const frustumGeometry = new BufferGeometry(); + frustumGeometry.setIndex( new BufferAttribute( indices, 1 ) ); + frustumGeometry.setAttribute( 'position', new BufferAttribute( positions, 3, false ) ); + const frustumLines = new LineSegments( frustumGeometry, new LineBasicMaterial() ); + this.add( frustumLines ); + + this.frustumLines = frustumLines; + this.cascadeLines = []; + this.cascadePlanes = []; + this.shadowLines = []; + + } + + updateVisibility() { + + const displayFrustum = this.displayFrustum; + const displayPlanes = this.displayPlanes; + const displayShadowBounds = this.displayShadowBounds; + + const frustumLines = this.frustumLines; + const cascadeLines = this.cascadeLines; + const cascadePlanes = this.cascadePlanes; + const shadowLines = this.shadowLines; + for ( let i = 0, l = cascadeLines.length; i < l; i ++ ) { + + const cascadeLine = cascadeLines[ i ]; + const cascadePlane = cascadePlanes[ i ]; + const shadowLineGroup = shadowLines[ i ]; + + cascadeLine.visible = displayFrustum; + cascadePlane.visible = displayFrustum && displayPlanes; + shadowLineGroup.visible = displayShadowBounds; + + } + + frustumLines.visible = displayFrustum; + + } + + update() { + + const csm = this.csm; + const camera = csm.camera; + const cascades = csm.cascades; + const mainFrustum = csm.mainFrustum; + const frustums = csm.frustums; + const lights = csm.lights; + + const frustumLines = this.frustumLines; + const frustumLinePositions = frustumLines.geometry.getAttribute( 'position' ); + const cascadeLines = this.cascadeLines; + const cascadePlanes = this.cascadePlanes; + const shadowLines = this.shadowLines; + + this.position.copy( camera.position ); + this.quaternion.copy( camera.quaternion ); + this.scale.copy( camera.scale ); + this.updateMatrixWorld( true ); + + while ( cascadeLines.length > cascades ) { + + this.remove( cascadeLines.pop() ); + this.remove( cascadePlanes.pop() ); + this.remove( shadowLines.pop() ); + + } + + while ( cascadeLines.length < cascades ) { + + const cascadeLine = new Box3Helper( new Box3(), 0xffffff ); + const planeMat = new MeshBasicMaterial( { transparent: true, opacity: 0.1, depthWrite: false, side: DoubleSide } ); + const cascadePlane = new Mesh( new PlaneGeometry(), planeMat ); + const shadowLineGroup = new Group(); + const shadowLine = new Box3Helper( new Box3(), 0xffff00 ); + shadowLineGroup.add( shadowLine ); + + this.add( cascadeLine ); + this.add( cascadePlane ); + this.add( shadowLineGroup ); + + cascadeLines.push( cascadeLine ); + cascadePlanes.push( cascadePlane ); + shadowLines.push( shadowLineGroup ); + + } + + for ( let i = 0; i < cascades; i ++ ) { + + const frustum = frustums[ i ]; + const light = lights[ i ]; + const shadowCam = light.shadow.camera; + const farVerts = frustum.vertices.far; + + const cascadeLine = cascadeLines[ i ]; + const cascadePlane = cascadePlanes[ i ]; + const shadowLineGroup = shadowLines[ i ]; + const shadowLine = shadowLineGroup.children[ 0 ]; + + cascadeLine.box.min.copy( farVerts[ 2 ] ); + cascadeLine.box.max.copy( farVerts[ 0 ] ); + cascadeLine.box.max.z += 1e-4; + + cascadePlane.position.addVectors( farVerts[ 0 ], farVerts[ 2 ] ); + cascadePlane.position.multiplyScalar( 0.5 ); + cascadePlane.scale.subVectors( farVerts[ 0 ], farVerts[ 2 ] ); + cascadePlane.scale.z = 1e-4; + + this.remove( shadowLineGroup ); + shadowLineGroup.position.copy( shadowCam.position ); + shadowLineGroup.quaternion.copy( shadowCam.quaternion ); + shadowLineGroup.scale.copy( shadowCam.scale ); + shadowLineGroup.updateMatrixWorld( true ); + this.attach( shadowLineGroup ); + + shadowLine.box.min.set( shadowCam.bottom, shadowCam.left, - shadowCam.far ); + shadowLine.box.max.set( shadowCam.top, shadowCam.right, - shadowCam.near ); + + } + + const nearVerts = mainFrustum.vertices.near; + const farVerts = mainFrustum.vertices.far; + frustumLinePositions.setXYZ( 0, farVerts[ 0 ].x, farVerts[ 0 ].y, farVerts[ 0 ].z ); + frustumLinePositions.setXYZ( 1, farVerts[ 3 ].x, farVerts[ 3 ].y, farVerts[ 3 ].z ); + frustumLinePositions.setXYZ( 2, farVerts[ 2 ].x, farVerts[ 2 ].y, farVerts[ 2 ].z ); + frustumLinePositions.setXYZ( 3, farVerts[ 1 ].x, farVerts[ 1 ].y, farVerts[ 1 ].z ); + + frustumLinePositions.setXYZ( 4, nearVerts[ 0 ].x, nearVerts[ 0 ].y, nearVerts[ 0 ].z ); + frustumLinePositions.setXYZ( 5, nearVerts[ 3 ].x, nearVerts[ 3 ].y, nearVerts[ 3 ].z ); + frustumLinePositions.setXYZ( 6, nearVerts[ 2 ].x, nearVerts[ 2 ].y, nearVerts[ 2 ].z ); + frustumLinePositions.setXYZ( 7, nearVerts[ 1 ].x, nearVerts[ 1 ].y, nearVerts[ 1 ].z ); + frustumLinePositions.needsUpdate = true; + + } + +} + +export { CSMHelper }; diff --git a/jsm/csm/CSMShader.js b/jsm/csm/CSMShader.js new file mode 100644 index 0000000..316461b --- /dev/null +++ b/jsm/csm/CSMShader.js @@ -0,0 +1,251 @@ +import { ShaderChunk } from 'three'; + +const CSMShader = { + lights_fragment_begin: /* glsl */` +GeometricContext geometry; + +geometry.position = - vViewPosition; +geometry.normal = normal; +geometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition ); + +#ifdef CLEARCOAT + + geometry.clearcoatNormal = clearcoatNormal; + +#endif + +IncidentLight directLight; + +#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct ) + + PointLight pointLight; + #if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0 + PointLightShadow pointLightShadow; + #endif + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) { + + pointLight = pointLights[ i ]; + + getPointLightInfo( pointLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS ) + pointLightShadow = pointLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0; + #endif + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct ) + + SpotLight spotLight; + #if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0 + SpotLightShadow spotLightShadow; + #endif + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) { + + spotLight = spotLights[ i ]; + + getSpotLightInfo( spotLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) + spotLightShadow = spotLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0; + #endif + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if ( NUM_DIR_LIGHTS > 0) && defined( RE_Direct ) && defined( USE_CSM ) && defined( CSM_CASCADES ) + + DirectionalLight directionalLight; + float linearDepth = (vViewPosition.z) / (shadowFar - cameraNear); + #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0 + DirectionalLightShadow directionalLightShadow; + #endif + + #if defined( USE_SHADOWMAP ) && defined( CSM_FADE ) + vec2 cascade; + float cascadeCenter; + float closestEdge; + float margin; + float csmx; + float csmy; + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + // NOTE: Depth gets larger away from the camera. + // cascade.x is closer, cascade.y is further + cascade = CSM_cascades[ i ]; + cascadeCenter = ( cascade.x + cascade.y ) / 2.0; + closestEdge = linearDepth < cascadeCenter ? cascade.x : cascade.y; + margin = 0.25 * pow( closestEdge, 2.0 ); + csmx = cascade.x - margin / 2.0; + csmy = cascade.y + margin / 2.0; + if( linearDepth >= csmx && ( linearDepth < csmy || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 ) ) { + + float dist = min( linearDepth - csmx, csmy - linearDepth ); + float ratio = clamp( dist / margin, 0.0, 1.0 ); + + vec3 prevColor = directLight.color; + directionalLightShadow = directionalLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + + bool shouldFadeLastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth > cascadeCenter; + directLight.color = mix( prevColor, directLight.color, shouldFadeLastCascade ? ratio : 1.0 ); + + ReflectedLight prevLight = reflectedLight; + RE_Direct( directLight, geometry, material, reflectedLight ); + + bool shouldBlend = UNROLLED_LOOP_INDEX != CSM_CASCADES - 1 || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth < cascadeCenter; + float blendRatio = shouldBlend ? ratio : 1.0; + + reflectedLight.directDiffuse = mix( prevLight.directDiffuse, reflectedLight.directDiffuse, blendRatio ); + reflectedLight.directSpecular = mix( prevLight.directSpecular, reflectedLight.directSpecular, blendRatio ); + reflectedLight.indirectDiffuse = mix( prevLight.indirectDiffuse, reflectedLight.indirectDiffuse, blendRatio ); + reflectedLight.indirectSpecular = mix( prevLight.indirectSpecular, reflectedLight.indirectSpecular, blendRatio ); + + } + #endif + + } + #pragma unroll_loop_end + #else + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + + directionalLightShadow = directionalLightShadows[ i ]; + if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + + if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometry, material, reflectedLight ); + + #endif + + } + #pragma unroll_loop_end + + #endif + + #if ( NUM_DIR_LIGHTS > NUM_DIR_LIGHT_SHADOWS) + // compute the lights not casting shadows (if any) + + #pragma unroll_loop_start + for ( int i = NUM_DIR_LIGHT_SHADOWS; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + + #endif + +#endif + + +#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && !defined( USE_CSM ) && !defined( CSM_CASCADES ) + + DirectionalLight directionalLight; + #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0 + DirectionalLightShadow directionalLightShadow; + #endif + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + directionalLightShadow = directionalLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + #endif + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea ) + + RectAreaLight rectAreaLight; + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) { + + rectAreaLight = rectAreaLights[ i ]; + RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if defined( RE_IndirectDiffuse ) + + vec3 iblIrradiance = vec3( 0.0 ); + + vec3 irradiance = getAmbientLightIrradiance( ambientLightColor ); + + irradiance += getLightProbeIrradiance( lightProbe, geometry.normal ); + + #if ( NUM_HEMI_LIGHTS > 0 ) + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) { + + irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal ); + + } + #pragma unroll_loop_end + + #endif + +#endif + +#if defined( RE_IndirectSpecular ) + + vec3 radiance = vec3( 0.0 ); + vec3 clearcoatRadiance = vec3( 0.0 ); + +#endif +`, + lights_pars_begin: /* glsl */` +#if defined( USE_CSM ) && defined( CSM_CASCADES ) +uniform vec2 CSM_cascades[CSM_CASCADES]; +uniform float cameraNear; +uniform float shadowFar; +#endif + ` + ShaderChunk.lights_pars_begin +}; + +export { CSMShader }; diff --git a/jsm/curves/CurveExtras.js b/jsm/curves/CurveExtras.js new file mode 100644 index 0000000..51efb84 --- /dev/null +++ b/jsm/curves/CurveExtras.js @@ -0,0 +1,422 @@ +import { + Curve, + Vector3 +} from 'three'; + +/** + * A bunch of parametric curves + * + * Formulas collected from various sources + * http://mathworld.wolfram.com/HeartCurve.html + * http://en.wikipedia.org/wiki/Viviani%27s_curve + * http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf + * https://prideout.net/blog/old/blog/index.html@p=44.html + */ + +// GrannyKnot + +class GrannyKnot extends Curve { + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = 2 * Math.PI * t; + + const x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t ); + const y = - 0.1 * Math.cos( 2 * t ) - 0.27 * Math.sin( 2 * t ) + 0.38 * Math.cos( 4 * t ) + 0.46 * Math.sin( 4 * t ); + const z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t ); + + return point.set( x, y, z ).multiplyScalar( 20 ); + + } + +} + +// HeartCurve + +class HeartCurve extends Curve { + + constructor( scale = 5 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= 2 * Math.PI; + + const x = 16 * Math.pow( Math.sin( t ), 3 ); + const y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t ); + const z = 0; + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// Viviani's Curve + +class VivianiCurve extends Curve { + + constructor( scale = 70 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = t * 4 * Math.PI; // normalized to 0..1 + const a = this.scale / 2; + + const x = a * ( 1 + Math.cos( t ) ); + const y = a * Math.sin( t ); + const z = 2 * a * Math.sin( t / 2 ); + + return point.set( x, y, z ); + + } + +} + +// KnotCurve + +class KnotCurve extends Curve { + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= 2 * Math.PI; + + const R = 10; + const s = 50; + + const x = s * Math.sin( t ); + const y = Math.cos( t ) * ( R + s * Math.cos( t ) ); + const z = Math.sin( t ) * ( R + s * Math.cos( t ) ); + + return point.set( x, y, z ); + + } + +} + + +// HelixCurve + +class HelixCurve extends Curve { + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const a = 30; // radius + const b = 150; // height + + const t2 = 2 * Math.PI * t * b / 30; + + const x = Math.cos( t2 ) * a; + const y = Math.sin( t2 ) * a; + const z = b * t; + + return point.set( x, y, z ); + + } + +} + +// TrefoilKnot + +class TrefoilKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= Math.PI * 2; + + const x = ( 2 + Math.cos( 3 * t ) ) * Math.cos( 2 * t ); + const y = ( 2 + Math.cos( 3 * t ) ) * Math.sin( 2 * t ); + const z = Math.sin( 3 * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// TorusKnot + +class TorusKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const p = 3; + const q = 4; + + t *= Math.PI * 2; + + const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t ); + const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t ); + const z = Math.sin( q * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// CinquefoilKnot + +class CinquefoilKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const p = 2; + const q = 5; + + t *= Math.PI * 2; + + const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t ); + const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t ); + const z = Math.sin( q * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + + +// TrefoilPolynomialKnot + +class TrefoilPolynomialKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = t * 4 - 2; + + const x = Math.pow( t, 3 ) - 3 * t; + const y = Math.pow( t, 4 ) - 4 * t * t; + const z = 1 / 5 * Math.pow( t, 5 ) - 2 * t; + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +function scaleTo( x, y, t ) { + + const r = y - x; + return t * r + x; + +} + +// FigureEightPolynomialKnot + +class FigureEightPolynomialKnot extends Curve { + + constructor( scale = 1 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = scaleTo( - 4, 4, t ); + + const x = 2 / 5 * t * ( t * t - 7 ) * ( t * t - 10 ); + const y = Math.pow( t, 4 ) - 13 * t * t; + const z = 1 / 10 * t * ( t * t - 4 ) * ( t * t - 9 ) * ( t * t - 12 ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// DecoratedTorusKnot4a + +class DecoratedTorusKnot4a extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= Math.PI * 2; + + const x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) ); + const y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) ); + const z = 0.35 * Math.sin( 5 * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// DecoratedTorusKnot4b + +class DecoratedTorusKnot4b extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const fi = t * Math.PI * 2; + + const x = Math.cos( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) ); + const y = Math.sin( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) ); + const z = 0.2 * Math.sin( 9 * fi ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + + +// DecoratedTorusKnot5a + +class DecoratedTorusKnot5a extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const fi = t * Math.PI * 2; + + const x = Math.cos( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) ); + const y = Math.sin( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) ); + const z = 0.2 * Math.sin( 20 * fi ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// DecoratedTorusKnot5c + +class DecoratedTorusKnot5c extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const fi = t * Math.PI * 2; + + const x = Math.cos( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) ); + const y = Math.sin( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) ); + const z = 0.35 * Math.sin( 15 * fi ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +export { + GrannyKnot, + HeartCurve, + VivianiCurve, + KnotCurve, + HelixCurve, + TrefoilKnot, + TorusKnot, + CinquefoilKnot, + TrefoilPolynomialKnot, + FigureEightPolynomialKnot, + DecoratedTorusKnot4a, + DecoratedTorusKnot4b, + DecoratedTorusKnot5a, + DecoratedTorusKnot5c +}; diff --git a/jsm/curves/NURBSCurve.js b/jsm/curves/NURBSCurve.js new file mode 100644 index 0000000..8be8dde --- /dev/null +++ b/jsm/curves/NURBSCurve.js @@ -0,0 +1,80 @@ +import { + Curve, + Vector3, + Vector4 +} from 'three'; +import * as NURBSUtils from '../curves/NURBSUtils.js'; + +/** + * NURBS curve object + * + * Derives from Curve, overriding getPoint and getTangent. + * + * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight. + * + **/ + +class NURBSCurve extends Curve { + + constructor( + degree, + knots /* array of reals */, + controlPoints /* array of Vector(2|3|4) */, + startKnot /* index in knots */, + endKnot /* index in knots */ + ) { + + super(); + + this.degree = degree; + this.knots = knots; + this.controlPoints = []; + // Used by periodic NURBS to remove hidden spans + this.startKnot = startKnot || 0; + this.endKnot = endKnot || ( this.knots.length - 1 ); + + for ( let i = 0; i < controlPoints.length; ++ i ) { + + // ensure Vector4 for control points + const point = controlPoints[ i ]; + this.controlPoints[ i ] = new Vector4( point.x, point.y, point.z, point.w ); + + } + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u + + // following results in (wx, wy, wz, w) homogeneous point + const hpoint = NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u ); + + if ( hpoint.w !== 1.0 ) { + + // project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1) + hpoint.divideScalar( hpoint.w ); + + } + + return point.set( hpoint.x, hpoint.y, hpoint.z ); + + } + + getTangent( t, optionalTarget = new Vector3() ) { + + const tangent = optionalTarget; + + const u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] ); + const ders = NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 ); + tangent.copy( ders[ 1 ] ).normalize(); + + return tangent; + + } + +} + +export { NURBSCurve }; diff --git a/jsm/curves/NURBSSurface.js b/jsm/curves/NURBSSurface.js new file mode 100644 index 0000000..577ecfc --- /dev/null +++ b/jsm/curves/NURBSSurface.js @@ -0,0 +1,52 @@ +import { + Vector4 +} from 'three'; +import * as NURBSUtils from '../curves/NURBSUtils.js'; + +/** + * NURBS surface object + * + * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight. + **/ + +class NURBSSurface { + + constructor( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) { + + this.degree1 = degree1; + this.degree2 = degree2; + this.knots1 = knots1; + this.knots2 = knots2; + this.controlPoints = []; + + const len1 = knots1.length - degree1 - 1; + const len2 = knots2.length - degree2 - 1; + + // ensure Vector4 for control points + for ( let i = 0; i < len1; ++ i ) { + + this.controlPoints[ i ] = []; + + for ( let j = 0; j < len2; ++ j ) { + + const point = controlPoints[ i ][ j ]; + this.controlPoints[ i ][ j ] = new Vector4( point.x, point.y, point.z, point.w ); + + } + + } + + } + + getPoint( t1, t2, target ) { + + const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u + const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u + + NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target ); + + } + +} + +export { NURBSSurface }; diff --git a/jsm/curves/NURBSUtils.js b/jsm/curves/NURBSUtils.js new file mode 100644 index 0000000..fc77fdb --- /dev/null +++ b/jsm/curves/NURBSUtils.js @@ -0,0 +1,487 @@ +import { + Vector3, + Vector4 +} from 'three'; + +/** + * NURBS utils + * + * See NURBSCurve and NURBSSurface. + **/ + + +/************************************************************** + * NURBS Utils + **************************************************************/ + +/* +Finds knot vector span. + +p : degree +u : parametric value +U : knot vector + +returns the span +*/ +function findSpan( p, u, U ) { + + const n = U.length - p - 1; + + if ( u >= U[ n ] ) { + + return n - 1; + + } + + if ( u <= U[ p ] ) { + + return p; + + } + + let low = p; + let high = n; + let mid = Math.floor( ( low + high ) / 2 ); + + while ( u < U[ mid ] || u >= U[ mid + 1 ] ) { + + if ( u < U[ mid ] ) { + + high = mid; + + } else { + + low = mid; + + } + + mid = Math.floor( ( low + high ) / 2 ); + + } + + return mid; + +} + + +/* +Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2 + +span : span in which u lies +u : parametric point +p : degree +U : knot vector + +returns array[p+1] with basis functions values. +*/ +function calcBasisFunctions( span, u, p, U ) { + + const N = []; + const left = []; + const right = []; + N[ 0 ] = 1.0; + + for ( let j = 1; j <= p; ++ j ) { + + left[ j ] = u - U[ span + 1 - j ]; + right[ j ] = U[ span + j ] - u; + + let saved = 0.0; + + for ( let r = 0; r < j; ++ r ) { + + const rv = right[ r + 1 ]; + const lv = left[ j - r ]; + const temp = N[ r ] / ( rv + lv ); + N[ r ] = saved + rv * temp; + saved = lv * temp; + + } + + N[ j ] = saved; + + } + + return N; + +} + + +/* +Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1. + +p : degree of B-Spline +U : knot vector +P : control points (x, y, z, w) +u : parametric point + +returns point for given u +*/ +function calcBSplinePoint( p, U, P, u ) { + + const span = findSpan( p, u, U ); + const N = calcBasisFunctions( span, u, p, U ); + const C = new Vector4( 0, 0, 0, 0 ); + + for ( let j = 0; j <= p; ++ j ) { + + const point = P[ span - p + j ]; + const Nj = N[ j ]; + const wNj = point.w * Nj; + C.x += point.x * wNj; + C.y += point.y * wNj; + C.z += point.z * wNj; + C.w += point.w * Nj; + + } + + return C; + +} + + +/* +Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3. + +span : span in which u lies +u : parametric point +p : degree +n : number of derivatives to calculate +U : knot vector + +returns array[n+1][p+1] with basis functions derivatives +*/ +function calcBasisFunctionDerivatives( span, u, p, n, U ) { + + const zeroArr = []; + for ( let i = 0; i <= p; ++ i ) + zeroArr[ i ] = 0.0; + + const ders = []; + + for ( let i = 0; i <= n; ++ i ) + ders[ i ] = zeroArr.slice( 0 ); + + const ndu = []; + + for ( let i = 0; i <= p; ++ i ) + ndu[ i ] = zeroArr.slice( 0 ); + + ndu[ 0 ][ 0 ] = 1.0; + + const left = zeroArr.slice( 0 ); + const right = zeroArr.slice( 0 ); + + for ( let j = 1; j <= p; ++ j ) { + + left[ j ] = u - U[ span + 1 - j ]; + right[ j ] = U[ span + j ] - u; + + let saved = 0.0; + + for ( let r = 0; r < j; ++ r ) { + + const rv = right[ r + 1 ]; + const lv = left[ j - r ]; + ndu[ j ][ r ] = rv + lv; + + const temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ]; + ndu[ r ][ j ] = saved + rv * temp; + saved = lv * temp; + + } + + ndu[ j ][ j ] = saved; + + } + + for ( let j = 0; j <= p; ++ j ) { + + ders[ 0 ][ j ] = ndu[ j ][ p ]; + + } + + for ( let r = 0; r <= p; ++ r ) { + + let s1 = 0; + let s2 = 1; + + const a = []; + for ( let i = 0; i <= p; ++ i ) { + + a[ i ] = zeroArr.slice( 0 ); + + } + + a[ 0 ][ 0 ] = 1.0; + + for ( let k = 1; k <= n; ++ k ) { + + let d = 0.0; + const rk = r - k; + const pk = p - k; + + if ( r >= k ) { + + a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ]; + d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ]; + + } + + const j1 = ( rk >= - 1 ) ? 1 : - rk; + const j2 = ( r - 1 <= pk ) ? k - 1 : p - r; + + for ( let j = j1; j <= j2; ++ j ) { + + a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ]; + d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ]; + + } + + if ( r <= pk ) { + + a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ]; + d += a[ s2 ][ k ] * ndu[ r ][ pk ]; + + } + + ders[ k ][ r ] = d; + + const j = s1; + s1 = s2; + s2 = j; + + } + + } + + let r = p; + + for ( let k = 1; k <= n; ++ k ) { + + for ( let j = 0; j <= p; ++ j ) { + + ders[ k ][ j ] *= r; + + } + + r *= p - k; + + } + + return ders; + +} + + +/* + Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2. + + p : degree + U : knot vector + P : control points + u : Parametric points + nd : number of derivatives + + returns array[d+1] with derivatives + */ +function calcBSplineDerivatives( p, U, P, u, nd ) { + + const du = nd < p ? nd : p; + const CK = []; + const span = findSpan( p, u, U ); + const nders = calcBasisFunctionDerivatives( span, u, p, du, U ); + const Pw = []; + + for ( let i = 0; i < P.length; ++ i ) { + + const point = P[ i ].clone(); + const w = point.w; + + point.x *= w; + point.y *= w; + point.z *= w; + + Pw[ i ] = point; + + } + + for ( let k = 0; k <= du; ++ k ) { + + const point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] ); + + for ( let j = 1; j <= p; ++ j ) { + + point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) ); + + } + + CK[ k ] = point; + + } + + for ( let k = du + 1; k <= nd + 1; ++ k ) { + + CK[ k ] = new Vector4( 0, 0, 0 ); + + } + + return CK; + +} + + +/* +Calculate "K over I" + +returns k!/(i!(k-i)!) +*/ +function calcKoverI( k, i ) { + + let nom = 1; + + for ( let j = 2; j <= k; ++ j ) { + + nom *= j; + + } + + let denom = 1; + + for ( let j = 2; j <= i; ++ j ) { + + denom *= j; + + } + + for ( let j = 2; j <= k - i; ++ j ) { + + denom *= j; + + } + + return nom / denom; + +} + + +/* +Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2. + +Pders : result of function calcBSplineDerivatives + +returns array with derivatives for rational curve. +*/ +function calcRationalCurveDerivatives( Pders ) { + + const nd = Pders.length; + const Aders = []; + const wders = []; + + for ( let i = 0; i < nd; ++ i ) { + + const point = Pders[ i ]; + Aders[ i ] = new Vector3( point.x, point.y, point.z ); + wders[ i ] = point.w; + + } + + const CK = []; + + for ( let k = 0; k < nd; ++ k ) { + + const v = Aders[ k ].clone(); + + for ( let i = 1; i <= k; ++ i ) { + + v.sub( CK[ k - i ].clone().multiplyScalar( calcKoverI( k, i ) * wders[ i ] ) ); + + } + + CK[ k ] = v.divideScalar( wders[ 0 ] ); + + } + + return CK; + +} + + +/* +Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2. + +p : degree +U : knot vector +P : control points in homogeneous space +u : parametric points +nd : number of derivatives + +returns array with derivatives. +*/ +function calcNURBSDerivatives( p, U, P, u, nd ) { + + const Pders = calcBSplineDerivatives( p, U, P, u, nd ); + return calcRationalCurveDerivatives( Pders ); + +} + + +/* +Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3. + +p1, p2 : degrees of B-Spline surface +U1, U2 : knot vectors +P : control points (x, y, z, w) +u, v : parametric values + +returns point for given (u, v) +*/ +function calcSurfacePoint( p, q, U, V, P, u, v, target ) { + + const uspan = findSpan( p, u, U ); + const vspan = findSpan( q, v, V ); + const Nu = calcBasisFunctions( uspan, u, p, U ); + const Nv = calcBasisFunctions( vspan, v, q, V ); + const temp = []; + + for ( let l = 0; l <= q; ++ l ) { + + temp[ l ] = new Vector4( 0, 0, 0, 0 ); + for ( let k = 0; k <= p; ++ k ) { + + const point = P[ uspan - p + k ][ vspan - q + l ].clone(); + const w = point.w; + point.x *= w; + point.y *= w; + point.z *= w; + temp[ l ].add( point.multiplyScalar( Nu[ k ] ) ); + + } + + } + + const Sw = new Vector4( 0, 0, 0, 0 ); + for ( let l = 0; l <= q; ++ l ) { + + Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) ); + + } + + Sw.divideScalar( Sw.w ); + target.set( Sw.x, Sw.y, Sw.z ); + +} + + + +export { + findSpan, + calcBasisFunctions, + calcBSplinePoint, + calcBasisFunctionDerivatives, + calcBSplineDerivatives, + calcKoverI, + calcRationalCurveDerivatives, + calcNURBSDerivatives, + calcSurfacePoint, +}; diff --git a/jsm/deprecated/Geometry.js b/jsm/deprecated/Geometry.js new file mode 100644 index 0000000..aae29e5 --- /dev/null +++ b/jsm/deprecated/Geometry.js @@ -0,0 +1,1869 @@ +import { + Box3, + BufferAttribute, + BufferGeometry, + Color, + EventDispatcher, + Float32BufferAttribute, + Matrix3, + Matrix4, + MathUtils, + Object3D, + Sphere, + Vector2, + Vector3 +} from 'three'; + +const _m1 = new Matrix4(); +const _obj = new Object3D(); +const _offset = new Vector3(); + +class Geometry extends EventDispatcher { + + constructor() { + + super(); + + this.uuid = MathUtils.generateUUID(); + + this.name = ''; + this.type = 'Geometry'; + + this.vertices = []; + this.colors = []; + this.faces = []; + this.faceVertexUvs = [[]]; + + this.morphTargets = []; + this.morphNormals = []; + + this.skinWeights = []; + this.skinIndices = []; + + this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + // update flags + + this.elementsNeedUpdate = false; + this.verticesNeedUpdate = false; + this.uvsNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.lineDistancesNeedUpdate = false; + this.groupsNeedUpdate = false; + + } + + applyMatrix4( matrix ) { + + const normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + for ( let i = 0, il = this.vertices.length; i < il; i ++ ) { + + const vertex = this.vertices[ i ]; + vertex.applyMatrix4( matrix ); + + } + + for ( let i = 0, il = this.faces.length; i < il; i ++ ) { + + const face = this.faces[ i ]; + face.normal.applyMatrix3( normalMatrix ).normalize(); + + for ( let j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize(); + + } + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + this.verticesNeedUpdate = true; + this.normalsNeedUpdate = true; + + return this; + + } + + rotateX( angle ) { + + // rotate geometry around world x-axis + + _m1.makeRotationX( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + rotateY( angle ) { + + // rotate geometry around world y-axis + + _m1.makeRotationY( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + rotateZ( angle ) { + + // rotate geometry around world z-axis + + _m1.makeRotationZ( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + translate( x, y, z ) { + + // translate geometry + + _m1.makeTranslation( x, y, z ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + scale( x, y, z ) { + + // scale geometry + + _m1.makeScale( x, y, z ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + lookAt( vector ) { + + _obj.lookAt( vector ); + + _obj.updateMatrix(); + + this.applyMatrix4( _obj.matrix ); + + return this; + + } + + fromBufferGeometry( geometry ) { + + const scope = this; + + const index = geometry.index !== null ? geometry.index : undefined; + const attributes = geometry.attributes; + + if ( attributes.position === undefined ) { + + console.error( 'THREE.Geometry.fromBufferGeometry(): Position attribute required for conversion.' ); + return this; + + } + + const position = attributes.position; + const normal = attributes.normal; + const color = attributes.color; + const uv = attributes.uv; + const uv2 = attributes.uv2; + + if ( uv2 !== undefined ) this.faceVertexUvs[ 1 ] = []; + + for ( let i = 0; i < position.count; i ++ ) { + + scope.vertices.push( new Vector3().fromBufferAttribute( position, i ) ); + + if ( color !== undefined ) { + + scope.colors.push( new Color().fromBufferAttribute( color, i ) ); + + } + + } + + function addFace( a, b, c, materialIndex ) { + + const vertexColors = ( color === undefined ) ? [] : [ + scope.colors[ a ].clone(), + scope.colors[ b ].clone(), + scope.colors[ c ].clone() + ]; + + const vertexNormals = ( normal === undefined ) ? [] : [ + new Vector3().fromBufferAttribute( normal, a ), + new Vector3().fromBufferAttribute( normal, b ), + new Vector3().fromBufferAttribute( normal, c ) + ]; + + const face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex ); + + scope.faces.push( face ); + + if ( uv !== undefined ) { + + scope.faceVertexUvs[ 0 ].push( [ + new Vector2().fromBufferAttribute( uv, a ), + new Vector2().fromBufferAttribute( uv, b ), + new Vector2().fromBufferAttribute( uv, c ) + ] ); + + } + + if ( uv2 !== undefined ) { + + scope.faceVertexUvs[ 1 ].push( [ + new Vector2().fromBufferAttribute( uv2, a ), + new Vector2().fromBufferAttribute( uv2, b ), + new Vector2().fromBufferAttribute( uv2, c ) + ] ); + + } + + } + + const groups = geometry.groups; + + if ( groups.length > 0 ) { + + for ( let i = 0; i < groups.length; i ++ ) { + + const group = groups[ i ]; + + const start = group.start; + const count = group.count; + + for ( let j = start, jl = start + count; j < jl; j += 3 ) { + + if ( index !== undefined ) { + + addFace( index.getX( j ), index.getX( j + 1 ), index.getX( j + 2 ), group.materialIndex ); + + } else { + + addFace( j, j + 1, j + 2, group.materialIndex ); + + } + + } + + } + + } else { + + if ( index !== undefined ) { + + for ( let i = 0; i < index.count; i += 3 ) { + + addFace( index.getX( i ), index.getX( i + 1 ), index.getX( i + 2 ) ); + + } + + } else { + + for ( let i = 0; i < position.count; i += 3 ) { + + addFace( i, i + 1, i + 2 ); + + } + + } + + } + + this.computeFaceNormals(); + + if ( geometry.boundingBox !== null ) { + + this.boundingBox = geometry.boundingBox.clone(); + + } + + if ( geometry.boundingSphere !== null ) { + + this.boundingSphere = geometry.boundingSphere.clone(); + + } + + return this; + + } + + center() { + + this.computeBoundingBox(); + + this.boundingBox.getCenter( _offset ).negate(); + + this.translate( _offset.x, _offset.y, _offset.z ); + + return this; + + } + + normalize() { + + this.computeBoundingSphere(); + + const center = this.boundingSphere.center; + const radius = this.boundingSphere.radius; + + const s = radius === 0 ? 1 : 1.0 / radius; + + const matrix = new Matrix4(); + matrix.set( + s, 0, 0, - s * center.x, + 0, s, 0, - s * center.y, + 0, 0, s, - s * center.z, + 0, 0, 0, 1 + ); + + this.applyMatrix4( matrix ); + + return this; + + } + + computeFaceNormals() { + + const cb = new Vector3(), ab = new Vector3(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vA = this.vertices[ face.a ]; + const vB = this.vertices[ face.b ]; + const vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + cb.normalize(); + + face.normal.copy( cb ); + + } + + } + + computeVertexNormals( areaWeighted = true ) { + + const vertices = new Array( this.vertices.length ); + + for ( let v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ] = new Vector3(); + + } + + if ( areaWeighted ) { + + // vertex normals weighted by triangle areas + // http://www.iquilezles.org/www/articles/normals/normals.htm + + const cb = new Vector3(), ab = new Vector3(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vA = this.vertices[ face.a ]; + const vB = this.vertices[ face.b ]; + const vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + vertices[ face.a ].add( cb ); + vertices[ face.b ].add( cb ); + vertices[ face.c ].add( cb ); + + } + + } else { + + this.computeFaceNormals(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + vertices[ face.a ].add( face.normal ); + vertices[ face.b ].add( face.normal ); + vertices[ face.c ].add( face.normal ); + + } + + } + + for ( let v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ].normalize(); + + } + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + vertexNormals[ 0 ].copy( vertices[ face.a ] ); + vertexNormals[ 1 ].copy( vertices[ face.b ] ); + vertexNormals[ 2 ].copy( vertices[ face.c ] ); + + } else { + + vertexNormals[ 0 ] = vertices[ face.a ].clone(); + vertexNormals[ 1 ] = vertices[ face.b ].clone(); + vertexNormals[ 2 ] = vertices[ face.c ].clone(); + + } + + } + + if ( this.faces.length > 0 ) { + + this.normalsNeedUpdate = true; + + } + + } + + computeFlatVertexNormals() { + + this.computeFaceNormals(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + vertexNormals[ 0 ].copy( face.normal ); + vertexNormals[ 1 ].copy( face.normal ); + vertexNormals[ 2 ].copy( face.normal ); + + } else { + + vertexNormals[ 0 ] = face.normal.clone(); + vertexNormals[ 1 ] = face.normal.clone(); + vertexNormals[ 2 ] = face.normal.clone(); + + } + + } + + if ( this.faces.length > 0 ) { + + this.normalsNeedUpdate = true; + + } + + } + + computeMorphNormals() { + + // save original normals + // - create temp variables on first access + // otherwise just copy (for faster repeated calls) + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + if ( ! face.__originalFaceNormal ) { + + face.__originalFaceNormal = face.normal.clone(); + + } else { + + face.__originalFaceNormal.copy( face.normal ); + + } + + if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = []; + + for ( let i = 0, il = face.vertexNormals.length; i < il; i ++ ) { + + if ( ! face.__originalVertexNormals[ i ] ) { + + face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone(); + + } else { + + face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] ); + + } + + } + + } + + // use temp geometry to compute face and vertex normals for each morph + + const tmpGeo = new Geometry(); + tmpGeo.faces = this.faces; + + for ( let i = 0, il = this.morphTargets.length; i < il; i ++ ) { + + // create on first access + + if ( ! this.morphNormals[ i ] ) { + + this.morphNormals[ i ] = {}; + this.morphNormals[ i ].faceNormals = []; + this.morphNormals[ i ].vertexNormals = []; + + const dstNormalsFace = this.morphNormals[ i ].faceNormals; + const dstNormalsVertex = this.morphNormals[ i ].vertexNormals; + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const faceNormal = new Vector3(); + const vertexNormals = { a: new Vector3(), b: new Vector3(), c: new Vector3() }; + + dstNormalsFace.push( faceNormal ); + dstNormalsVertex.push( vertexNormals ); + + } + + } + + const morphNormals = this.morphNormals[ i ]; + + // set vertices to morph target + + tmpGeo.vertices = this.morphTargets[ i ].vertices; + + // compute morph normals + + tmpGeo.computeFaceNormals(); + tmpGeo.computeVertexNormals(); + + // store morph normals + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const faceNormal = morphNormals.faceNormals[ f ]; + const vertexNormals = morphNormals.vertexNormals[ f ]; + + faceNormal.copy( face.normal ); + + vertexNormals.a.copy( face.vertexNormals[ 0 ] ); + vertexNormals.b.copy( face.vertexNormals[ 1 ] ); + vertexNormals.c.copy( face.vertexNormals[ 2 ] ); + + } + + } + + // restore original normals + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + face.normal = face.__originalFaceNormal; + face.vertexNormals = face.__originalVertexNormals; + + } + + } + + computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + this.boundingBox.setFromPoints( this.vertices ); + + } + + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + this.boundingSphere.setFromPoints( this.vertices ); + + } + + merge( geometry, matrix, materialIndexOffset = 0 ) { + + if ( ! ( geometry && geometry.isGeometry ) ) { + + console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry ); + return; + + } + + let normalMatrix; + const vertexOffset = this.vertices.length, + vertices1 = this.vertices, + vertices2 = geometry.vertices, + faces1 = this.faces, + faces2 = geometry.faces, + colors1 = this.colors, + colors2 = geometry.colors; + + if ( matrix !== undefined ) { + + normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + } + + // vertices + + for ( let i = 0, il = vertices2.length; i < il; i ++ ) { + + const vertex = vertices2[ i ]; + + const vertexCopy = vertex.clone(); + + if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix ); + + vertices1.push( vertexCopy ); + + } + + // colors + + for ( let i = 0, il = colors2.length; i < il; i ++ ) { + + colors1.push( colors2[ i ].clone() ); + + } + + // faces + + for ( let i = 0, il = faces2.length; i < il; i ++ ) { + + const face = faces2[ i ]; + let normal, color; + const faceVertexNormals = face.vertexNormals, + faceVertexColors = face.vertexColors; + + const faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); + faceCopy.normal.copy( face.normal ); + + if ( normalMatrix !== undefined ) { + + faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); + + } + + for ( let j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { + + normal = faceVertexNormals[ j ].clone(); + + if ( normalMatrix !== undefined ) { + + normal.applyMatrix3( normalMatrix ).normalize(); + + } + + faceCopy.vertexNormals.push( normal ); + + } + + faceCopy.color.copy( face.color ); + + for ( let j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { + + color = faceVertexColors[ j ]; + faceCopy.vertexColors.push( color.clone() ); + + } + + faceCopy.materialIndex = face.materialIndex + materialIndexOffset; + + faces1.push( faceCopy ); + + } + + // uvs + + for ( let i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) { + + const faceVertexUvs2 = geometry.faceVertexUvs[ i ]; + + if ( this.faceVertexUvs[ i ] === undefined ) this.faceVertexUvs[ i ] = []; + + for ( let j = 0, jl = faceVertexUvs2.length; j < jl; j ++ ) { + + const uvs2 = faceVertexUvs2[ j ], uvsCopy = []; + + for ( let k = 0, kl = uvs2.length; k < kl; k ++ ) { + + uvsCopy.push( uvs2[ k ].clone() ); + + } + + this.faceVertexUvs[ i ].push( uvsCopy ); + + } + + } + + } + + mergeMesh( mesh ) { + + if ( ! ( mesh && mesh.isMesh ) ) { + + console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh ); + return; + + } + + if ( mesh.matrixAutoUpdate ) mesh.updateMatrix(); + + this.merge( mesh.geometry, mesh.matrix ); + + } + + /* + * Checks for duplicate vertices with hashmap. + * Duplicated vertices are removed + * and faces' vertices are updated. + */ + + mergeVertices( precisionPoints = 4 ) { + + const verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique) + const unique = [], changes = []; + + const precision = Math.pow( 10, precisionPoints ); + + for ( let i = 0, il = this.vertices.length; i < il; i ++ ) { + + const v = this.vertices[ i ]; + const key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision ); + + if ( verticesMap[ key ] === undefined ) { + + verticesMap[ key ] = i; + unique.push( this.vertices[ i ] ); + changes[ i ] = unique.length - 1; + + } else { + + //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); + changes[ i ] = changes[ verticesMap[ key ] ]; + + } + + } + + + // if faces are completely degenerate after merging vertices, we + // have to remove them from the geometry. + const faceIndicesToRemove = []; + + for ( let i = 0, il = this.faces.length; i < il; i ++ ) { + + const face = this.faces[ i ]; + + face.a = changes[ face.a ]; + face.b = changes[ face.b ]; + face.c = changes[ face.c ]; + + const indices = [ face.a, face.b, face.c ]; + + // if any duplicate vertices are found in a Face3 + // we have to remove the face as nothing can be saved + for ( let n = 0; n < 3; n ++ ) { + + if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) { + + faceIndicesToRemove.push( i ); + break; + + } + + } + + } + + for ( let i = faceIndicesToRemove.length - 1; i >= 0; i -- ) { + + const idx = faceIndicesToRemove[ i ]; + + this.faces.splice( idx, 1 ); + + for ( let j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) { + + this.faceVertexUvs[ j ].splice( idx, 1 ); + + } + + } + + // Use unique set of vertices + + const diff = this.vertices.length - unique.length; + this.vertices = unique; + return diff; + + } + + setFromPoints( points ) { + + this.vertices = []; + + for ( let i = 0, l = points.length; i < l; i ++ ) { + + const point = points[ i ]; + this.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) ); + + } + + return this; + + } + + sortFacesByMaterialIndex() { + + const faces = this.faces; + const length = faces.length; + + // tag faces + + for ( let i = 0; i < length; i ++ ) { + + faces[ i ]._id = i; + + } + + // sort faces + + function materialIndexSort( a, b ) { + + return a.materialIndex - b.materialIndex; + + } + + faces.sort( materialIndexSort ); + + // sort uvs + + const uvs1 = this.faceVertexUvs[ 0 ]; + const uvs2 = this.faceVertexUvs[ 1 ]; + + let newUvs1, newUvs2; + + if ( uvs1 && uvs1.length === length ) newUvs1 = []; + if ( uvs2 && uvs2.length === length ) newUvs2 = []; + + for ( let i = 0; i < length; i ++ ) { + + const id = faces[ i ]._id; + + if ( newUvs1 ) newUvs1.push( uvs1[ id ] ); + if ( newUvs2 ) newUvs2.push( uvs2[ id ] ); + + } + + if ( newUvs1 ) this.faceVertexUvs[ 0 ] = newUvs1; + if ( newUvs2 ) this.faceVertexUvs[ 1 ] = newUvs2; + + } + + toJSON() { + + const data = { + metadata: { + version: 4.5, + type: 'Geometry', + generator: 'Geometry.toJSON' + } + }; + + // standard Geometry serialization + + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + + if ( this.parameters !== undefined ) { + + const parameters = this.parameters; + + for ( const key in parameters ) { + + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; + + } + + return data; + + } + + const vertices = []; + + for ( let i = 0; i < this.vertices.length; i ++ ) { + + const vertex = this.vertices[ i ]; + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + const faces = []; + const normals = []; + const normalsHash = {}; + const colors = []; + const colorsHash = {}; + const uvs = []; + const uvsHash = {}; + + for ( let i = 0; i < this.faces.length; i ++ ) { + + const face = this.faces[ i ]; + + const hasMaterial = true; + const hasFaceUv = false; // deprecated + const hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined; + const hasFaceNormal = face.normal.length() > 0; + const hasFaceVertexNormal = face.vertexNormals.length > 0; + const hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1; + const hasFaceVertexColor = face.vertexColors.length > 0; + + let faceType = 0; + + faceType = setBit( faceType, 0, 0 ); // isQuad + faceType = setBit( faceType, 1, hasMaterial ); + faceType = setBit( faceType, 2, hasFaceUv ); + faceType = setBit( faceType, 3, hasFaceVertexUv ); + faceType = setBit( faceType, 4, hasFaceNormal ); + faceType = setBit( faceType, 5, hasFaceVertexNormal ); + faceType = setBit( faceType, 6, hasFaceColor ); + faceType = setBit( faceType, 7, hasFaceVertexColor ); + + faces.push( faceType ); + faces.push( face.a, face.b, face.c ); + faces.push( face.materialIndex ); + + if ( hasFaceVertexUv ) { + + const faceVertexUvs = this.faceVertexUvs[ 0 ][ i ]; + + faces.push( + getUvIndex( faceVertexUvs[ 0 ] ), + getUvIndex( faceVertexUvs[ 1 ] ), + getUvIndex( faceVertexUvs[ 2 ] ) + ); + + } + + if ( hasFaceNormal ) { + + faces.push( getNormalIndex( face.normal ) ); + + } + + if ( hasFaceVertexNormal ) { + + const vertexNormals = face.vertexNormals; + + faces.push( + getNormalIndex( vertexNormals[ 0 ] ), + getNormalIndex( vertexNormals[ 1 ] ), + getNormalIndex( vertexNormals[ 2 ] ) + ); + + } + + if ( hasFaceColor ) { + + faces.push( getColorIndex( face.color ) ); + + } + + if ( hasFaceVertexColor ) { + + const vertexColors = face.vertexColors; + + faces.push( + getColorIndex( vertexColors[ 0 ] ), + getColorIndex( vertexColors[ 1 ] ), + getColorIndex( vertexColors[ 2 ] ) + ); + + } + + } + + function setBit( value, position, enabled ) { + + return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) ); + + } + + function getNormalIndex( normal ) { + + const hash = normal.x.toString() + normal.y.toString() + normal.z.toString(); + + if ( normalsHash[ hash ] !== undefined ) { + + return normalsHash[ hash ]; + + } + + normalsHash[ hash ] = normals.length / 3; + normals.push( normal.x, normal.y, normal.z ); + + return normalsHash[ hash ]; + + } + + function getColorIndex( color ) { + + const hash = color.r.toString() + color.g.toString() + color.b.toString(); + + if ( colorsHash[ hash ] !== undefined ) { + + return colorsHash[ hash ]; + + } + + colorsHash[ hash ] = colors.length; + colors.push( color.getHex() ); + + return colorsHash[ hash ]; + + } + + function getUvIndex( uv ) { + + const hash = uv.x.toString() + uv.y.toString(); + + if ( uvsHash[ hash ] !== undefined ) { + + return uvsHash[ hash ]; + + } + + uvsHash[ hash ] = uvs.length / 2; + uvs.push( uv.x, uv.y ); + + return uvsHash[ hash ]; + + } + + data.data = {}; + + data.data.vertices = vertices; + data.data.normals = normals; + if ( colors.length > 0 ) data.data.colors = colors; + if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility + data.data.faces = faces; + + return data; + + } + + clone() { + + /* + // Handle primitives + + const parameters = this.parameters; + + if ( parameters !== undefined ) { + + const values = []; + + for ( const key in parameters ) { + + values.push( parameters[ key ] ); + + } + + const geometry = Object.create( this.constructor.prototype ); + this.constructor.apply( geometry, values ); + return geometry; + + } + + return new this.constructor().copy( this ); + */ + + return new Geometry().copy( this ); + + } + + copy( source ) { + + // reset + + this.vertices = []; + this.colors = []; + this.faces = []; + this.faceVertexUvs = [[]]; + this.morphTargets = []; + this.morphNormals = []; + this.skinWeights = []; + this.skinIndices = []; + this.lineDistances = []; + this.boundingBox = null; + this.boundingSphere = null; + + // name + + this.name = source.name; + + // vertices + + const vertices = source.vertices; + + for ( let i = 0, il = vertices.length; i < il; i ++ ) { + + this.vertices.push( vertices[ i ].clone() ); + + } + + // colors + + const colors = source.colors; + + for ( let i = 0, il = colors.length; i < il; i ++ ) { + + this.colors.push( colors[ i ].clone() ); + + } + + // faces + + const faces = source.faces; + + for ( let i = 0, il = faces.length; i < il; i ++ ) { + + this.faces.push( faces[ i ].clone() ); + + } + + // face vertex uvs + + for ( let i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) { + + const faceVertexUvs = source.faceVertexUvs[ i ]; + + if ( this.faceVertexUvs[ i ] === undefined ) { + + this.faceVertexUvs[ i ] = []; + + } + + for ( let j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) { + + const uvs = faceVertexUvs[ j ], uvsCopy = []; + + for ( let k = 0, kl = uvs.length; k < kl; k ++ ) { + + const uv = uvs[ k ]; + + uvsCopy.push( uv.clone() ); + + } + + this.faceVertexUvs[ i ].push( uvsCopy ); + + } + + } + + // morph targets + + const morphTargets = source.morphTargets; + + for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { + + const morphTarget = {}; + morphTarget.name = morphTargets[ i ].name; + + // vertices + + if ( morphTargets[ i ].vertices !== undefined ) { + + morphTarget.vertices = []; + + for ( let j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) { + + morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() ); + + } + + } + + // normals + + if ( morphTargets[ i ].normals !== undefined ) { + + morphTarget.normals = []; + + for ( let j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) { + + morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() ); + + } + + } + + this.morphTargets.push( morphTarget ); + + } + + // morph normals + + const morphNormals = source.morphNormals; + + for ( let i = 0, il = morphNormals.length; i < il; i ++ ) { + + const morphNormal = {}; + + // vertex normals + + if ( morphNormals[ i ].vertexNormals !== undefined ) { + + morphNormal.vertexNormals = []; + + for ( let j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) { + + const srcVertexNormal = morphNormals[ i ].vertexNormals[ j ]; + const destVertexNormal = {}; + + destVertexNormal.a = srcVertexNormal.a.clone(); + destVertexNormal.b = srcVertexNormal.b.clone(); + destVertexNormal.c = srcVertexNormal.c.clone(); + + morphNormal.vertexNormals.push( destVertexNormal ); + + } + + } + + // face normals + + if ( morphNormals[ i ].faceNormals !== undefined ) { + + morphNormal.faceNormals = []; + + for ( let j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) { + + morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() ); + + } + + } + + this.morphNormals.push( morphNormal ); + + } + + // skin weights + + const skinWeights = source.skinWeights; + + for ( let i = 0, il = skinWeights.length; i < il; i ++ ) { + + this.skinWeights.push( skinWeights[ i ].clone() ); + + } + + // skin indices + + const skinIndices = source.skinIndices; + + for ( let i = 0, il = skinIndices.length; i < il; i ++ ) { + + this.skinIndices.push( skinIndices[ i ].clone() ); + + } + + // line distances + + const lineDistances = source.lineDistances; + + for ( let i = 0, il = lineDistances.length; i < il; i ++ ) { + + this.lineDistances.push( lineDistances[ i ] ); + + } + + // bounding box + + const boundingBox = source.boundingBox; + + if ( boundingBox !== null ) { + + this.boundingBox = boundingBox.clone(); + + } + + // bounding sphere + + const boundingSphere = source.boundingSphere; + + if ( boundingSphere !== null ) { + + this.boundingSphere = boundingSphere.clone(); + + } + + // update flags + + this.elementsNeedUpdate = source.elementsNeedUpdate; + this.verticesNeedUpdate = source.verticesNeedUpdate; + this.uvsNeedUpdate = source.uvsNeedUpdate; + this.normalsNeedUpdate = source.normalsNeedUpdate; + this.colorsNeedUpdate = source.colorsNeedUpdate; + this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate; + this.groupsNeedUpdate = source.groupsNeedUpdate; + + return this; + + } + + toBufferGeometry() { + + const geometry = new DirectGeometry().fromGeometry( this ); + + const buffergeometry = new BufferGeometry(); + + const positions = new Float32Array( geometry.vertices.length * 3 ); + buffergeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) ); + + if ( geometry.normals.length > 0 ) { + + const normals = new Float32Array( geometry.normals.length * 3 ); + buffergeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) ); + + } + + if ( geometry.colors.length > 0 ) { + + const colors = new Float32Array( geometry.colors.length * 3 ); + buffergeometry.setAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) ); + + } + + if ( geometry.uvs.length > 0 ) { + + const uvs = new Float32Array( geometry.uvs.length * 2 ); + buffergeometry.setAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) ); + + } + + if ( geometry.uvs2.length > 0 ) { + + const uvs2 = new Float32Array( geometry.uvs2.length * 2 ); + buffergeometry.setAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) ); + + } + + // groups + + buffergeometry.groups = geometry.groups; + + // morphs + + for ( const name in geometry.morphTargets ) { + + const array = []; + const morphTargets = geometry.morphTargets[ name ]; + + for ( let i = 0, l = morphTargets.length; i < l; i ++ ) { + + const morphTarget = morphTargets[ i ]; + + const attribute = new Float32BufferAttribute( morphTarget.data.length * 3, 3 ); + attribute.name = morphTarget.name; + + array.push( attribute.copyVector3sArray( morphTarget.data ) ); + + } + + buffergeometry.morphAttributes[ name ] = array; + + } + + // skinning + + if ( geometry.skinIndices.length > 0 ) { + + const skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 ); + buffergeometry.setAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) ); + + } + + if ( geometry.skinWeights.length > 0 ) { + + const skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 ); + buffergeometry.setAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) ); + + } + + // + + if ( geometry.boundingSphere !== null ) { + + buffergeometry.boundingSphere = geometry.boundingSphere.clone(); + + } + + if ( geometry.boundingBox !== null ) { + + buffergeometry.boundingBox = geometry.boundingBox.clone(); + + } + + return buffergeometry; + + } + + computeTangents() { + + console.error( 'THREE.Geometry: .computeTangents() has been removed.' ); + + } + + computeLineDistances() { + + console.error( 'THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.' ); + + } + + applyMatrix( matrix ) { + + console.warn( 'THREE.Geometry: .applyMatrix() has been renamed to .applyMatrix4().' ); + return this.applyMatrix4( matrix ); + + } + + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + static createBufferGeometryFromObject( object ) { + + let buffergeometry = new BufferGeometry(); + + const geometry = object.geometry; + + if ( object.isPoints || object.isLine ) { + + const positions = new Float32BufferAttribute( geometry.vertices.length * 3, 3 ); + const colors = new Float32BufferAttribute( geometry.colors.length * 3, 3 ); + + buffergeometry.setAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) ); + buffergeometry.setAttribute( 'color', colors.copyColorsArray( geometry.colors ) ); + + if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) { + + const lineDistances = new Float32BufferAttribute( geometry.lineDistances.length, 1 ); + + buffergeometry.setAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) ); + + } + + if ( geometry.boundingSphere !== null ) { + + buffergeometry.boundingSphere = geometry.boundingSphere.clone(); + + } + + if ( geometry.boundingBox !== null ) { + + buffergeometry.boundingBox = geometry.boundingBox.clone(); + + } + + } else if ( object.isMesh ) { + + buffergeometry = geometry.toBufferGeometry(); + + } + + return buffergeometry; + + } + +} + +Geometry.prototype.isGeometry = true; + +class DirectGeometry { + + constructor() { + + this.vertices = []; + this.normals = []; + this.colors = []; + this.uvs = []; + this.uvs2 = []; + + this.groups = []; + + this.morphTargets = {}; + + this.skinWeights = []; + this.skinIndices = []; + + // this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + // update flags + + this.verticesNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.uvsNeedUpdate = false; + this.groupsNeedUpdate = false; + + } + + computeGroups( geometry ) { + + const groups = []; + + let group, i; + let materialIndex = undefined; + + const faces = geometry.faces; + + for ( i = 0; i < faces.length; i ++ ) { + + const face = faces[ i ]; + + // materials + + if ( face.materialIndex !== materialIndex ) { + + materialIndex = face.materialIndex; + + if ( group !== undefined ) { + + group.count = ( i * 3 ) - group.start; + groups.push( group ); + + } + + group = { + start: i * 3, + materialIndex: materialIndex + }; + + } + + } + + if ( group !== undefined ) { + + group.count = ( i * 3 ) - group.start; + groups.push( group ); + + } + + this.groups = groups; + + } + + fromGeometry( geometry ) { + + const faces = geometry.faces; + const vertices = geometry.vertices; + const faceVertexUvs = geometry.faceVertexUvs; + + const hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0; + const hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0; + + // morphs + + const morphTargets = geometry.morphTargets; + const morphTargetsLength = morphTargets.length; + + let morphTargetsPosition; + + if ( morphTargetsLength > 0 ) { + + morphTargetsPosition = []; + + for ( let i = 0; i < morphTargetsLength; i ++ ) { + + morphTargetsPosition[ i ] = { + name: morphTargets[ i ].name, + data: [] + }; + + } + + this.morphTargets.position = morphTargetsPosition; + + } + + const morphNormals = geometry.morphNormals; + const morphNormalsLength = morphNormals.length; + + let morphTargetsNormal; + + if ( morphNormalsLength > 0 ) { + + morphTargetsNormal = []; + + for ( let i = 0; i < morphNormalsLength; i ++ ) { + + morphTargetsNormal[ i ] = { + name: morphNormals[ i ].name, + data: [] + }; + + } + + this.morphTargets.normal = morphTargetsNormal; + + } + + // skins + + const skinIndices = geometry.skinIndices; + const skinWeights = geometry.skinWeights; + + const hasSkinIndices = skinIndices.length === vertices.length; + const hasSkinWeights = skinWeights.length === vertices.length; + + // + + if ( vertices.length > 0 && faces.length === 0 ) { + + console.error( 'THREE.DirectGeometry: Faceless geometries are not supported.' ); + + } + + for ( let i = 0; i < faces.length; i ++ ) { + + const face = faces[ i ]; + + this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] ); + + const vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] ); + + } else { + + const normal = face.normal; + + this.normals.push( normal, normal, normal ); + + } + + const vertexColors = face.vertexColors; + + if ( vertexColors.length === 3 ) { + + this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] ); + + } else { + + const color = face.color; + + this.colors.push( color, color, color ); + + } + + if ( hasFaceVertexUv === true ) { + + const vertexUvs = faceVertexUvs[ 0 ][ i ]; + + if ( vertexUvs !== undefined ) { + + this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); + + } else { + + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i ); + + this.uvs.push( new Vector2(), new Vector2(), new Vector2() ); + + } + + } + + if ( hasFaceVertexUv2 === true ) { + + const vertexUvs = faceVertexUvs[ 1 ][ i ]; + + if ( vertexUvs !== undefined ) { + + this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); + + } else { + + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i ); + + this.uvs2.push( new Vector2(), new Vector2(), new Vector2() ); + + } + + } + + // morphs + + for ( let j = 0; j < morphTargetsLength; j ++ ) { + + const morphTarget = morphTargets[ j ].vertices; + + morphTargetsPosition[ j ].data.push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] ); + + } + + for ( let j = 0; j < morphNormalsLength; j ++ ) { + + const morphNormal = morphNormals[ j ].vertexNormals[ i ]; + + morphTargetsNormal[ j ].data.push( morphNormal.a, morphNormal.b, morphNormal.c ); + + } + + // skins + + if ( hasSkinIndices ) { + + this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] ); + + } + + if ( hasSkinWeights ) { + + this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] ); + + } + + } + + this.computeGroups( geometry ); + + this.verticesNeedUpdate = geometry.verticesNeedUpdate; + this.normalsNeedUpdate = geometry.normalsNeedUpdate; + this.colorsNeedUpdate = geometry.colorsNeedUpdate; + this.uvsNeedUpdate = geometry.uvsNeedUpdate; + this.groupsNeedUpdate = geometry.groupsNeedUpdate; + + if ( geometry.boundingSphere !== null ) { + + this.boundingSphere = geometry.boundingSphere.clone(); + + } + + if ( geometry.boundingBox !== null ) { + + this.boundingBox = geometry.boundingBox.clone(); + + } + + return this; + + } + +} + +class Face3 { + + constructor( a, b, c, normal, color, materialIndex = 0 ) { + + this.a = a; + this.b = b; + this.c = c; + + this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3(); + this.vertexNormals = Array.isArray( normal ) ? normal : []; + + this.color = ( color && color.isColor ) ? color : new Color(); + this.vertexColors = Array.isArray( color ) ? color : []; + + this.materialIndex = materialIndex; + + } + + clone() { + + return new this.constructor().copy( this ); + + } + + copy( source ) { + + this.a = source.a; + this.b = source.b; + this.c = source.c; + + this.normal.copy( source.normal ); + this.color.copy( source.color ); + + this.materialIndex = source.materialIndex; + + for ( let i = 0, il = source.vertexNormals.length; i < il; i ++ ) { + + this.vertexNormals[ i ] = source.vertexNormals[ i ].clone(); + + } + + for ( let i = 0, il = source.vertexColors.length; i < il; i ++ ) { + + this.vertexColors[ i ] = source.vertexColors[ i ].clone(); + + } + + return this; + + } + +} + +export { Face3, Geometry }; diff --git a/jsm/effects/AnaglyphEffect.js b/jsm/effects/AnaglyphEffect.js new file mode 100644 index 0000000..d2b4134 --- /dev/null +++ b/jsm/effects/AnaglyphEffect.js @@ -0,0 +1,168 @@ +import { + LinearFilter, + Matrix3, + Mesh, + NearestFilter, + OrthographicCamera, + PlaneGeometry, + RGBAFormat, + Scene, + ShaderMaterial, + StereoCamera, + WebGLRenderTarget +} from 'three'; + +class AnaglyphEffect { + + constructor( renderer, width = 512, height = 512 ) { + + // Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4 + + this.colorMatrixLeft = new Matrix3().fromArray( [ + 0.456100, - 0.0400822, - 0.0152161, + 0.500484, - 0.0378246, - 0.0205971, + 0.176381, - 0.0157589, - 0.00546856 + ] ); + + this.colorMatrixRight = new Matrix3().fromArray( [ + - 0.0434706, 0.378476, - 0.0721527, + - 0.0879388, 0.73364, - 0.112961, + - 0.00155529, - 0.0184503, 1.2264 + ] ); + + const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); + + const _scene = new Scene(); + + const _stereo = new StereoCamera(); + + const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat }; + + const _renderTargetL = new WebGLRenderTarget( width, height, _params ); + const _renderTargetR = new WebGLRenderTarget( width, height, _params ); + + const _material = new ShaderMaterial( { + + uniforms: { + + 'mapLeft': { value: _renderTargetL.texture }, + 'mapRight': { value: _renderTargetR.texture }, + + 'colorMatrixLeft': { value: this.colorMatrixLeft }, + 'colorMatrixRight': { value: this.colorMatrixRight } + + }, + + vertexShader: [ + + 'varying vec2 vUv;', + + 'void main() {', + + ' vUv = vec2( uv.x, uv.y );', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}' + + ].join( '\n' ), + + fragmentShader: [ + + 'uniform sampler2D mapLeft;', + 'uniform sampler2D mapRight;', + 'varying vec2 vUv;', + + 'uniform mat3 colorMatrixLeft;', + 'uniform mat3 colorMatrixRight;', + + // These functions implement sRGB linearization and gamma correction + + 'float lin( float c ) {', + ' return c <= 0.04045 ? c * 0.0773993808 :', + ' pow( c * 0.9478672986 + 0.0521327014, 2.4 );', + '}', + + 'vec4 lin( vec4 c ) {', + ' return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );', + '}', + + 'float dev( float c ) {', + ' return c <= 0.0031308 ? c * 12.92', + ' : pow( c, 0.41666 ) * 1.055 - 0.055;', + '}', + + + 'void main() {', + + ' vec2 uv = vUv;', + + ' vec4 colorL = lin( texture2D( mapLeft, uv ) );', + ' vec4 colorR = lin( texture2D( mapRight, uv ) );', + + ' vec3 color = clamp(', + ' colorMatrixLeft * colorL.rgb +', + ' colorMatrixRight * colorR.rgb, 0., 1. );', + + ' gl_FragColor = vec4(', + ' dev( color.r ), dev( color.g ), dev( color.b ),', + ' max( colorL.a, colorR.a ) );', + + '}' + + ].join( '\n' ) + + } ); + + const _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material ); + _scene.add( _mesh ); + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + const pixelRatio = renderer.getPixelRatio(); + + _renderTargetL.setSize( width * pixelRatio, height * pixelRatio ); + _renderTargetR.setSize( width * pixelRatio, height * pixelRatio ); + + }; + + this.render = function ( scene, camera ) { + + const currentRenderTarget = renderer.getRenderTarget(); + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.setRenderTarget( _renderTargetL ); + renderer.clear(); + renderer.render( scene, _stereo.cameraL ); + + renderer.setRenderTarget( _renderTargetR ); + renderer.clear(); + renderer.render( scene, _stereo.cameraR ); + + renderer.setRenderTarget( null ); + renderer.render( _scene, _camera ); + + renderer.setRenderTarget( currentRenderTarget ); + + }; + + this.dispose = function () { + + _renderTargetL.dispose(); + _renderTargetR.dispose(); + _mesh.geometry.dispose(); + _mesh.material.dispose(); + + }; + + } + +} + +export { AnaglyphEffect }; diff --git a/jsm/effects/AsciiEffect.js b/jsm/effects/AsciiEffect.js new file mode 100644 index 0000000..b42b73a --- /dev/null +++ b/jsm/effects/AsciiEffect.js @@ -0,0 +1,279 @@ +/** + * Ascii generation is based on https://github.com/hassadee/jsascii/blob/master/jsascii.js + * + * 16 April 2012 - @blurspline + */ + +class AsciiEffect { + + constructor( renderer, charSet = ' .:-=+*#%@', options = {} ) { + + // ' .,:;=|iI+hHOE#`$'; + // darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/ + // ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split(''); + + // Some ASCII settings + + const bResolution = ! options[ 'resolution' ] ? 0.15 : options[ 'resolution' ]; // Higher for more details + const iScale = ! options[ 'scale' ] ? 1 : options[ 'scale' ]; + const bColor = ! options[ 'color' ] ? false : options[ 'color' ]; // nice but slows down rendering! + const bAlpha = ! options[ 'alpha' ] ? false : options[ 'alpha' ]; // Transparency + const bBlock = ! options[ 'block' ] ? false : options[ 'block' ]; // blocked characters. like good O dos + const bInvert = ! options[ 'invert' ] ? false : options[ 'invert' ]; // black is white, white is black + + const strResolution = 'low'; + + let width, height; + + const domElement = document.createElement( 'div' ); + domElement.style.cursor = 'default'; + + const oAscii = document.createElement( 'table' ); + domElement.appendChild( oAscii ); + + let iWidth, iHeight; + let oImg; + + this.setSize = function ( w, h ) { + + width = w; + height = h; + + renderer.setSize( w, h ); + + initAsciiSize(); + + }; + + + this.render = function ( scene, camera ) { + + renderer.render( scene, camera ); + asciifyImage( oAscii ); + + }; + + this.domElement = domElement; + + + // Throw in ascii library from https://github.com/hassadee/jsascii/blob/master/jsascii.js (MIT License) + + function initAsciiSize() { + + iWidth = Math.round( width * fResolution ); + iHeight = Math.round( height * fResolution ); + + oCanvas.width = iWidth; + oCanvas.height = iHeight; + // oCanvas.style.display = "none"; + // oCanvas.style.width = iWidth; + // oCanvas.style.height = iHeight; + + oImg = renderer.domElement; + + if ( oImg.style.backgroundColor ) { + + oAscii.rows[ 0 ].cells[ 0 ].style.backgroundColor = oImg.style.backgroundColor; + oAscii.rows[ 0 ].cells[ 0 ].style.color = oImg.style.color; + + } + + oAscii.cellSpacing = 0; + oAscii.cellPadding = 0; + + const oStyle = oAscii.style; + oStyle.display = 'inline'; + oStyle.width = Math.round( iWidth / fResolution * iScale ) + 'px'; + oStyle.height = Math.round( iHeight / fResolution * iScale ) + 'px'; + oStyle.whiteSpace = 'pre'; + oStyle.margin = '0px'; + oStyle.padding = '0px'; + oStyle.letterSpacing = fLetterSpacing + 'px'; + oStyle.fontFamily = strFont; + oStyle.fontSize = fFontSize + 'px'; + oStyle.lineHeight = fLineHeight + 'px'; + oStyle.textAlign = 'left'; + oStyle.textDecoration = 'none'; + + } + + + const aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' ); + const aDefaultColorCharList = ( ' CGO08@' ).split( '' ); + const strFont = 'courier new, monospace'; + + const oCanvasImg = renderer.domElement; + + const oCanvas = document.createElement( 'canvas' ); + if ( ! oCanvas.getContext ) { + + return; + + } + + const oCtx = oCanvas.getContext( '2d' ); + if ( ! oCtx.getImageData ) { + + return; + + } + + let aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList ); + + if ( charSet ) aCharList = charSet; + + let fResolution = 0.5; + + switch ( strResolution ) { + + case 'low' : fResolution = 0.25; break; + case 'medium' : fResolution = 0.5; break; + case 'high' : fResolution = 1; break; + + } + + if ( bResolution ) fResolution = bResolution; + + // Setup dom + + const fFontSize = ( 2 / fResolution ) * iScale; + const fLineHeight = ( 2 / fResolution ) * iScale; + + // adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width. + + let fLetterSpacing = 0; + + if ( strResolution == 'low' ) { + + switch ( iScale ) { + + case 1 : fLetterSpacing = - 1; break; + case 2 : + case 3 : fLetterSpacing = - 2.1; break; + case 4 : fLetterSpacing = - 3.1; break; + case 5 : fLetterSpacing = - 4.15; break; + + } + + } + + if ( strResolution == 'medium' ) { + + switch ( iScale ) { + + case 1 : fLetterSpacing = 0; break; + case 2 : fLetterSpacing = - 1; break; + case 3 : fLetterSpacing = - 1.04; break; + case 4 : + case 5 : fLetterSpacing = - 2.1; break; + + } + + } + + if ( strResolution == 'high' ) { + + switch ( iScale ) { + + case 1 : + case 2 : fLetterSpacing = 0; break; + case 3 : + case 4 : + case 5 : fLetterSpacing = - 1; break; + + } + + } + + + // can't get a span or div to flow like an img element, but a table works? + + + // convert img element to ascii + + function asciifyImage( oAscii ) { + + oCtx.clearRect( 0, 0, iWidth, iHeight ); + oCtx.drawImage( oCanvasImg, 0, 0, iWidth, iHeight ); + const oImgData = oCtx.getImageData( 0, 0, iWidth, iHeight ).data; + + // Coloring loop starts now + let strChars = ''; + + // console.time('rendering'); + + for ( let y = 0; y < iHeight; y += 2 ) { + + for ( let x = 0; x < iWidth; x ++ ) { + + const iOffset = ( y * iWidth + x ) * 4; + + const iRed = oImgData[ iOffset ]; + const iGreen = oImgData[ iOffset + 1 ]; + const iBlue = oImgData[ iOffset + 2 ]; + const iAlpha = oImgData[ iOffset + 3 ]; + let iCharIdx; + + let fBrightness; + + fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255; + // fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255; + + if ( iAlpha == 0 ) { + + // should calculate alpha instead, but quick hack :) + //fBrightness *= (iAlpha / 255); + fBrightness = 1; + + } + + iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) ); + + if ( bInvert ) { + + iCharIdx = aCharList.length - iCharIdx - 1; + + } + + // good for debugging + //fBrightness = Math.floor(fBrightness * 10); + //strThisChar = fBrightness; + + let strThisChar = aCharList[ iCharIdx ]; + + if ( strThisChar === undefined || strThisChar == ' ' ) + strThisChar = ' '; + + if ( bColor ) { + + strChars += '' + strThisChar + ''; + + } else { + + strChars += strThisChar; + + } + + } + + strChars += '
'; + + } + + oAscii.innerHTML = '' + strChars + ''; + + // console.timeEnd('rendering'); + + // return oAscii; + + } + + } + +} + +export { AsciiEffect }; diff --git a/jsm/effects/OutlineEffect.js b/jsm/effects/OutlineEffect.js new file mode 100644 index 0000000..02c70fa --- /dev/null +++ b/jsm/effects/OutlineEffect.js @@ -0,0 +1,574 @@ +import { + BackSide, + Color, + ShaderMaterial, + UniformsLib, + UniformsUtils +} from 'three'; + +/** + * Reference: https://en.wikipedia.org/wiki/Cel_shading + * + * API + * + * 1. Traditional + * + * const effect = new OutlineEffect( renderer ); + * + * function render() { + * + * effect.render( scene, camera ); + * + * } + * + * 2. VR compatible + * + * const effect = new OutlineEffect( renderer ); + * let renderingOutline = false; + * + * scene.onAfterRender = function () { + * + * if ( renderingOutline ) return; + * + * renderingOutline = true; + * + * effect.renderOutline( scene, camera ); + * + * renderingOutline = false; + * + * }; + * + * function render() { + * + * renderer.render( scene, camera ); + * + * } + * + * // How to set default outline parameters + * new OutlineEffect( renderer, { + * defaultThickness: 0.01, + * defaultColor: [ 0, 0, 0 ], + * defaultAlpha: 0.8, + * defaultKeepAlive: true // keeps outline material in cache even if material is removed from scene + * } ); + * + * // How to set outline parameters for each material + * material.userData.outlineParameters = { + * thickness: 0.01, + * color: [ 0, 0, 0 ] + * alpha: 0.8, + * visible: true, + * keepAlive: true + * }; + */ + +class OutlineEffect { + + constructor( renderer, parameters = {} ) { + + this.enabled = true; + + const defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003; + const defaultColor = new Color().fromArray( parameters.defaultColor !== undefined ? parameters.defaultColor : [ 0, 0, 0 ] ); + const defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0; + const defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false; + + // object.material.uuid -> outlineMaterial or + // object.material[ n ].uuid -> outlineMaterial + // save at the outline material creation and release + // if it's unused removeThresholdCount frames + // unless keepAlive is true. + const cache = {}; + + const removeThresholdCount = 60; + + // outlineMaterial.uuid -> object.material or + // outlineMaterial.uuid -> object.material[ n ] + // save before render and release after render. + const originalMaterials = {}; + + // object.uuid -> originalOnBeforeRender + // save before render and release after render. + const originalOnBeforeRenders = {}; + + //this.cache = cache; // for debug + + const uniformsOutline = { + outlineThickness: { value: defaultThickness }, + outlineColor: { value: defaultColor }, + outlineAlpha: { value: defaultAlpha } + }; + + const vertexShader = [ + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + + 'uniform float outlineThickness;', + + 'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {', + ' float thickness = outlineThickness;', + ' const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex + ' vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );', + // NOTE: subtract pos2 from pos because BackSide objectNormal is negative + ' vec4 norm = normalize( pos - pos2 );', + ' return pos + norm * thickness * pos.w * ratio;', + '}', + + 'void main() {', + + ' #include ', + + ' #include ', + ' #include ', + ' #include ', + ' #include ', + + ' #include ', + ' #include ', + ' #include ', + ' #include ', + ' #include ', + + ' vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide + + ' gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );', + + ' #include ', + ' #include ', + ' #include ', + + '}', + + ].join( '\n' ); + + const fragmentShader = [ + + '#include ', + '#include ', + '#include ', + '#include ', + + 'uniform vec3 outlineColor;', + 'uniform float outlineAlpha;', + + 'void main() {', + + ' #include ', + ' #include ', + + ' gl_FragColor = vec4( outlineColor, outlineAlpha );', + + ' #include ', + ' #include ', + ' #include ', + ' #include ', + + '}' + + ].join( '\n' ); + + function createMaterial() { + + return new ShaderMaterial( { + type: 'OutlineEffect', + uniforms: UniformsUtils.merge( [ + UniformsLib[ 'fog' ], + UniformsLib[ 'displacementmap' ], + uniformsOutline + ] ), + vertexShader: vertexShader, + fragmentShader: fragmentShader, + side: BackSide + } ); + + } + + function getOutlineMaterialFromCache( originalMaterial ) { + + let data = cache[ originalMaterial.uuid ]; + + if ( data === undefined ) { + + data = { + material: createMaterial(), + used: true, + keepAlive: defaultKeepAlive, + count: 0 + }; + + cache[ originalMaterial.uuid ] = data; + + } + + data.used = true; + + return data.material; + + } + + function getOutlineMaterial( originalMaterial ) { + + const outlineMaterial = getOutlineMaterialFromCache( originalMaterial ); + + originalMaterials[ outlineMaterial.uuid ] = originalMaterial; + + updateOutlineMaterial( outlineMaterial, originalMaterial ); + + return outlineMaterial; + + } + + function isCompatible( object ) { + + const geometry = object.geometry; + let hasNormals = false; + + if ( object.geometry !== undefined ) { + + if ( geometry.isBufferGeometry ) { + + hasNormals = geometry.attributes.normal !== undefined; + + } else { + + hasNormals = true; // the renderer always produces a normal attribute for Geometry + + } + + } + + return ( object.isMesh === true && object.material !== undefined && hasNormals === true ); + + } + + function setOutlineMaterial( object ) { + + if ( isCompatible( object ) === false ) return; + + if ( Array.isArray( object.material ) ) { + + for ( let i = 0, il = object.material.length; i < il; i ++ ) { + + object.material[ i ] = getOutlineMaterial( object.material[ i ] ); + + } + + } else { + + object.material = getOutlineMaterial( object.material ); + + } + + originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender; + object.onBeforeRender = onBeforeRender; + + } + + function restoreOriginalMaterial( object ) { + + if ( isCompatible( object ) === false ) return; + + if ( Array.isArray( object.material ) ) { + + for ( let i = 0, il = object.material.length; i < il; i ++ ) { + + object.material[ i ] = originalMaterials[ object.material[ i ].uuid ]; + + } + + } else { + + object.material = originalMaterials[ object.material.uuid ]; + + } + + object.onBeforeRender = originalOnBeforeRenders[ object.uuid ]; + + } + + function onBeforeRender( renderer, scene, camera, geometry, material ) { + + const originalMaterial = originalMaterials[ material.uuid ]; + + // just in case + if ( originalMaterial === undefined ) return; + + updateUniforms( material, originalMaterial ); + + } + + function updateUniforms( material, originalMaterial ) { + + const outlineParameters = originalMaterial.userData.outlineParameters; + + material.uniforms.outlineAlpha.value = originalMaterial.opacity; + + if ( outlineParameters !== undefined ) { + + if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness; + if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.fromArray( outlineParameters.color ); + if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha; + + } + + if ( originalMaterial.displacementMap ) { + + material.uniforms.displacementMap.value = originalMaterial.displacementMap; + material.uniforms.displacementScale.value = originalMaterial.displacementScale; + material.uniforms.displacementBias.value = originalMaterial.displacementBias; + + } + + } + + function updateOutlineMaterial( material, originalMaterial ) { + + if ( material.name === 'invisible' ) return; + + const outlineParameters = originalMaterial.userData.outlineParameters; + + material.fog = originalMaterial.fog; + material.toneMapped = originalMaterial.toneMapped; + material.premultipliedAlpha = originalMaterial.premultipliedAlpha; + material.displacementMap = originalMaterial.displacementMap; + + if ( outlineParameters !== undefined ) { + + if ( originalMaterial.visible === false ) { + + material.visible = false; + + } else { + + material.visible = ( outlineParameters.visible !== undefined ) ? outlineParameters.visible : true; + + } + + material.transparent = ( outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ) ? true : originalMaterial.transparent; + + if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive; + + } else { + + material.transparent = originalMaterial.transparent; + material.visible = originalMaterial.visible; + + } + + if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false; + + if ( originalMaterial.clippingPlanes ) { + + material.clipping = true; + + material.clippingPlanes = originalMaterial.clippingPlanes; + material.clipIntersection = originalMaterial.clipIntersection; + material.clipShadows = originalMaterial.clipShadows; + + } + + material.version = originalMaterial.version; // update outline material if necessary + + } + + function cleanupCache() { + + let keys; + + // clear originialMaterials + keys = Object.keys( originalMaterials ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + originalMaterials[ keys[ i ] ] = undefined; + + } + + // clear originalOnBeforeRenders + keys = Object.keys( originalOnBeforeRenders ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + originalOnBeforeRenders[ keys[ i ] ] = undefined; + + } + + // remove unused outlineMaterial from cache + keys = Object.keys( cache ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + const key = keys[ i ]; + + if ( cache[ key ].used === false ) { + + cache[ key ].count ++; + + if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) { + + delete cache[ key ]; + + } + + } else { + + cache[ key ].used = false; + cache[ key ].count = 0; + + } + + } + + } + + this.render = function ( scene, camera ) { + + let renderTarget; + let forceClear = false; + + if ( arguments[ 2 ] !== undefined ) { + + console.warn( 'THREE.OutlineEffect.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' ); + renderTarget = arguments[ 2 ]; + + } + + if ( arguments[ 3 ] !== undefined ) { + + console.warn( 'THREE.OutlineEffect.render(): the forceClear argument has been removed. Use .clear() instead.' ); + forceClear = arguments[ 3 ]; + + } + + if ( renderTarget !== undefined ) renderer.setRenderTarget( renderTarget ); + + if ( forceClear ) renderer.clear(); + + if ( this.enabled === false ) { + + renderer.render( scene, camera ); + return; + + } + + const currentAutoClear = renderer.autoClear; + renderer.autoClear = this.autoClear; + + renderer.render( scene, camera ); + + renderer.autoClear = currentAutoClear; + + this.renderOutline( scene, camera ); + + }; + + this.renderOutline = function ( scene, camera ) { + + const currentAutoClear = renderer.autoClear; + const currentSceneAutoUpdate = scene.autoUpdate; + const currentSceneBackground = scene.background; + const currentShadowMapEnabled = renderer.shadowMap.enabled; + + scene.autoUpdate = false; + scene.background = null; + renderer.autoClear = false; + renderer.shadowMap.enabled = false; + + scene.traverse( setOutlineMaterial ); + + renderer.render( scene, camera ); + + scene.traverse( restoreOriginalMaterial ); + + cleanupCache(); + + scene.autoUpdate = currentSceneAutoUpdate; + scene.background = currentSceneBackground; + renderer.autoClear = currentAutoClear; + renderer.shadowMap.enabled = currentShadowMapEnabled; + + }; + + /* + * See #9918 + * + * The following property copies and wrapper methods enable + * OutlineEffect to be called from other *Effect, like + * + * effect = new StereoEffect( new OutlineEffect( renderer ) ); + * + * function render () { + * + * effect.render( scene, camera ); + * + * } + */ + this.autoClear = renderer.autoClear; + this.domElement = renderer.domElement; + this.shadowMap = renderer.shadowMap; + + this.clear = function ( color, depth, stencil ) { + + renderer.clear( color, depth, stencil ); + + }; + + this.getPixelRatio = function () { + + return renderer.getPixelRatio(); + + }; + + this.setPixelRatio = function ( value ) { + + renderer.setPixelRatio( value ); + + }; + + this.getSize = function ( target ) { + + return renderer.getSize( target ); + + }; + + this.setSize = function ( width, height, updateStyle ) { + + renderer.setSize( width, height, updateStyle ); + + }; + + this.setViewport = function ( x, y, width, height ) { + + renderer.setViewport( x, y, width, height ); + + }; + + this.setScissor = function ( x, y, width, height ) { + + renderer.setScissor( x, y, width, height ); + + }; + + this.setScissorTest = function ( boolean ) { + + renderer.setScissorTest( boolean ); + + }; + + this.setRenderTarget = function ( renderTarget ) { + + renderer.setRenderTarget( renderTarget ); + + }; + + } + +} + +export { OutlineEffect }; diff --git a/jsm/effects/ParallaxBarrierEffect.js b/jsm/effects/ParallaxBarrierEffect.js new file mode 100644 index 0000000..5006793 --- /dev/null +++ b/jsm/effects/ParallaxBarrierEffect.js @@ -0,0 +1,116 @@ +import { + LinearFilter, + Mesh, + NearestFilter, + OrthographicCamera, + PlaneGeometry, + RGBAFormat, + Scene, + ShaderMaterial, + StereoCamera, + WebGLRenderTarget +} from 'three'; + +class ParallaxBarrierEffect { + + constructor( renderer ) { + + const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); + + const _scene = new Scene(); + + const _stereo = new StereoCamera(); + + const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat }; + + const _renderTargetL = new WebGLRenderTarget( 512, 512, _params ); + const _renderTargetR = new WebGLRenderTarget( 512, 512, _params ); + + const _material = new ShaderMaterial( { + + uniforms: { + + 'mapLeft': { value: _renderTargetL.texture }, + 'mapRight': { value: _renderTargetR.texture } + + }, + + vertexShader: [ + + 'varying vec2 vUv;', + + 'void main() {', + + ' vUv = vec2( uv.x, uv.y );', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}' + + ].join( '\n' ), + + fragmentShader: [ + + 'uniform sampler2D mapLeft;', + 'uniform sampler2D mapRight;', + 'varying vec2 vUv;', + + 'void main() {', + + ' vec2 uv = vUv;', + + ' if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {', + + ' gl_FragColor = texture2D( mapLeft, uv );', + + ' } else {', + + ' gl_FragColor = texture2D( mapRight, uv );', + + ' }', + + '}' + + ].join( '\n' ) + + } ); + + const mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material ); + _scene.add( mesh ); + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + const pixelRatio = renderer.getPixelRatio(); + + _renderTargetL.setSize( width * pixelRatio, height * pixelRatio ); + _renderTargetR.setSize( width * pixelRatio, height * pixelRatio ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.setRenderTarget( _renderTargetL ); + renderer.clear(); + renderer.render( scene, _stereo.cameraL ); + + renderer.setRenderTarget( _renderTargetR ); + renderer.clear(); + renderer.render( scene, _stereo.cameraR ); + + renderer.setRenderTarget( null ); + renderer.render( _scene, _camera ); + + }; + + } + +} + +export { ParallaxBarrierEffect }; diff --git a/jsm/effects/PeppersGhostEffect.js b/jsm/effects/PeppersGhostEffect.js new file mode 100644 index 0000000..5af1599 --- /dev/null +++ b/jsm/effects/PeppersGhostEffect.js @@ -0,0 +1,153 @@ +import { + PerspectiveCamera, + Quaternion, + Vector3 +} from 'three'; + +/** + * peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS + */ + +class PeppersGhostEffect { + + constructor( renderer ) { + + const scope = this; + + scope.cameraDistance = 15; + scope.reflectFromAbove = false; + + // Internals + let _halfWidth, _width, _height; + + const _cameraF = new PerspectiveCamera(); //front + const _cameraB = new PerspectiveCamera(); //back + const _cameraL = new PerspectiveCamera(); //left + const _cameraR = new PerspectiveCamera(); //right + + const _position = new Vector3(); + const _quaternion = new Quaternion(); + const _scale = new Vector3(); + + // Initialization + renderer.autoClear = false; + + this.setSize = function ( width, height ) { + + _halfWidth = width / 2; + if ( width < height ) { + + _width = width / 3; + _height = width / 3; + + } else { + + _width = height / 3; + _height = height / 3; + + } + + renderer.setSize( width, height ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + camera.matrixWorld.decompose( _position, _quaternion, _scale ); + + // front + _cameraF.position.copy( _position ); + _cameraF.quaternion.copy( _quaternion ); + _cameraF.translateZ( scope.cameraDistance ); + _cameraF.lookAt( scene.position ); + + // back + _cameraB.position.copy( _position ); + _cameraB.quaternion.copy( _quaternion ); + _cameraB.translateZ( - ( scope.cameraDistance ) ); + _cameraB.lookAt( scene.position ); + _cameraB.rotation.z += 180 * ( Math.PI / 180 ); + + // left + _cameraL.position.copy( _position ); + _cameraL.quaternion.copy( _quaternion ); + _cameraL.translateX( - ( scope.cameraDistance ) ); + _cameraL.lookAt( scene.position ); + _cameraL.rotation.x += 90 * ( Math.PI / 180 ); + + // right + _cameraR.position.copy( _position ); + _cameraR.quaternion.copy( _quaternion ); + _cameraR.translateX( scope.cameraDistance ); + _cameraR.lookAt( scene.position ); + _cameraR.rotation.x += 90 * ( Math.PI / 180 ); + + + renderer.clear(); + renderer.setScissorTest( true ); + + renderer.setScissor( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height ); + renderer.setViewport( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height ); + + if ( scope.reflectFromAbove ) { + + renderer.render( scene, _cameraB ); + + } else { + + renderer.render( scene, _cameraF ); + + } + + renderer.setScissor( _halfWidth - ( _width / 2 ), 0, _width, _height ); + renderer.setViewport( _halfWidth - ( _width / 2 ), 0, _width, _height ); + + if ( scope.reflectFromAbove ) { + + renderer.render( scene, _cameraF ); + + } else { + + renderer.render( scene, _cameraB ); + + } + + renderer.setScissor( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height ); + renderer.setViewport( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height ); + + if ( scope.reflectFromAbove ) { + + renderer.render( scene, _cameraR ); + + } else { + + renderer.render( scene, _cameraL ); + + } + + renderer.setScissor( _halfWidth + ( _width / 2 ), _height, _width, _height ); + renderer.setViewport( _halfWidth + ( _width / 2 ), _height, _width, _height ); + + if ( scope.reflectFromAbove ) { + + renderer.render( scene, _cameraL ); + + } else { + + renderer.render( scene, _cameraR ); + + } + + renderer.setScissorTest( false ); + + }; + + } + +} + +export { PeppersGhostEffect }; diff --git a/jsm/effects/StereoEffect.js b/jsm/effects/StereoEffect.js new file mode 100644 index 0000000..e4c6d92 --- /dev/null +++ b/jsm/effects/StereoEffect.js @@ -0,0 +1,55 @@ +import { + StereoCamera, + Vector2 +} from 'three'; + +class StereoEffect { + + constructor( renderer ) { + + const _stereo = new StereoCamera(); + _stereo.aspect = 0.5; + const size = new Vector2(); + + this.setEyeSeparation = function ( eyeSep ) { + + _stereo.eyeSep = eyeSep; + + }; + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.getSize( size ); + + if ( renderer.autoClear ) renderer.clear(); + renderer.setScissorTest( true ); + + renderer.setScissor( 0, 0, size.width / 2, size.height ); + renderer.setViewport( 0, 0, size.width / 2, size.height ); + renderer.render( scene, _stereo.cameraL ); + + renderer.setScissor( size.width / 2, 0, size.width / 2, size.height ); + renderer.setViewport( size.width / 2, 0, size.width / 2, size.height ); + renderer.render( scene, _stereo.cameraR ); + + renderer.setScissorTest( false ); + + }; + + } + +} + +export { StereoEffect }; diff --git a/jsm/environments/DebugEnvironment.js b/jsm/environments/DebugEnvironment.js new file mode 100644 index 0000000..ce3db06 --- /dev/null +++ b/jsm/environments/DebugEnvironment.js @@ -0,0 +1,52 @@ +import { + BackSide, + BoxGeometry, + Mesh, + MeshLambertMaterial, + MeshStandardMaterial, + PointLight, + Scene, +} from 'three'; + +class DebugEnvironment extends Scene { + + constructor() { + + super(); + + const geometry = new BoxGeometry(); + geometry.deleteAttribute( 'uv' ); + const roomMaterial = new MeshStandardMaterial( { metalness: 0, side: BackSide } ); + const room = new Mesh( geometry, roomMaterial ); + room.scale.setScalar( 10 ); + this.add( room ); + + const mainLight = new PointLight( 0xffffff, 50, 0, 2 ); + this.add( mainLight ); + + const material1 = new MeshLambertMaterial( { color: 0xff0000, emissive: 0xffffff, emissiveIntensity: 10 } ); + + const light1 = new Mesh( geometry, material1 ); + light1.position.set( - 5, 2, 0 ); + light1.scale.set( 0.1, 1, 1 ); + this.add( light1 ); + + const material2 = new MeshLambertMaterial( { color: 0x00ff00, emissive: 0xffffff, emissiveIntensity: 10 } ); + + const light2 = new Mesh( geometry, material2 ); + light2.position.set( 0, 5, 0 ); + light2.scale.set( 1, 0.1, 1 ); + this.add( light2 ); + + const material3 = new MeshLambertMaterial( { color: 0x0000ff, emissive: 0xffffff, emissiveIntensity: 10 } ); + + const light3 = new Mesh( geometry, material3 ); + light3.position.set( 2, 1, 5 ); + light3.scale.set( 1.5, 2, 0.1 ); + this.add( light3 ); + + } + +} + +export { DebugEnvironment }; diff --git a/jsm/environments/RoomEnvironment.js b/jsm/environments/RoomEnvironment.js new file mode 100644 index 0000000..eb222c1 --- /dev/null +++ b/jsm/environments/RoomEnvironment.js @@ -0,0 +1,121 @@ +/** + * https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts + */ + +import { + BackSide, + BoxGeometry, + Mesh, + MeshBasicMaterial, + MeshStandardMaterial, + PointLight, + Scene, +} from 'three'; + +class RoomEnvironment extends Scene { + + constructor() { + + super(); + + const geometry = new BoxGeometry(); + geometry.deleteAttribute( 'uv' ); + + const roomMaterial = new MeshStandardMaterial( { side: BackSide } ); + const boxMaterial = new MeshStandardMaterial(); + + const mainLight = new PointLight( 0xffffff, 5.0, 28, 2 ); + mainLight.position.set( 0.418, 16.199, 0.300 ); + this.add( mainLight ); + + const room = new Mesh( geometry, roomMaterial ); + room.position.set( - 0.757, 13.219, 0.717 ); + room.scale.set( 31.713, 28.305, 28.591 ); + this.add( room ); + + const box1 = new Mesh( geometry, boxMaterial ); + box1.position.set( - 10.906, 2.009, 1.846 ); + box1.rotation.set( 0, - 0.195, 0 ); + box1.scale.set( 2.328, 7.905, 4.651 ); + this.add( box1 ); + + const box2 = new Mesh( geometry, boxMaterial ); + box2.position.set( - 5.607, - 0.754, - 0.758 ); + box2.rotation.set( 0, 0.994, 0 ); + box2.scale.set( 1.970, 1.534, 3.955 ); + this.add( box2 ); + + const box3 = new Mesh( geometry, boxMaterial ); + box3.position.set( 6.167, 0.857, 7.803 ); + box3.rotation.set( 0, 0.561, 0 ); + box3.scale.set( 3.927, 6.285, 3.687 ); + this.add( box3 ); + + const box4 = new Mesh( geometry, boxMaterial ); + box4.position.set( - 2.017, 0.018, 6.124 ); + box4.rotation.set( 0, 0.333, 0 ); + box4.scale.set( 2.002, 4.566, 2.064 ); + this.add( box4 ); + + const box5 = new Mesh( geometry, boxMaterial ); + box5.position.set( 2.291, - 0.756, - 2.621 ); + box5.rotation.set( 0, - 0.286, 0 ); + box5.scale.set( 1.546, 1.552, 1.496 ); + this.add( box5 ); + + const box6 = new Mesh( geometry, boxMaterial ); + box6.position.set( - 2.193, - 0.369, - 5.547 ); + box6.rotation.set( 0, 0.516, 0 ); + box6.scale.set( 3.875, 3.487, 2.986 ); + this.add( box6 ); + + + // -x right + const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) ); + light1.position.set( - 16.116, 14.37, 8.208 ); + light1.scale.set( 0.1, 2.428, 2.739 ); + this.add( light1 ); + + // -x left + const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) ); + light2.position.set( - 16.109, 18.021, - 8.207 ); + light2.scale.set( 0.1, 2.425, 2.751 ); + this.add( light2 ); + + // +x + const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) ); + light3.position.set( 14.904, 12.198, - 1.832 ); + light3.scale.set( 0.15, 4.265, 6.331 ); + this.add( light3 ); + + // +z + const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) ); + light4.position.set( - 0.462, 8.89, 14.520 ); + light4.scale.set( 4.38, 5.441, 0.088 ); + this.add( light4 ); + + // -z + const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) ); + light5.position.set( 3.235, 11.486, - 12.541 ); + light5.scale.set( 2.5, 2.0, 0.1 ); + this.add( light5 ); + + // +y + const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) ); + light6.position.set( 0.0, 20.0, 0.0 ); + light6.scale.set( 1.0, 0.1, 1.0 ); + this.add( light6 ); + + } + +} + +function createAreaLightMaterial( intensity ) { + + const material = new MeshBasicMaterial(); + material.color.setScalar( intensity ); + return material; + +} + +export { RoomEnvironment }; diff --git a/jsm/exporters/ColladaExporter.js b/jsm/exporters/ColladaExporter.js new file mode 100644 index 0000000..c9cbcbf --- /dev/null +++ b/jsm/exporters/ColladaExporter.js @@ -0,0 +1,722 @@ +import { + Color, + DoubleSide, + Matrix4, + MeshBasicMaterial +} from 'three'; + +/** + * https://github.com/gkjohnson/collada-exporter-js + * + * Usage: + * const exporter = new ColladaExporter(); + * + * const data = exporter.parse(mesh); + * + * Format Definition: + * https://www.khronos.org/collada/ + */ + +class ColladaExporter { + + parse( object, onDone, options = {} ) { + + options = Object.assign( { + version: '1.4.1', + author: null, + textureDirectory: '', + upAxis: 'Y_UP', + unitName: null, + unitMeter: null, + }, options ); + + if ( options.upAxis.match( /^[XYZ]_UP$/ ) === null ) { + + console.error( 'ColladaExporter: Invalid upAxis: valid values are X_UP, Y_UP or Z_UP.' ); + return null; + + } + + if ( options.unitName !== null && options.unitMeter === null ) { + + console.error( 'ColladaExporter: unitMeter needs to be specified if unitName is specified.' ); + return null; + + } + + if ( options.unitMeter !== null && options.unitName === null ) { + + console.error( 'ColladaExporter: unitName needs to be specified if unitMeter is specified.' ); + return null; + + } + + if ( options.textureDirectory !== '' ) { + + options.textureDirectory = `${ options.textureDirectory }/` + .replace( /\\/g, '/' ) + .replace( /\/+/g, '/' ); + + } + + const version = options.version; + + if ( version !== '1.4.1' && version !== '1.5.0' ) { + + console.warn( `ColladaExporter : Version ${ version } not supported for export. Only 1.4.1 and 1.5.0.` ); + return null; + + } + + // Convert the urdf xml into a well-formatted, indented format + function format( urdf ) { + + const IS_END_TAG = /^<\//; + const IS_SELF_CLOSING = /(\?>$)|(\/>$)/; + const HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/; + + const pad = ( ch, num ) => ( num > 0 ? ch + pad( ch, num - 1 ) : '' ); + + let tagnum = 0; + + return urdf + .match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g ) + .map( tag => { + + if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && IS_END_TAG.test( tag ) ) { + + tagnum --; + + } + + const res = `${ pad( ' ', tagnum ) }${ tag }`; + + if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) { + + tagnum ++; + + } + + return res; + + } ) + .join( '\n' ); + + } + + // Convert an image into a png format for saving + function base64ToBuffer( str ) { + + const b = atob( str ); + const buf = new Uint8Array( b.length ); + + for ( let i = 0, l = buf.length; i < l; i ++ ) { + + buf[ i ] = b.charCodeAt( i ); + + } + + return buf; + + } + + let canvas, ctx; + + function imageToData( image, ext ) { + + canvas = canvas || document.createElement( 'canvas' ); + ctx = ctx || canvas.getContext( '2d' ); + + canvas.width = image.width; + canvas.height = image.height; + + ctx.drawImage( image, 0, 0 ); + + // Get the base64 encoded data + const base64data = canvas + .toDataURL( `image/${ ext }`, 1 ) + .replace( /^data:image\/(png|jpg);base64,/, '' ); + + // Convert to a uint8 array + return base64ToBuffer( base64data ); + + } + + // gets the attribute array. Generate a new array if the attribute is interleaved + const getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ]; + const tempColor = new Color(); + + function attrBufferToArray( attr, isColor = false ) { + + if ( isColor ) { + + // convert the colors to srgb before export + // colors are always written as floats + const arr = new Float32Array( attr.count * 3 ); + for ( let i = 0, l = attr.count; i < l; i ++ ) { + + tempColor + .fromBufferAttribute( attr, i ) + .convertLinearToSRGB(); + + arr[ 3 * i + 0 ] = tempColor.r; + arr[ 3 * i + 1 ] = tempColor.g; + arr[ 3 * i + 2 ] = tempColor.b; + + } + + return arr; + + } else if ( attr.isInterleavedBufferAttribute ) { + + // use the typed array constructor to save on memory + const arr = new attr.array.constructor( attr.count * attr.itemSize ); + const size = attr.itemSize; + + for ( let i = 0, l = attr.count; i < l; i ++ ) { + + for ( let j = 0; j < size; j ++ ) { + + arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i ); + + } + + } + + return arr; + + } else { + + return attr.array; + + } + + } + + // Returns an array of the same type starting at the `st` index, + // and `ct` length + function subArray( arr, st, ct ) { + + if ( Array.isArray( arr ) ) return arr.slice( st, st + ct ); + else return new arr.constructor( arr.buffer, st * arr.BYTES_PER_ELEMENT, ct ); + + } + + // Returns the string for a geometry's attribute + function getAttribute( attr, name, params, type, isColor = false ) { + + const array = attrBufferToArray( attr, isColor ); + const res = + `` + + + `` + + array.join( ' ' ) + + '' + + + '' + + `` + + + params.map( n => `` ).join( '' ) + + + '' + + '' + + ''; + + return res; + + } + + // Returns the string for a node's transform information + let transMat; + function getTransform( o ) { + + // ensure the object's matrix is up to date + // before saving the transform + o.updateMatrix(); + + transMat = transMat || new Matrix4(); + transMat.copy( o.matrix ); + transMat.transpose(); + return `${ transMat.toArray().join( ' ' ) }`; + + } + + // Process the given piece of geometry into the geometry library + // Returns the mesh id + function processGeometry( g ) { + + let info = geometryInfo.get( g ); + + if ( ! info ) { + + // convert the geometry to bufferGeometry if it isn't already + const bufferGeometry = g; + + if ( bufferGeometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.ColladaExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const meshid = `Mesh${ libraryGeometries.length + 1 }`; + + const indexCount = + bufferGeometry.index ? + bufferGeometry.index.count * bufferGeometry.index.itemSize : + bufferGeometry.attributes.position.count; + + const groups = + bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? + bufferGeometry.groups : + [ { start: 0, count: indexCount, materialIndex: 0 } ]; + + + const gname = g.name ? ` name="${ g.name }"` : ''; + let gnode = ``; + + // define the geometry node and the vertices for the geometry + const posName = `${ meshid }-position`; + const vertName = `${ meshid }-vertices`; + gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' ); + gnode += ``; + + // NOTE: We're not optimizing the attribute arrays here, so they're all the same length and + // can therefore share the same triangle indices. However, MeshLab seems to have trouble opening + // models with attributes that share an offset. + // MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/ + + // serialize normals + let triangleInputs = ``; + if ( 'normal' in bufferGeometry.attributes ) { + + const normName = `${ meshid }-normal`; + gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' ); + triangleInputs += ``; + + } + + // serialize uvs + if ( 'uv' in bufferGeometry.attributes ) { + + const uvName = `${ meshid }-texcoord`; + gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' ); + triangleInputs += ``; + + } + + // serialize lightmap uvs + if ( 'uv2' in bufferGeometry.attributes ) { + + const uvName = `${ meshid }-texcoord2`; + gnode += getAttribute( bufferGeometry.attributes.uv2, uvName, [ 'S', 'T' ], 'float' ); + triangleInputs += ``; + + } + + // serialize colors + if ( 'color' in bufferGeometry.attributes ) { + + // colors are always written as floats + const colName = `${ meshid }-color`; + gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'R', 'G', 'B' ], 'float', true ); + triangleInputs += ``; + + } + + let indexArray = null; + if ( bufferGeometry.index ) { + + indexArray = attrBufferToArray( bufferGeometry.index ); + + } else { + + indexArray = new Array( indexCount ); + for ( let i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i; + + } + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + const group = groups[ i ]; + const subarr = subArray( indexArray, group.start, group.count ); + const polycount = subarr.length / 3; + gnode += ``; + gnode += triangleInputs; + + gnode += `

${ subarr.join( ' ' ) }

`; + gnode += '
'; + + } + + gnode += '
'; + + libraryGeometries.push( gnode ); + + info = { meshid: meshid, bufferGeometry: bufferGeometry }; + geometryInfo.set( g, info ); + + } + + return info; + + } + + // Process the given texture into the image library + // Returns the image library + function processTexture( tex ) { + + let texid = imageMap.get( tex ); + if ( texid == null ) { + + texid = `image-${ libraryImages.length + 1 }`; + + const ext = 'png'; + const name = tex.name || texid; + let imageNode = ``; + + if ( version === '1.5.0' ) { + + imageNode += `${ options.textureDirectory }${ name }.${ ext }`; + + } else { + + // version image node 1.4.1 + imageNode += `${ options.textureDirectory }${ name }.${ ext }`; + + } + + imageNode += ''; + + libraryImages.push( imageNode ); + imageMap.set( tex, texid ); + textures.push( { + directory: options.textureDirectory, + name, + ext, + data: imageToData( tex.image, ext ), + original: tex + } ); + + } + + return texid; + + } + + // Process the given material into the material and effect libraries + // Returns the material id + function processMaterial( m ) { + + let matid = materialMap.get( m ); + + if ( matid == null ) { + + matid = `Mat${ libraryEffects.length + 1 }`; + + let type = 'phong'; + + if ( m.isMeshLambertMaterial === true ) { + + type = 'lambert'; + + } else if ( m.isMeshBasicMaterial === true ) { + + type = 'constant'; + + if ( m.map !== null ) { + + // The Collada spec does not support diffuse texture maps with the + // constant shader type. + // mrdoob/three.js#15469 + console.warn( 'ColladaExporter: Texture maps not supported with MeshBasicMaterial.' ); + + } + + } + + const emissive = m.emissive ? m.emissive : new Color( 0, 0, 0 ); + const diffuse = m.color ? m.color : new Color( 0, 0, 0 ); + const specular = m.specular ? m.specular : new Color( 1, 1, 1 ); + const shininess = m.shininess || 0; + const reflectivity = m.reflectivity || 0; + + emissive.convertLinearToSRGB(); + specular.convertLinearToSRGB(); + diffuse.convertLinearToSRGB(); + + // Do not export and alpha map for the reasons mentioned in issue (#13792) + // in three.js alpha maps are black and white, but collada expects the alpha + // channel to specify the transparency + let transparencyNode = ''; + if ( m.transparent === true ) { + + transparencyNode += + '' + + ( + m.map ? + '' : + '1' + ) + + ''; + + if ( m.opacity < 1 ) { + + transparencyNode += `${ m.opacity }`; + + } + + } + + const techniqueNode = `<${ type }>` + + + '' + + + ( + m.emissiveMap ? + '' : + `${ emissive.r } ${ emissive.g } ${ emissive.b } 1` + ) + + + '' + + + ( + type !== 'constant' ? + '' + + + ( + m.map ? + '' : + `${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1` + ) + + '' + : '' + ) + + + ( + type !== 'constant' ? + '' + + + ( + m.normalMap ? '' : '' + ) + + '' + : '' + ) + + + ( + type === 'phong' ? + `${ specular.r } ${ specular.g } ${ specular.b } 1` + + + '' + + + ( + m.specularMap ? + '' : + `${ shininess }` + ) + + + '' + : '' + ) + + + `${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1` + + + `${ reflectivity }` + + + transparencyNode + + + ``; + + const effectnode = + `` + + '' + + + ( + m.map ? + '' + + `${ processTexture( m.map ) }` + + '' + + 'diffuse-surface' : + '' + ) + + + ( + m.specularMap ? + '' + + `${ processTexture( m.specularMap ) }` + + '' + + 'specular-surface' : + '' + ) + + + ( + m.emissiveMap ? + '' + + `${ processTexture( m.emissiveMap ) }` + + '' + + 'emissive-surface' : + '' + ) + + + ( + m.normalMap ? + '' + + `${ processTexture( m.normalMap ) }` + + '' + + 'bump-surface' : + '' + ) + + + techniqueNode + + + ( + m.side === DoubleSide ? + '1' : + '' + ) + + + '' + + + ''; + + const materialName = m.name ? ` name="${ m.name }"` : ''; + const materialNode = ``; + + libraryMaterials.push( materialNode ); + libraryEffects.push( effectnode ); + materialMap.set( m, matid ); + + } + + return matid; + + } + + // Recursively process the object into a scene + function processObject( o ) { + + let node = ``; + + node += getTransform( o ); + + if ( o.isMesh === true && o.geometry !== null ) { + + // function returns the id associated with the mesh and a "BufferGeometry" version + // of the geometry in case it's not a geometry. + const geomInfo = processGeometry( o.geometry ); + const meshid = geomInfo.meshid; + const geometry = geomInfo.bufferGeometry; + + // ids of the materials to bind to the geometry + let matids = null; + let matidsArray; + + // get a list of materials to bind to the sub groups of the geometry. + // If the amount of subgroups is greater than the materials, than reuse + // the materials. + const mat = o.material || new MeshBasicMaterial(); + const materials = Array.isArray( mat ) ? mat : [ mat ]; + + if ( geometry.groups.length > materials.length ) { + + matidsArray = new Array( geometry.groups.length ); + + } else { + + matidsArray = new Array( materials.length ); + + } + + matids = matidsArray.fill().map( ( v, i ) => processMaterial( materials[ i % materials.length ] ) ); + + node += + `` + + + ( + matids.length > 0 ? + '' + + matids.map( ( id, i ) => + + `` + + + '' + + + '' + ).join( '' ) + + '' : + '' + ) + + + ''; + + } + + o.children.forEach( c => node += processObject( c ) ); + + node += ''; + + return node; + + } + + const geometryInfo = new WeakMap(); + const materialMap = new WeakMap(); + const imageMap = new WeakMap(); + const textures = []; + + const libraryImages = []; + const libraryGeometries = []; + const libraryEffects = []; + const libraryMaterials = []; + const libraryVisualScenes = processObject( object ); + + const specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/'; + let dae = + '' + + `` + + '' + + ( + '' + + 'three.js Collada Exporter' + + ( options.author !== null ? `${ options.author }` : '' ) + + '' + + `${ ( new Date() ).toISOString() }` + + `${ ( new Date() ).toISOString() }` + + ( options.unitName !== null ? `` : '' ) + + `${ options.upAxis }` + ) + + ''; + + dae += `${ libraryImages.join( '' ) }`; + + dae += `${ libraryEffects.join( '' ) }`; + + dae += `${ libraryMaterials.join( '' ) }`; + + dae += `${ libraryGeometries.join( '' ) }`; + + dae += `${ libraryVisualScenes }`; + + dae += ''; + + dae += ''; + + const res = { + data: format( dae ), + textures + }; + + if ( typeof onDone === 'function' ) { + + requestAnimationFrame( () => onDone( res ) ); + + } + + return res; + + } + +} + + +export { ColladaExporter }; diff --git a/jsm/exporters/DRACOExporter.js b/jsm/exporters/DRACOExporter.js new file mode 100644 index 0000000..6800d86 --- /dev/null +++ b/jsm/exporters/DRACOExporter.js @@ -0,0 +1,238 @@ +/** + * Export draco compressed files from threejs geometry objects. + * + * Draco files are compressed and usually are smaller than conventional 3D file formats. + * + * The exporter receives a options object containing + * - decodeSpeed, indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality) + * - encodeSpeed, indicates how to tune the encoder parameters (0 gives better speed but worst quality) + * - encoderMethod + * - quantization, indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC) + * - exportUvs + * - exportNormals + */ + +/* global DracoEncoderModule */ + +class DRACOExporter { + + parse( object, options = { + decodeSpeed: 5, + encodeSpeed: 5, + encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, + quantization: [ 16, 8, 8, 8, 8 ], + exportUvs: true, + exportNormals: true, + exportColor: false, + } ) { + + if ( object.isBufferGeometry === true ) { + + throw new Error( 'DRACOExporter: The first parameter of parse() is now an instance of Mesh or Points.' ); + + } + + if ( DracoEncoderModule === undefined ) { + + throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' ); + + } + + const geometry = object.geometry; + + const dracoEncoder = DracoEncoderModule(); + const encoder = new dracoEncoder.Encoder(); + let builder; + let dracoObject; + + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.DRACOExporter.parse(geometry, options): geometry is not a THREE.BufferGeometry instance.' ); + + } + + if ( object.isMesh === true ) { + + builder = new dracoEncoder.MeshBuilder(); + dracoObject = new dracoEncoder.Mesh(); + + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + + const faces = geometry.getIndex(); + + if ( faces !== null ) { + + builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array ); + + } else { + + const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); + + for ( let i = 0; i < faces.length; i ++ ) { + + faces[ i ] = i; + + } + + builder.AddFacesToMesh( dracoObject, vertices.count, faces ); + + } + + if ( options.exportNormals === true ) { + + const normals = geometry.getAttribute( 'normal' ); + + if ( normals !== undefined ) { + + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array ); + + } + + } + + if ( options.exportUvs === true ) { + + const uvs = geometry.getAttribute( 'uv' ); + + if ( uvs !== undefined ) { + + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array ); + + } + + } + + if ( options.exportColor === true ) { + + const colors = geometry.getAttribute( 'color' ); + + if ( colors !== undefined ) { + + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array ); + + } + + } + + } else if ( object.isPoints === true ) { + + builder = new dracoEncoder.PointCloudBuilder(); + dracoObject = new dracoEncoder.PointCloud(); + + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + + if ( options.exportColor === true ) { + + const colors = geometry.getAttribute( 'color' ); + + if ( colors !== undefined ) { + + builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array ); + + } + + } + + } else { + + throw new Error( 'DRACOExporter: Unsupported object type.' ); + + } + + //Compress using draco encoder + + const encodedData = new dracoEncoder.DracoInt8Array(); + + //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). + + const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; + const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; + + encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); + + // Sets the desired encoding method for a given geometry. + + if ( options.encoderMethod !== undefined ) { + + encoder.SetEncodingMethod( options.encoderMethod ); + + } + + // Sets the quantization (number of bits used to represent) compression options for a named attribute. + // The attribute values will be quantized in a box defined by the maximum extent of the attribute values. + if ( options.quantization !== undefined ) { + + for ( let i = 0; i < 5; i ++ ) { + + if ( options.quantization[ i ] !== undefined ) { + + encoder.SetAttributeQuantization( i, options.quantization[ i ] ); + + } + + } + + } + + let length; + + if ( object.isMesh === true ) { + + length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData ); + + } else { + + length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData ); + + } + + dracoEncoder.destroy( dracoObject ); + + if ( length === 0 ) { + + throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' ); + + } + + //Copy encoded data to buffer. + const outputData = new Int8Array( new ArrayBuffer( length ) ); + + for ( let i = 0; i < length; i ++ ) { + + outputData[ i ] = encodedData.GetValue( i ); + + } + + dracoEncoder.destroy( encodedData ); + dracoEncoder.destroy( encoder ); + dracoEncoder.destroy( builder ); + + return outputData; + + } + +} + +// Encoder methods + +DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; +DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; + +// Geometry type + +DRACOExporter.POINT_CLOUD = 0; +DRACOExporter.TRIANGULAR_MESH = 1; + +// Attribute type + +DRACOExporter.INVALID = - 1; +DRACOExporter.POSITION = 0; +DRACOExporter.NORMAL = 1; +DRACOExporter.COLOR = 2; +DRACOExporter.TEX_COORD = 3; +DRACOExporter.GENERIC = 4; + +export { DRACOExporter }; diff --git a/jsm/exporters/EXRExporter.js b/jsm/exporters/EXRExporter.js new file mode 100644 index 0000000..8e752b3 --- /dev/null +++ b/jsm/exporters/EXRExporter.js @@ -0,0 +1,507 @@ +/** + * @author sciecode / https://github.com/sciecode + * + * EXR format references: + * https://www.openexr.com/documentation/openexrfilelayout.pdf + */ + +import { + FloatType, + HalfFloatType, + RGBAFormat, + DataUtils, +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +const textEncoder = new TextEncoder(); + +const NO_COMPRESSION = 0; +const ZIPS_COMPRESSION = 2; +const ZIP_COMPRESSION = 3; + +class EXRExporter { + + parse( renderer, renderTarget, options ) { + + if ( ! supported( renderer, renderTarget ) ) return undefined; + + const info = buildInfo( renderTarget, options ), + dataBuffer = getPixelData( renderer, renderTarget, info ), + rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ), + chunks = compressData( rawContentBuffer, info ); + + return fillData( chunks, info ); + + } + +} + +function supported( renderer, renderTarget ) { + + if ( ! renderer || ! renderer.isWebGLRenderer ) { + + console.error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer.' ); + + return false; + + } + + if ( ! renderTarget || ! renderTarget.isWebGLRenderTarget ) { + + console.error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' ); + + return false; + + } + + if ( renderTarget.texture.type !== FloatType && renderTarget.texture.type !== HalfFloatType ) { + + console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' ); + + return false; + + } + + if ( renderTarget.texture.format !== RGBAFormat ) { + + console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' ); + + return false; + + } + + + return true; + +} + +function buildInfo( renderTarget, options = {} ) { + + const compressionSizes = { + 0: 1, + 2: 1, + 3: 16 + }; + + const WIDTH = renderTarget.width, + HEIGHT = renderTarget.height, + TYPE = renderTarget.texture.type, + FORMAT = renderTarget.texture.format, + ENCODING = renderTarget.texture.encoding, + COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION, + EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType, + OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1, + COMPRESSION_SIZE = compressionSizes[ COMPRESSION ], + NUM_CHANNELS = 4; + + return { + width: WIDTH, + height: HEIGHT, + type: TYPE, + format: FORMAT, + encoding: ENCODING, + compression: COMPRESSION, + blockLines: COMPRESSION_SIZE, + dataType: OUT_TYPE, + dataSize: 2 * OUT_TYPE, + numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ), + numInputChannels: 4, + numOutputChannels: NUM_CHANNELS, + }; + +} + +function getPixelData( renderer, rtt, info ) { + + let dataBuffer; + + if ( info.type === FloatType ) { + + dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels ); + + } else { + + dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels ); + + } + + renderer.readRenderTargetPixels( rtt, 0, 0, info.width, info.height, dataBuffer ); + + return dataBuffer; + +} + +function reorganizeDataBuffer( inBuffer, info ) { + + const w = info.width, + h = info.height, + dec = { r: 0, g: 0, b: 0, a: 0 }, + offset = { value: 0 }, + cOffset = ( info.numOutputChannels == 4 ) ? 1 : 0, + getValue = ( info.type == FloatType ) ? getFloat32 : getFloat16, + setValue = ( info.dataType == 1 ) ? setFloat16 : setFloat32, + outBuffer = new Uint8Array( info.width * info.height * info.numOutputChannels * info.dataSize ), + dv = new DataView( outBuffer.buffer ); + + for ( let y = 0; y < h; ++ y ) { + + for ( let x = 0; x < w; ++ x ) { + + const i = y * w * 4 + x * 4; + + const r = getValue( inBuffer, i ); + const g = getValue( inBuffer, i + 1 ); + const b = getValue( inBuffer, i + 2 ); + const a = getValue( inBuffer, i + 3 ); + + const line = ( h - y - 1 ) * w * ( 3 + cOffset ) * info.dataSize; + + decodeLinear( dec, r, g, b, a ); + + offset.value = line + x * info.dataSize; + setValue( dv, dec.a, offset ); + + offset.value = line + ( cOffset ) * w * info.dataSize + x * info.dataSize; + setValue( dv, dec.b, offset ); + + offset.value = line + ( 1 + cOffset ) * w * info.dataSize + x * info.dataSize; + setValue( dv, dec.g, offset ); + + offset.value = line + ( 2 + cOffset ) * w * info.dataSize + x * info.dataSize; + setValue( dv, dec.r, offset ); + + } + + } + + return outBuffer; + +} + +function compressData( inBuffer, info ) { + + let compress, + tmpBuffer, + sum = 0; + + const chunks = { data: new Array(), totalSize: 0 }, + size = info.width * info.numOutputChannels * info.blockLines * info.dataSize; + + switch ( info.compression ) { + + case 0: + compress = compressNONE; + break; + + case 2: + case 3: + compress = compressZIP; + break; + + } + + if ( info.compression !== 0 ) { + + tmpBuffer = new Uint8Array( size ); + + } + + for ( let i = 0; i < info.numBlocks; ++ i ) { + + const arr = inBuffer.subarray( size * i, size * ( i + 1 ) ); + + const block = compress( arr, tmpBuffer ); + + sum += block.length; + + chunks.data.push( { dataChunk: block, size: block.length } ); + + } + + chunks.totalSize = sum; + + return chunks; + +} + +function compressNONE( data ) { + + return data; + +} + +function compressZIP( data, tmpBuffer ) { + + // + // Reorder the pixel data. + // + + let t1 = 0, + t2 = Math.floor( ( data.length + 1 ) / 2 ), + s = 0; + + const stop = data.length - 1; + + while ( true ) { + + if ( s > stop ) break; + tmpBuffer[ t1 ++ ] = data[ s ++ ]; + + if ( s > stop ) break; + tmpBuffer[ t2 ++ ] = data[ s ++ ]; + + } + + // + // Predictor. + // + + let p = tmpBuffer[ 0 ]; + + for ( let t = 1; t < tmpBuffer.length; t ++ ) { + + const d = tmpBuffer[ t ] - p + ( 128 + 256 ); + p = tmpBuffer[ t ]; + tmpBuffer[ t ] = d; + + } + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.EXRLoader: External \`fflate.module.js\` required' ); + + } + + const deflate = fflate.zlibSync( tmpBuffer ); // eslint-disable-line no-undef + + return deflate; + +} + +function fillHeader( outBuffer, chunks, info ) { + + const offset = { value: 0 }; + const dv = new DataView( outBuffer.buffer ); + + setUint32( dv, 20000630, offset ); // magic + setUint32( dv, 2, offset ); // mask + + // = HEADER = + + setString( dv, 'compression', offset ); + setString( dv, 'compression', offset ); + setUint32( dv, 1, offset ); + setUint8( dv, info.compression, offset ); + + setString( dv, 'screenWindowCenter', offset ); + setString( dv, 'v2f', offset ); + setUint32( dv, 8, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, 0, offset ); + + setString( dv, 'screenWindowWidth', offset ); + setString( dv, 'float', offset ); + setUint32( dv, 4, offset ); + setFloat32( dv, 1.0, offset ); + + setString( dv, 'pixelAspectRatio', offset ); + setString( dv, 'float', offset ); + setUint32( dv, 4, offset ); + setFloat32( dv, 1.0, offset ); + + setString( dv, 'lineOrder', offset ); + setString( dv, 'lineOrder', offset ); + setUint32( dv, 1, offset ); + setUint8( dv, 0, offset ); + + setString( dv, 'dataWindow', offset ); + setString( dv, 'box2i', offset ); + setUint32( dv, 16, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, info.width - 1, offset ); + setUint32( dv, info.height - 1, offset ); + + setString( dv, 'displayWindow', offset ); + setString( dv, 'box2i', offset ); + setUint32( dv, 16, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, info.width - 1, offset ); + setUint32( dv, info.height - 1, offset ); + + setString( dv, 'channels', offset ); + setString( dv, 'chlist', offset ); + setUint32( dv, info.numOutputChannels * 18 + 1, offset ); + + setString( dv, 'A', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setString( dv, 'B', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setString( dv, 'G', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setString( dv, 'R', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setUint8( dv, 0, offset ); + + // null-byte + setUint8( dv, 0, offset ); + + // = OFFSET TABLE = + + let sum = offset.value + info.numBlocks * 8; + + for ( let i = 0; i < chunks.data.length; ++ i ) { + + setUint64( dv, sum, offset ); + + sum += chunks.data[ i ].size + 8; + + } + +} + +function fillData( chunks, info ) { + + const TableSize = info.numBlocks * 8, + HeaderSize = 259 + ( 18 * info.numOutputChannels ), // 259 + 18 * chlist + offset = { value: HeaderSize + TableSize }, + outBuffer = new Uint8Array( HeaderSize + TableSize + chunks.totalSize + info.numBlocks * 8 ), + dv = new DataView( outBuffer.buffer ); + + fillHeader( outBuffer, chunks, info ); + + for ( let i = 0; i < chunks.data.length; ++ i ) { + + const data = chunks.data[ i ].dataChunk; + const size = chunks.data[ i ].size; + + setUint32( dv, i * info.blockLines, offset ); + setUint32( dv, size, offset ); + + outBuffer.set( data, offset.value ); + offset.value += size; + + } + + return outBuffer; + +} + +function decodeLinear( dec, r, g, b, a ) { + + dec.r = r; + dec.g = g; + dec.b = b; + dec.a = a; + +} + +// function decodeSRGB( dec, r, g, b, a ) { + +// dec.r = r > 0.04045 ? Math.pow( r * 0.9478672986 + 0.0521327014, 2.4 ) : r * 0.0773993808; +// dec.g = g > 0.04045 ? Math.pow( g * 0.9478672986 + 0.0521327014, 2.4 ) : g * 0.0773993808; +// dec.b = b > 0.04045 ? Math.pow( b * 0.9478672986 + 0.0521327014, 2.4 ) : b * 0.0773993808; +// dec.a = a; + +// } + + +function setUint8( dv, value, offset ) { + + dv.setUint8( offset.value, value ); + + offset.value += 1; + +} + +function setUint32( dv, value, offset ) { + + dv.setUint32( offset.value, value, true ); + + offset.value += 4; + +} + +function setFloat16( dv, value, offset ) { + + dv.setUint16( offset.value, DataUtils.toHalfFloat( value ), true ); + + offset.value += 2; + +} + +function setFloat32( dv, value, offset ) { + + dv.setFloat32( offset.value, value, true ); + + offset.value += 4; + +} + +function setUint64( dv, value, offset ) { + + dv.setBigUint64( offset.value, BigInt( value ), true ); + + offset.value += 8; + +} + +function setString( dv, string, offset ) { + + const tmp = textEncoder.encode( string + '\0' ); + + for ( let i = 0; i < tmp.length; ++ i ) { + + setUint8( dv, tmp[ i ], offset ); + + } + +} + +function decodeFloat16( binary ) { + + const exponent = ( binary & 0x7C00 ) >> 10, + fraction = binary & 0x03FF; + + return ( binary >> 15 ? - 1 : 1 ) * ( + exponent ? + ( + exponent === 0x1F ? + fraction ? NaN : Infinity : + Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 ) + ) : + 6.103515625e-5 * ( fraction / 0x400 ) + ); + +} + +function getFloat16( arr, i ) { + + return decodeFloat16( arr[ i ] ); + +} + +function getFloat32( arr, i ) { + + return arr[ i ]; + +} + +export { EXRExporter, NO_COMPRESSION, ZIP_COMPRESSION, ZIPS_COMPRESSION }; diff --git a/jsm/exporters/GLTFExporter.js b/jsm/exporters/GLTFExporter.js new file mode 100644 index 0000000..eb93845 --- /dev/null +++ b/jsm/exporters/GLTFExporter.js @@ -0,0 +1,2647 @@ +import { + BufferAttribute, + ClampToEdgeWrapping, + DoubleSide, + InterpolateDiscrete, + InterpolateLinear, + LinearFilter, + LinearMipmapLinearFilter, + LinearMipmapNearestFilter, + MathUtils, + Matrix4, + MirroredRepeatWrapping, + NearestFilter, + NearestMipmapLinearFilter, + NearestMipmapNearestFilter, + PropertyBinding, + RGBAFormat, + RepeatWrapping, + Scene, + Source, + Vector3 +} from 'three'; + + +class GLTFExporter { + + constructor() { + + this.pluginCallbacks = []; + + this.register( function ( writer ) { + + return new GLTFLightExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsUnlitExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsPBRSpecularGlossiness( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsTransmissionExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsVolumeExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsClearcoatExtension( writer ); + + } ); + + } + + register( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + + this.pluginCallbacks.push( callback ); + + } + + return this; + + } + + unregister( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + + } + + return this; + + } + + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Function} onError Callback on errors + * @param {Object} options options + */ + parse( input, onDone, onError, options ) { + + if ( typeof onError === 'object' ) { + + console.warn( 'THREE.GLTFExporter: parse() expects options as the fourth argument now.' ); + + options = onError; + + } + + const writer = new GLTFWriter(); + const plugins = []; + + for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { + + plugins.push( this.pluginCallbacks[ i ]( writer ) ); + + } + + writer.setPlugins( plugins ); + writer.write( input, onDone, options ).catch( onError ); + + } + + parseAsync( input, options ) { + + const scope = this; + + return new Promise( function ( resolve, reject ) { + + scope.parse( input, resolve, reject, options ); + + } ); + + } + +} + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const WEBGL_CONSTANTS = { + POINTS: 0x0000, + LINES: 0x0001, + LINE_LOOP: 0x0002, + LINE_STRIP: 0x0003, + TRIANGLES: 0x0004, + TRIANGLE_STRIP: 0x0005, + TRIANGLE_FAN: 0x0006, + + UNSIGNED_BYTE: 0x1401, + UNSIGNED_SHORT: 0x1403, + FLOAT: 0x1406, + UNSIGNED_INT: 0x1405, + ARRAY_BUFFER: 0x8892, + ELEMENT_ARRAY_BUFFER: 0x8893, + + NEAREST: 0x2600, + LINEAR: 0x2601, + NEAREST_MIPMAP_NEAREST: 0x2700, + LINEAR_MIPMAP_NEAREST: 0x2701, + NEAREST_MIPMAP_LINEAR: 0x2702, + LINEAR_MIPMAP_LINEAR: 0x2703, + + CLAMP_TO_EDGE: 33071, + MIRRORED_REPEAT: 33648, + REPEAT: 10497 +}; + +const THREE_TO_WEBGL = {}; + +THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; +THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; +THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; +THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; +THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; +THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; + +THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; +THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; +THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; + +const PATH_PROPERTIES = { + scale: 'scale', + position: 'translation', + quaternion: 'rotation', + morphTargetInfluences: 'weights' +}; + +// GLB constants +// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + +const GLB_HEADER_BYTES = 12; +const GLB_HEADER_MAGIC = 0x46546C67; +const GLB_VERSION = 2; + +const GLB_CHUNK_PREFIX_BYTES = 8; +const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; +const GLB_CHUNK_TYPE_BIN = 0x004E4942; + +//------------------------------------------------------------------------------ +// Utility functions +//------------------------------------------------------------------------------ + +/** + * Compare two arrays + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {Boolean} Returns true if both arrays are equal + */ +function equalArray( array1, array2 ) { + + return ( array1.length === array2.length ) && array1.every( function ( element, index ) { + + return element === array2[ index ]; + + } ); + +} + +/** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ +function stringToArrayBuffer( text ) { + + if ( window.TextEncoder !== undefined ) { + + return new TextEncoder().encode( text ).buffer; + + } + + const array = new Uint8Array( new ArrayBuffer( text.length ) ); + + for ( let i = 0, il = text.length; i < il; i ++ ) { + + const value = text.charCodeAt( i ); + + // Replacing multi-byte character with space(0x20). + array[ i ] = value > 0xFF ? 0x20 : value; + + } + + return array.buffer; + +} + +/** + * Is identity matrix + * + * @param {Matrix4} matrix + * @returns {Boolean} Returns true, if parameter is identity matrix + */ +function isIdentityMatrix( matrix ) { + + return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); + +} + +/** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ +function getMinMax( attribute, start, count ) { + + const output = { + + min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), + max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) + + }; + + for ( let i = start; i < start + count; i ++ ) { + + for ( let a = 0; a < attribute.itemSize; a ++ ) { + + let value; + + if ( attribute.itemSize > 4 ) { + + // no support for interleaved data for itemSize > 4 + + value = attribute.array[ i * attribute.itemSize + a ]; + + } else { + + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); + + } + + output.min[ a ] = Math.min( output.min[ a ], value ); + output.max[ a ] = Math.max( output.max[ a ], value ); + + } + + } + + return output; + +} + +/** + * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ +function getPaddedBufferSize( bufferSize ) { + + return Math.ceil( bufferSize / 4 ) * 4; + +} + +/** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ +function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { + + const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); + + if ( paddedLength !== arrayBuffer.byteLength ) { + + const array = new Uint8Array( paddedLength ); + array.set( new Uint8Array( arrayBuffer ) ); + + if ( paddingByte !== 0 ) { + + for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + + array[ i ] = paddingByte; + + } + + } + + return array.buffer; + + } + + return arrayBuffer; + +} + +let cachedCanvas = null; + +/** + * Writer + */ +class GLTFWriter { + + constructor() { + + this.plugins = []; + + this.options = {}; + this.pending = []; + this.buffers = []; + + this.byteOffset = 0; + this.buffers = []; + this.nodeMap = new Map(); + this.skins = []; + this.extensionsUsed = {}; + + this.uids = new Map(); + this.uid = 0; + + this.json = { + asset: { + version: '2.0', + generator: 'THREE.GLTFExporter' + } + }; + + this.cache = { + meshes: new Map(), + attributes: new Map(), + attributesNormalized: new Map(), + materials: new Map(), + textures: new Map(), + images: new Map() + }; + + } + + setPlugins( plugins ) { + + this.plugins = plugins; + + } + + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ + async write( input, onDone, options ) { + + this.options = Object.assign( {}, { + // default options + binary: false, + trs: false, + onlyVisible: true, + truncateDrawRange: true, + embedImages: true, + maxTextureSize: Infinity, + animations: [], + includeCustomExtensions: false + }, options ); + + if ( this.options.animations.length > 0 ) { + + // Only TRS properties, and not matrices, may be targeted by animation. + this.options.trs = true; + + } + + this.processInput( input ); + + await Promise.all( this.pending ); + + const writer = this; + const buffers = writer.buffers; + const json = writer.json; + options = writer.options; + const extensionsUsed = writer.extensionsUsed; + + // Merge buffers. + const blob = new Blob( buffers, { type: 'application/octet-stream' } ); + + // Declare extensions. + const extensionsUsedList = Object.keys( extensionsUsed ); + + if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; + + // Update bytelength of the single buffer. + if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + + if ( options.binary === true ) { + + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + + const reader = new window.FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { + + // Binary chunk. + const binaryChunk = getPaddedArrayBuffer( reader.result ); + const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); + binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); + + // JSON chunk. + const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); + const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); + jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); + + // GLB header. + const header = new ArrayBuffer( GLB_HEADER_BYTES ); + const headerView = new DataView( header ); + headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); + headerView.setUint32( 4, GLB_VERSION, true ); + const totalByteLength = GLB_HEADER_BYTES + + jsonChunkPrefix.byteLength + jsonChunk.byteLength + + binaryChunkPrefix.byteLength + binaryChunk.byteLength; + headerView.setUint32( 8, totalByteLength, true ); + + const glbBlob = new Blob( [ + header, + jsonChunkPrefix, + jsonChunk, + binaryChunkPrefix, + binaryChunk + ], { type: 'application/octet-stream' } ); + + const glbReader = new window.FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + glbReader.onloadend = function () { + + onDone( glbReader.result ); + + }; + + }; + + } else { + + if ( json.buffers && json.buffers.length > 0 ) { + + const reader = new window.FileReader(); + reader.readAsDataURL( blob ); + reader.onloadend = function () { + + const base64data = reader.result; + json.buffers[ 0 ].uri = base64data; + onDone( json ); + + }; + + } else { + + onDone( json ); + + } + + } + + + } + + /** + * Serializes a userData. + * + * @param {THREE.Object3D|THREE.Material} object + * @param {Object} objectDef + */ + serializeUserData( object, objectDef ) { + + if ( Object.keys( object.userData ).length === 0 ) return; + + const options = this.options; + const extensionsUsed = this.extensionsUsed; + + try { + + const json = JSON.parse( JSON.stringify( object.userData ) ); + + if ( options.includeCustomExtensions && json.gltfExtensions ) { + + if ( objectDef.extensions === undefined ) objectDef.extensions = {}; + + for ( const extensionName in json.gltfExtensions ) { + + objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; + extensionsUsed[ extensionName ] = true; + + } + + delete json.gltfExtensions; + + } + + if ( Object.keys( json ).length > 0 ) objectDef.extras = json; + + } catch ( error ) { + + console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + + } + + } + + /** + * Assign and return a temporal unique id for an object + * especially which doesn't have .uuid + * @param {Object} object + * @return {Integer} + */ + getUID( object ) { + + if ( ! this.uids.has( object ) ) this.uids.set( object, this.uid ++ ); + + return this.uids.get( object ); + + } + + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + */ + isNormalizedNormalAttribute( normal ) { + + const cache = this.cache; + + if ( cache.attributesNormalized.has( normal ) ) return false; + + const v = new Vector3(); + + for ( let i = 0, il = normal.count; i < il; i ++ ) { + + // 0.0005 is from glTF-validator + if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; + + } + + return true; + + } + + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ + createNormalizedNormalAttribute( normal ) { + + const cache = this.cache; + + if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); + + const attribute = normal.clone(); + const v = new Vector3(); + + for ( let i = 0, il = attribute.count; i < il; i ++ ) { + + v.fromBufferAttribute( attribute, i ); + + if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + + // if values can't be normalized set (1, 0, 0) + v.setX( 1.0 ); + + } else { + + v.normalize(); + + } + + attribute.setXYZ( i, v.x, v.y, v.z ); + + } + + cache.attributesNormalized.set( normal, attribute ); + + return attribute; + + } + + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + * + * @param {Object} mapDef + * @param {THREE.Texture} texture + */ + applyTextureTransform( mapDef, texture ) { + + let didTransform = false; + const transformDef = {}; + + if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { + + transformDef.offset = texture.offset.toArray(); + didTransform = true; + + } + + if ( texture.rotation !== 0 ) { + + transformDef.rotation = texture.rotation; + didTransform = true; + + } + + if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { + + transformDef.scale = texture.repeat.toArray(); + didTransform = true; + + } + + if ( didTransform ) { + + mapDef.extensions = mapDef.extensions || {}; + mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; + this.extensionsUsed[ 'KHR_texture_transform' ] = true; + + } + + } + + buildMetalRoughTexture( metalnessMap, roughnessMap ) { + + if ( metalnessMap === roughnessMap ) return metalnessMap; + + console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); + + const metalness = metalnessMap?.image; + const roughness = roughnessMap?.image; + + const width = Math.max( metalness?.width || 0, roughness?.width || 0 ); + const height = Math.max( metalness?.height || 0, roughness?.height || 0 ); + + const canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext( '2d' ); + context.fillStyle = '#00ffff'; + context.fillRect( 0, 0, width, height ); + + const composite = context.getImageData( 0, 0, width, height ); + + if ( metalness ) { + + context.drawImage( metalness, 0, 0, width, height ); + + const data = context.getImageData( 0, 0, width, height ).data; + + for ( let i = 2; i < data.length; i += 4 ) { + + composite.data[ i ] = data[ i ]; + + } + + } + + if ( roughness ) { + + context.drawImage( roughness, 0, 0, width, height ); + + const data = context.getImageData( 0, 0, width, height ).data; + + for ( let i = 1; i < data.length; i += 4 ) { + + composite.data[ i ] = data[ i ]; + + } + + } + + context.putImageData( composite, 0, 0 ); + + // + + const reference = metalnessMap || roughnessMap; + + const texture = reference.clone(); + + texture.source = new Source( canvas ); + + return texture; + + } + + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ + processBuffer( buffer ) { + + const json = this.json; + const buffers = this.buffers; + + if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; + + // All buffers are merged before export. + buffers.push( buffer ); + + return 0; + + } + + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ + processBufferView( attribute, componentType, start, count, target ) { + + const json = this.json; + + if ( ! json.bufferViews ) json.bufferViews = []; + + // Create a new dataview and dump the attribute's array into it + + let componentSize; + + if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + + componentSize = 1; + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + + componentSize = 2; + + } else { + + componentSize = 4; + + } + + const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + const dataView = new DataView( new ArrayBuffer( byteLength ) ); + let offset = 0; + + for ( let i = start; i < start + count; i ++ ) { + + for ( let a = 0; a < attribute.itemSize; a ++ ) { + + let value; + + if ( attribute.itemSize > 4 ) { + + // no support for interleaved data for itemSize > 4 + + value = attribute.array[ i * attribute.itemSize + a ]; + + } else { + + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); + + } + + if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + + dataView.setFloat32( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + + dataView.setUint32( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + + dataView.setUint16( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + + dataView.setUint8( offset, value ); + + } + + offset += componentSize; + + } + + } + + const bufferViewDef = { + + buffer: this.processBuffer( dataView.buffer ), + byteOffset: this.byteOffset, + byteLength: byteLength + + }; + + if ( target !== undefined ) bufferViewDef.target = target; + + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + + // Only define byteStride for vertex attributes. + bufferViewDef.byteStride = attribute.itemSize * componentSize; + + } + + this.byteOffset += byteLength; + + json.bufferViews.push( bufferViewDef ); + + // @TODO Merge bufferViews where possible. + const output = { + + id: json.bufferViews.length - 1, + byteLength: 0 + + }; + + return output; + + } + + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ + processBufferViewImage( blob ) { + + const writer = this; + const json = writer.json; + + if ( ! json.bufferViews ) json.bufferViews = []; + + return new Promise( function ( resolve ) { + + const reader = new window.FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { + + const buffer = getPaddedArrayBuffer( reader.result ); + + const bufferViewDef = { + buffer: writer.processBuffer( buffer ), + byteOffset: writer.byteOffset, + byteLength: buffer.byteLength + }; + + writer.byteOffset += buffer.byteLength; + resolve( json.bufferViews.push( bufferViewDef ) - 1 ); + + }; + + } ); + + } + + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer|null} Index of the processed accessor on the "accessors" array + */ + processAccessor( attribute, geometry, start, count ) { + + const options = this.options; + const json = this.json; + + const types = { + + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 16: 'MAT4' + + }; + + let componentType; + + // Detect the component type of the attribute array (float, uint or ushort) + if ( attribute.array.constructor === Float32Array ) { + + componentType = WEBGL_CONSTANTS.FLOAT; + + } else if ( attribute.array.constructor === Uint32Array ) { + + componentType = WEBGL_CONSTANTS.UNSIGNED_INT; + + } else if ( attribute.array.constructor === Uint16Array ) { + + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; + + } else if ( attribute.array.constructor === Uint8Array ) { + + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + + } else { + + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); + + } + + if ( start === undefined ) start = 0; + if ( count === undefined ) count = attribute.count; + + // @TODO Indexed buffer geometry with drawRange not supported yet + if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) { + + const end = start + count; + const end2 = geometry.drawRange.count === Infinity + ? attribute.count + : geometry.drawRange.start + geometry.drawRange.count; + + start = Math.max( start, geometry.drawRange.start ); + count = Math.min( end, end2 ) - start; + + if ( count < 0 ) count = 0; + + } + + // Skip creating an accessor if the attribute doesn't have data to export + if ( count === 0 ) return null; + + const minMax = getMinMax( attribute, start, count ); + let bufferViewTarget; + + // If geometry isn't provided, don't infer the target usage of the bufferView. For + // animation samplers, target must not be set. + if ( geometry !== undefined ) { + + bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + + } + + const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + + const accessorDef = { + + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[ attribute.itemSize ] + + }; + + if ( attribute.normalized === true ) accessorDef.normalized = true; + if ( ! json.accessors ) json.accessors = []; + + return json.accessors.push( accessorDef ) - 1; + + } + + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (RGBAFormat) + * @param {Boolean} flipY before writing out the image + * @param {String} mimeType export format + * @return {Integer} Index of the processed texture in the "images" array + */ + processImage( image, format, flipY, mimeType = 'image/png' ) { + + const writer = this; + const cache = writer.cache; + const json = writer.json; + const options = writer.options; + const pending = writer.pending; + + if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); + + const cachedImages = cache.images.get( image ); + + const key = mimeType + ':flipY/' + flipY.toString(); + + if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; + + if ( ! json.images ) json.images = []; + + const imageDef = { mimeType: mimeType }; + + if ( options.embedImages ) { + + const canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); + + canvas.width = Math.min( image.width, options.maxTextureSize ); + canvas.height = Math.min( image.height, options.maxTextureSize ); + + const ctx = canvas.getContext( '2d' ); + + if ( flipY === true ) { + + ctx.translate( 0, canvas.height ); + ctx.scale( 1, - 1 ); + + } + + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + + } else { + + if ( format !== RGBAFormat ) { + + console.error( 'GLTFExporter: Only RGBAFormat is supported.' ); + + } + + if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { + + console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); + + } + + const data = new Uint8ClampedArray( image.height * image.width * 4 ); + + for ( let i = 0; i < data.length; i += 4 ) { + + data[ i + 0 ] = image.data[ i + 0 ]; + data[ i + 1 ] = image.data[ i + 1 ]; + data[ i + 2 ] = image.data[ i + 2 ]; + data[ i + 3 ] = image.data[ i + 3 ]; + + } + + ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); + + } + + if ( options.binary === true ) { + + pending.push( new Promise( function ( resolve ) { + + canvas.toBlob( function ( blob ) { + + writer.processBufferViewImage( blob ).then( function ( bufferViewIndex ) { + + imageDef.bufferView = bufferViewIndex; + resolve(); + + } ); + + }, mimeType ); + + } ) ); + + } else { + + imageDef.uri = canvas.toDataURL( mimeType ); + + } + + } else { + + imageDef.uri = image.src; + + } + + const index = json.images.push( imageDef ) - 1; + cachedImages[ key ] = index; + return index; + + } + + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ + processSampler( map ) { + + const json = this.json; + + if ( ! json.samplers ) json.samplers = []; + + const samplerDef = { + magFilter: THREE_TO_WEBGL[ map.magFilter ], + minFilter: THREE_TO_WEBGL[ map.minFilter ], + wrapS: THREE_TO_WEBGL[ map.wrapS ], + wrapT: THREE_TO_WEBGL[ map.wrapT ] + }; + + return json.samplers.push( samplerDef ) - 1; + + } + + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ + processTexture( map ) { + + const cache = this.cache; + const json = this.json; + + if ( cache.textures.has( map ) ) return cache.textures.get( map ); + + if ( ! json.textures ) json.textures = []; + + let mimeType = map.userData.mimeType; + + if ( mimeType === 'image/webp' ) mimeType = 'image/png'; + + const textureDef = { + sampler: this.processSampler( map ), + source: this.processImage( map.image, map.format, map.flipY, mimeType ) + }; + + if ( map.name ) textureDef.name = map.name; + + this._invokeAll( function ( ext ) { + + ext.writeTexture && ext.writeTexture( map, textureDef ); + + } ); + + const index = json.textures.push( textureDef ) - 1; + cache.textures.set( map, index ); + return index; + + } + + /** + * Process material + * @param {THREE.Material} material Material to process + * @return {Integer|null} Index of the processed material in the "materials" array + */ + processMaterial( material ) { + + const cache = this.cache; + const json = this.json; + + if ( cache.materials.has( material ) ) return cache.materials.get( material ); + + if ( material.isShaderMaterial ) { + + console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); + return null; + + } + + if ( ! json.materials ) json.materials = []; + + // @QUESTION Should we avoid including any attribute that has the default value? + const materialDef = { pbrMetallicRoughness: {} }; + + if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { + + console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); + + } + + // pbrMetallicRoughness.baseColorFactor + const color = material.color.toArray().concat( [ material.opacity ] ); + + if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { + + materialDef.pbrMetallicRoughness.baseColorFactor = color; + + } + + if ( material.isMeshStandardMaterial ) { + + materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; + materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; + + } else { + + materialDef.pbrMetallicRoughness.metallicFactor = 0.5; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; + + } + + // pbrMetallicRoughness.metallicRoughnessTexture + if ( material.metalnessMap || material.roughnessMap ) { + + const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); + + const metalRoughMapDef = { index: this.processTexture( metalRoughTexture ) }; + this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + + } + + // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture + if ( material.map ) { + + const baseColorMapDef = { index: this.processTexture( material.map ) }; + this.applyTextureTransform( baseColorMapDef, material.map ); + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + + } + + if ( material.emissive ) { + + // note: emissive components are limited to stay within the 0 - 1 range to accommodate glTF spec. see #21849 and #22000. + const emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ); + const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); + + if ( maxEmissiveComponent > 1 ) { + + emissive.multiplyScalar( 1 / maxEmissiveComponent ); + + console.warn( 'THREE.GLTFExporter: Some emissive components exceed 1; emissive has been limited' ); + + } + + if ( maxEmissiveComponent > 0 ) { + + materialDef.emissiveFactor = emissive.toArray(); + + } + + // emissiveTexture + if ( material.emissiveMap ) { + + const emissiveMapDef = { index: this.processTexture( material.emissiveMap ) }; + this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); + materialDef.emissiveTexture = emissiveMapDef; + + } + + } + + // normalTexture + if ( material.normalMap ) { + + const normalMapDef = { index: this.processTexture( material.normalMap ) }; + + if ( material.normalScale && material.normalScale.x !== 1 ) { + + // glTF normal scale is univariate. Ignore `y`, which may be flipped. + // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + normalMapDef.scale = material.normalScale.x; + + } + + this.applyTextureTransform( normalMapDef, material.normalMap ); + materialDef.normalTexture = normalMapDef; + + } + + // occlusionTexture + if ( material.aoMap ) { + + const occlusionMapDef = { + index: this.processTexture( material.aoMap ), + texCoord: 1 + }; + + if ( material.aoMapIntensity !== 1.0 ) { + + occlusionMapDef.strength = material.aoMapIntensity; + + } + + this.applyTextureTransform( occlusionMapDef, material.aoMap ); + materialDef.occlusionTexture = occlusionMapDef; + + } + + // alphaMode + if ( material.transparent ) { + + materialDef.alphaMode = 'BLEND'; + + } else { + + if ( material.alphaTest > 0.0 ) { + + materialDef.alphaMode = 'MASK'; + materialDef.alphaCutoff = material.alphaTest; + + } + + } + + // doubleSided + if ( material.side === DoubleSide ) materialDef.doubleSided = true; + if ( material.name !== '' ) materialDef.name = material.name; + + this.serializeUserData( material, materialDef ); + + this._invokeAll( function ( ext ) { + + ext.writeMaterial && ext.writeMaterial( material, materialDef ); + + } ); + + const index = json.materials.push( materialDef ) - 1; + cache.materials.set( material, index ); + return index; + + } + + /** + * Process mesh + * @param {THREE.Mesh} mesh Mesh to process + * @return {Integer|null} Index of the processed mesh in the "meshes" array + */ + processMesh( mesh ) { + + const cache = this.cache; + const json = this.json; + + const meshCacheKeyParts = [ mesh.geometry.uuid ]; + + if ( Array.isArray( mesh.material ) ) { + + for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { + + meshCacheKeyParts.push( mesh.material[ i ].uuid ); + + } + + } else { + + meshCacheKeyParts.push( mesh.material.uuid ); + + } + + const meshCacheKey = meshCacheKeyParts.join( ':' ); + + if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); + + const geometry = mesh.geometry; + let mode; + + // Use the correct mode + if ( mesh.isLineSegments ) { + + mode = WEBGL_CONSTANTS.LINES; + + } else if ( mesh.isLineLoop ) { + + mode = WEBGL_CONSTANTS.LINE_LOOP; + + } else if ( mesh.isLine ) { + + mode = WEBGL_CONSTANTS.LINE_STRIP; + + } else if ( mesh.isPoints ) { + + mode = WEBGL_CONSTANTS.POINTS; + + } else { + + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + + } + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const meshDef = {}; + const attributes = {}; + const primitives = []; + const targets = []; + + // Conversion between attributes names in threejs and gltf spec + const nameConversion = { + uv: 'TEXCOORD_0', + uv2: 'TEXCOORD_1', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0' + }; + + const originalNormal = geometry.getAttribute( 'normal' ); + + if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { + + console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + + geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); + + } + + // @QUESTION Detect if .vertexColors = true? + // For every attribute create an accessor + let modifiedAttribute = null; + + for ( let attributeName in geometry.attributes ) { + + // Ignore morph target attributes, which are exported later. + if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; + + const attribute = geometry.attributes[ attributeName ]; + attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); + + // Prefix all geometry attributes except the ones specifically + // listed in the spec; non-spec attributes are considered custom. + const validVertexAttributes = + /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; + + if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + + if ( cache.attributes.has( this.getUID( attribute ) ) ) { + + attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; + + } + + // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + modifiedAttribute = null; + const array = attribute.array; + + if ( attributeName === 'JOINTS_0' && + ! ( array instanceof Uint16Array ) && + ! ( array instanceof Uint8Array ) ) { + + console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); + modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + + } + + const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + + if ( accessor !== null ) { + + attributes[ attributeName ] = accessor; + cache.attributes.set( this.getUID( attribute ), accessor ); + + } + + } + + if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); + + // Skip if no exportable attributes found + if ( Object.keys( attributes ).length === 0 ) return null; + + // Morph targets + if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + + const weights = []; + const targetNames = []; + const reverseDictionary = {}; + + if ( mesh.morphTargetDictionary !== undefined ) { + + for ( const key in mesh.morphTargetDictionary ) { + + reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + + } + + } + + for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { + + const target = {}; + let warned = false; + + for ( const attributeName in geometry.morphAttributes ) { + + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. + + if ( attributeName !== 'position' && attributeName !== 'normal' ) { + + if ( ! warned ) { + + console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); + warned = true; + + } + + continue; + + } + + const attribute = geometry.morphAttributes[ attributeName ][ i ]; + const gltfAttributeName = attributeName.toUpperCase(); + + // Three.js morph attribute has absolute values while the one of glTF has relative values. + // + // glTF 2.0 Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets + + const baseAttribute = geometry.attributes[ attributeName ]; + + if ( cache.attributes.has( this.getUID( attribute ) ) ) { + + target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; + + } + + // Clones attribute not to override + const relativeAttribute = attribute.clone(); + + if ( ! geometry.morphTargetsRelative ) { + + for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { + + relativeAttribute.setXYZ( + j, + attribute.getX( j ) - baseAttribute.getX( j ), + attribute.getY( j ) - baseAttribute.getY( j ), + attribute.getZ( j ) - baseAttribute.getZ( j ) + ); + + } + + } + + target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); + cache.attributes.set( this.getUID( baseAttribute ), target[ gltfAttributeName ] ); + + } + + targets.push( target ); + + weights.push( mesh.morphTargetInfluences[ i ] ); + + if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); + + } + + meshDef.weights = weights; + + if ( targetNames.length > 0 ) { + + meshDef.extras = {}; + meshDef.extras.targetNames = targetNames; + + } + + } + + const isMultiMaterial = Array.isArray( mesh.material ); + + if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + + const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; + const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; + + for ( let i = 0, il = groups.length; i < il; i ++ ) { + + const primitive = { + mode: mode, + attributes: attributes, + }; + + this.serializeUserData( geometry, primitive ); + + if ( targets.length > 0 ) primitive.targets = targets; + + if ( geometry.index !== null ) { + + let cacheKey = this.getUID( geometry.index ); + + if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { + + cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; + + } + + if ( cache.attributes.has( cacheKey ) ) { + + primitive.indices = cache.attributes.get( cacheKey ); + + } else { + + primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); + cache.attributes.set( cacheKey, primitive.indices ); + + } + + if ( primitive.indices === null ) delete primitive.indices; + + } + + const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); + + if ( material !== null ) primitive.material = material; + + primitives.push( primitive ); + + } + + meshDef.primitives = primitives; + + if ( ! json.meshes ) json.meshes = []; + + this._invokeAll( function ( ext ) { + + ext.writeMesh && ext.writeMesh( mesh, meshDef ); + + } ); + + const index = json.meshes.push( meshDef ) - 1; + cache.meshes.set( meshCacheKey, index ); + return index; + + } + + /** + * Process camera + * @param {THREE.Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ + processCamera( camera ) { + + const json = this.json; + + if ( ! json.cameras ) json.cameras = []; + + const isOrtho = camera.isOrthographicCamera; + + const cameraDef = { + type: isOrtho ? 'orthographic' : 'perspective' + }; + + if ( isOrtho ) { + + cameraDef.orthographic = { + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; + + } else { + + cameraDef.perspective = { + aspectRatio: camera.aspect, + yfov: MathUtils.degToRad( camera.fov ), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; + + } + + // Question: Is saving "type" as name intentional? + if ( camera.name !== '' ) cameraDef.name = camera.type; + + return json.cameras.push( cameraDef ) - 1; + + } + + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {THREE.AnimationClip} clip + * @param {THREE.Object3D} root + * @return {number|null} + */ + processAnimation( clip, root ) { + + const json = this.json; + const nodeMap = this.nodeMap; + + if ( ! json.animations ) json.animations = []; + + clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); + + const tracks = clip.tracks; + const channels = []; + const samplers = []; + + for ( let i = 0; i < tracks.length; ++ i ) { + + const track = tracks[ i ]; + const trackBinding = PropertyBinding.parseTrackName( track.name ); + let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); + const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; + + if ( trackBinding.objectName === 'bones' ) { + + if ( trackNode.isSkinnedMesh === true ) { + + trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); + + } else { + + trackNode = undefined; + + } + + } + + if ( ! trackNode || ! trackProperty ) { + + console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); + return null; + + } + + const inputItemSize = 1; + let outputItemSize = track.values.length / track.times.length; + + if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { + + outputItemSize /= trackNode.morphTargetInfluences.length; + + } + + let interpolation; + + // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + + // Detecting glTF cubic spline interpolant by checking factory method's special property + // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return + // valid value from .getInterpolation(). + if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { + + interpolation = 'CUBICSPLINE'; + + // itemSize of CUBICSPLINE keyframe is 9 + // (VEC3 * 3: inTangent, splineVertex, and outTangent) + // but needs to be stored as VEC3 so dividing by 3 here. + outputItemSize /= 3; + + } else if ( track.getInterpolation() === InterpolateDiscrete ) { + + interpolation = 'STEP'; + + } else { + + interpolation = 'LINEAR'; + + } + + samplers.push( { + input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), + output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), + interpolation: interpolation + } ); + + channels.push( { + sampler: samplers.length - 1, + target: { + node: nodeMap.get( trackNode ), + path: trackProperty + } + } ); + + } + + json.animations.push( { + name: clip.name || 'clip_' + json.animations.length, + samplers: samplers, + channels: channels + } ); + + return json.animations.length - 1; + + } + + /** + * @param {THREE.Object3D} object + * @return {number|null} + */ + processSkin( object ) { + + const json = this.json; + const nodeMap = this.nodeMap; + + const node = json.nodes[ nodeMap.get( object ) ]; + + const skeleton = object.skeleton; + + if ( skeleton === undefined ) return null; + + const rootJoint = object.skeleton.bones[ 0 ]; + + if ( rootJoint === undefined ) return null; + + const joints = []; + const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); + const temporaryBoneInverse = new Matrix4(); + + for ( let i = 0; i < skeleton.bones.length; ++ i ) { + + joints.push( nodeMap.get( skeleton.bones[ i ] ) ); + temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); + temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); + + } + + if ( json.skins === undefined ) json.skins = []; + + json.skins.push( { + inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), + joints: joints, + skeleton: nodeMap.get( rootJoint ) + } ); + + const skinIndex = node.skin = json.skins.length - 1; + + return skinIndex; + + } + + /** + * Process Object3D node + * @param {THREE.Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ + processNode( object ) { + + const json = this.json; + const options = this.options; + const nodeMap = this.nodeMap; + + if ( ! json.nodes ) json.nodes = []; + + const nodeDef = {}; + + if ( options.trs ) { + + const rotation = object.quaternion.toArray(); + const position = object.position.toArray(); + const scale = object.scale.toArray(); + + if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { + + nodeDef.rotation = rotation; + + } + + if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { + + nodeDef.translation = position; + + } + + if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { + + nodeDef.scale = scale; + + } + + } else { + + if ( object.matrixAutoUpdate ) { + + object.updateMatrix(); + + } + + if ( isIdentityMatrix( object.matrix ) === false ) { + + nodeDef.matrix = object.matrix.elements; + + } + + } + + // We don't export empty strings name because it represents no-name in Three.js. + if ( object.name !== '' ) nodeDef.name = String( object.name ); + + this.serializeUserData( object, nodeDef ); + + if ( object.isMesh || object.isLine || object.isPoints ) { + + const meshIndex = this.processMesh( object ); + + if ( meshIndex !== null ) nodeDef.mesh = meshIndex; + + } else if ( object.isCamera ) { + + nodeDef.camera = this.processCamera( object ); + + } + + if ( object.isSkinnedMesh ) this.skins.push( object ); + + if ( object.children.length > 0 ) { + + const children = []; + + for ( let i = 0, l = object.children.length; i < l; i ++ ) { + + const child = object.children[ i ]; + + if ( child.visible || options.onlyVisible === false ) { + + const nodeIndex = this.processNode( child ); + + if ( nodeIndex !== null ) children.push( nodeIndex ); + + } + + } + + if ( children.length > 0 ) nodeDef.children = children; + + } + + this._invokeAll( function ( ext ) { + + ext.writeNode && ext.writeNode( object, nodeDef ); + + } ); + + const nodeIndex = json.nodes.push( nodeDef ) - 1; + nodeMap.set( object, nodeIndex ); + return nodeIndex; + + } + + /** + * Process Scene + * @param {Scene} node Scene to process + */ + processScene( scene ) { + + const json = this.json; + const options = this.options; + + if ( ! json.scenes ) { + + json.scenes = []; + json.scene = 0; + + } + + const sceneDef = {}; + + if ( scene.name !== '' ) sceneDef.name = scene.name; + + json.scenes.push( sceneDef ); + + const nodes = []; + + for ( let i = 0, l = scene.children.length; i < l; i ++ ) { + + const child = scene.children[ i ]; + + if ( child.visible || options.onlyVisible === false ) { + + const nodeIndex = this.processNode( child ); + + if ( nodeIndex !== null ) nodes.push( nodeIndex ); + + } + + } + + if ( nodes.length > 0 ) sceneDef.nodes = nodes; + + this.serializeUserData( scene, sceneDef ); + + } + + /** + * Creates a Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ + processObjects( objects ) { + + const scene = new Scene(); + scene.name = 'AuxScene'; + + for ( let i = 0; i < objects.length; i ++ ) { + + // We push directly to children instead of calling `add` to prevent + // modify the .parent and break its original scene and hierarchy + scene.children.push( objects[ i ] ); + + } + + this.processScene( scene ); + + } + + /** + * @param {THREE.Object3D|Array} input + */ + processInput( input ) { + + const options = this.options; + + input = input instanceof Array ? input : [ input ]; + + this._invokeAll( function ( ext ) { + + ext.beforeParse && ext.beforeParse( input ); + + } ); + + const objectsWithoutScene = []; + + for ( let i = 0; i < input.length; i ++ ) { + + if ( input[ i ] instanceof Scene ) { + + this.processScene( input[ i ] ); + + } else { + + objectsWithoutScene.push( input[ i ] ); + + } + + } + + if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); + + for ( let i = 0; i < this.skins.length; ++ i ) { + + this.processSkin( this.skins[ i ] ); + + } + + for ( let i = 0; i < options.animations.length; ++ i ) { + + this.processAnimation( options.animations[ i ], input[ 0 ] ); + + } + + this._invokeAll( function ( ext ) { + + ext.afterParse && ext.afterParse( input ); + + } ); + + } + + _invokeAll( func ) { + + for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { + + func( this.plugins[ i ] ); + + } + + } + +} + +/** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ +class GLTFLightExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_lights_punctual'; + + } + + writeNode( light, nodeDef ) { + + if ( ! light.isLight ) return; + + if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { + + console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); + return; + + } + + const writer = this.writer; + const json = writer.json; + const extensionsUsed = writer.extensionsUsed; + + const lightDef = {}; + + if ( light.name ) lightDef.name = light.name; + + lightDef.color = light.color.toArray(); + + lightDef.intensity = light.intensity; + + if ( light.isDirectionalLight ) { + + lightDef.type = 'directional'; + + } else if ( light.isPointLight ) { + + lightDef.type = 'point'; + + if ( light.distance > 0 ) lightDef.range = light.distance; + + } else if ( light.isSpotLight ) { + + lightDef.type = 'spot'; + + if ( light.distance > 0 ) lightDef.range = light.distance; + + lightDef.spot = {}; + lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; + lightDef.spot.outerConeAngle = light.angle; + + } + + if ( light.decay !== undefined && light.decay !== 2 ) { + + console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + + 'and expects light.decay=2.' ); + + } + + if ( light.target + && ( light.target.parent !== light + || light.target.position.x !== 0 + || light.target.position.y !== 0 + || light.target.position.z !== - 1 ) ) { + + console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + + 'make light.target a child of the light with position 0,0,-1.' ); + + } + + if ( ! extensionsUsed[ this.name ] ) { + + json.extensions = json.extensions || {}; + json.extensions[ this.name ] = { lights: [] }; + extensionsUsed[ this.name ] = true; + + } + + const lights = json.extensions[ this.name ].lights; + lights.push( lightDef ); + + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; + + } + +} + +/** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ +class GLTFMaterialsUnlitExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_unlit'; + + } + + writeMaterial( material, materialDef ) { + + if ( ! material.isMeshBasicMaterial ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = {}; + + extensionsUsed[ this.name ] = true; + + materialDef.pbrMetallicRoughness.metallicFactor = 0.0; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; + + } + +} + +/** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness + */ +class GLTFMaterialsPBRSpecularGlossiness { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_pbrSpecularGlossiness'; + + } + + writeMaterial( material, materialDef ) { + + if ( ! material.isGLTFSpecularGlossinessMaterial ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + if ( materialDef.pbrMetallicRoughness.baseColorFactor ) { + + extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor; + + } + + const specularFactor = [ 1, 1, 1 ]; + material.specular.toArray( specularFactor, 0 ); + extensionDef.specularFactor = specularFactor; + extensionDef.glossinessFactor = material.glossiness; + + if ( materialDef.pbrMetallicRoughness.baseColorTexture ) { + + extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture; + + } + + if ( material.specularMap ) { + + const specularMapDef = { index: writer.processTexture( material.specularMap ) }; + writer.applyTextureTransform( specularMapDef, material.specularMap ); + extensionDef.specularGlossinessTexture = specularMapDef; + + } + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; + + } + +} + +/** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ +class GLTFMaterialsClearcoatExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_clearcoat'; + + } + + writeMaterial( material, materialDef ) { + + if ( ! material.isMeshPhysicalMaterial ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + extensionDef.clearcoatFactor = material.clearcoat; + + if ( material.clearcoatMap ) { + + const clearcoatMapDef = { index: writer.processTexture( material.clearcoatMap ) }; + writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); + extensionDef.clearcoatTexture = clearcoatMapDef; + + } + + extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; + + if ( material.clearcoatRoughnessMap ) { + + const clearcoatRoughnessMapDef = { index: writer.processTexture( material.clearcoatRoughnessMap ) }; + writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); + extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; + + } + + if ( material.clearcoatNormalMap ) { + + const clearcoatNormalMapDef = { index: writer.processTexture( material.clearcoatNormalMap ) }; + writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); + extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; + + } + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; + + + } + +} + +/** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + */ +class GLTFMaterialsTransmissionExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_transmission'; + + } + + writeMaterial( material, materialDef ) { + + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + extensionDef.transmissionFactor = material.transmission; + + if ( material.transmissionMap ) { + + const transmissionMapDef = { index: writer.processTexture( material.transmissionMap ) }; + writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); + extensionDef.transmissionTexture = transmissionMapDef; + + } + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; + + } + +} + +/** + * Materials Volume Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + */ +class GLTFMaterialsVolumeExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_volume'; + + } + + writeMaterial( material, materialDef ) { + + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + extensionDef.thicknessFactor = material.thickness; + + if ( material.thicknessMap ) { + + const thicknessMapDef = { index: writer.processTexture( material.thicknessMap ) }; + writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); + extensionDef.thicknessTexture = thicknessMapDef; + + } + + extensionDef.attenuationDistance = material.attenuationDistance; + extensionDef.attenuationColor = material.attenuationColor.toArray(); + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; + + } + +} + +/** + * Static utility functions + */ +GLTFExporter.Utils = { + + insertKeyframe: function ( track, time ) { + + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); + + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + + let index; + + if ( track.times.length === 0 ) { + + times[ 0 ] = time; + + for ( let i = 0; i < valueSize; i ++ ) { + + values[ i ] = 0; + + } + + index = 0; + + } else if ( time < track.times[ 0 ] ) { + + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + + times[ 0 ] = time; + times.set( track.times, 1 ); + + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); + + index = 0; + + } else if ( time > track.times[ track.times.length - 1 ] ) { + + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + + return track.times.length - 1; + + } + + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); + + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); + + index = times.length - 1; + + } else { + + for ( let i = 0; i < track.times.length; i ++ ) { + + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); + + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + + index = i + 1; + + break; + + } + + } + + } + + track.times = times; + track.values = values; + + return index; + + }, + + mergeMorphTargetTracks: function ( clip, root ) { + + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; + + for ( let i = 0; i < sourceTracks.length; ++ i ) { + + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; + + } + + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + + } + + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); + + } + + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + + if ( targetIndex === undefined ) { + + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + + } + + let mergedTrack; + + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + + mergedTrack = sourceTrack.clone(); + + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + + } + + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; + + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); + + continue; + + } + + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + + } + + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + + } + + } + + clip.tracks = tracks; + + return clip; + + } + +}; + +export { GLTFExporter }; diff --git a/jsm/exporters/MMDExporter.js b/jsm/exporters/MMDExporter.js new file mode 100644 index 0000000..36cc8b3 --- /dev/null +++ b/jsm/exporters/MMDExporter.js @@ -0,0 +1,217 @@ +import { + Matrix4, + Quaternion, + Vector3 +} from 'three'; +import { MMDParser } from '../libs/mmdparser.module.js'; + +/** + * Dependencies + * - mmd-parser https://github.com/takahirox/mmd-parser + */ + +class MMDExporter { + + /* TODO: implement + // mesh -> pmd + this.parsePmd = function ( object ) { + + }; + */ + + /* TODO: implement + // mesh -> pmx + this.parsePmx = function ( object ) { + + }; + */ + + /* TODO: implement + // animation + skeleton -> vmd + this.parseVmd = function ( object ) { + + }; + */ + + /* + * skeleton -> vpd + * Returns Shift_JIS encoded Uint8Array. Otherwise return strings. + */ + parseVpd( skin, outputShiftJis, useOriginalBones ) { + + if ( skin.isSkinnedMesh !== true ) { + + console.warn( 'THREE.MMDExporter: parseVpd() requires SkinnedMesh instance.' ); + return null; + + } + + function toStringsFromNumber( num ) { + + if ( Math.abs( num ) < 1e-6 ) num = 0; + + let a = num.toString(); + + if ( a.indexOf( '.' ) === - 1 ) { + + a += '.'; + + } + + a += '000000'; + + const index = a.indexOf( '.' ); + + const d = a.slice( 0, index ); + const p = a.slice( index + 1, index + 7 ); + + return d + '.' + p; + + } + + function toStringsFromArray( array ) { + + const a = []; + + for ( let i = 0, il = array.length; i < il; i ++ ) { + + a.push( toStringsFromNumber( array[ i ] ) ); + + } + + return a.join( ',' ); + + } + + skin.updateMatrixWorld( true ); + + const bones = skin.skeleton.bones; + const bones2 = getBindBones( skin ); + + const position = new Vector3(); + const quaternion = new Quaternion(); + const quaternion2 = new Quaternion(); + const matrix = new Matrix4(); + + const array = []; + array.push( 'Vocaloid Pose Data file' ); + array.push( '' ); + array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' ); + array.push( bones.length + ';' ); + array.push( '' ); + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + const bone = bones[ i ]; + const bone2 = bones2[ i ]; + + /* + * use the bone matrix saved before solving IK. + * see CCDIKSolver for the detail. + */ + if ( useOriginalBones === true && + bone.userData.ik !== undefined && + bone.userData.ik.originalMatrix !== undefined ) { + + matrix.fromArray( bone.userData.ik.originalMatrix ); + + } else { + + matrix.copy( bone.matrix ); + + } + + position.setFromMatrixPosition( matrix ); + quaternion.setFromRotationMatrix( matrix ); + + const pArray = position.sub( bone2.position ).toArray(); + const qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); + + // right to left + pArray[ 2 ] = - pArray[ 2 ]; + qArray[ 0 ] = - qArray[ 0 ]; + qArray[ 1 ] = - qArray[ 1 ]; + + array.push( 'Bone' + i + '{' + bone.name ); + array.push( ' ' + toStringsFromArray( pArray ) + ';' ); + array.push( ' ' + toStringsFromArray( qArray ) + ';' ); + array.push( '}' ); + array.push( '' ); + + } + + array.push( '' ); + + const lines = array.join( '\n' ); + + return ( outputShiftJis === true ) ? unicodeToShiftjis( lines ) : lines; + + } + +} + +// Unicode to Shift_JIS table +let u2sTable; + +function unicodeToShiftjis( str ) { + + if ( u2sTable === undefined ) { + + const encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef + const table = encoder.s2uTable; + u2sTable = {}; + + const keys = Object.keys( table ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + let key = keys[ i ]; + + const value = table[ key ]; + key = parseInt( key ); + + u2sTable[ value ] = key; + + } + + } + + const array = []; + + for ( let i = 0, il = str.length; i < il; i ++ ) { + + const code = str.charCodeAt( i ); + + const value = u2sTable[ code ]; + + if ( value === undefined ) { + + throw new Error( 'cannot convert charcode 0x' + code.toString( 16 ) ); + + } else if ( value > 0xff ) { + + array.push( ( value >> 8 ) & 0xff ); + array.push( value & 0xff ); + + } else { + + array.push( value & 0xff ); + + } + + } + + return new Uint8Array( array ); + +} + +function getBindBones( skin ) { + + // any more efficient ways? + const poseSkin = skin.clone(); + poseSkin.pose(); + return poseSkin.skeleton.bones; + +} + +export { MMDExporter }; diff --git a/jsm/exporters/OBJExporter.js b/jsm/exporters/OBJExporter.js new file mode 100644 index 0000000..0c7265e --- /dev/null +++ b/jsm/exporters/OBJExporter.js @@ -0,0 +1,302 @@ +import { + Color, + Matrix3, + Vector2, + Vector3 +} from 'three'; + +class OBJExporter { + + parse( object ) { + + let output = ''; + + let indexVertex = 0; + let indexVertexUvs = 0; + let indexNormals = 0; + + const vertex = new Vector3(); + const color = new Color(); + const normal = new Vector3(); + const uv = new Vector2(); + + const face = []; + + function parseMesh( mesh ) { + + let nbVertex = 0; + let nbNormals = 0; + let nbVertexUvs = 0; + + const geometry = mesh.geometry; + + const normalMatrixWorld = new Matrix3(); + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + // shortcuts + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const indices = geometry.getIndex(); + + // name of the mesh object + output += 'o ' + mesh.name + '\n'; + + // name of the mesh material + if ( mesh.material && mesh.material.name ) { + + output += 'usemtl ' + mesh.material.name + '\n'; + + } + + // vertices + + if ( vertices !== undefined ) { + + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + // transform the vertex to world space + vertex.applyMatrix4( mesh.matrixWorld ); + + // transform the vertex to export format + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + // uvs + + if ( uvs !== undefined ) { + + for ( let i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) { + + uv.fromBufferAttribute( uvs, i ); + + // transform the uv to export format + output += 'vt ' + uv.x + ' ' + uv.y + '\n'; + + } + + } + + // normals + + if ( normals !== undefined ) { + + normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); + + for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) { + + normal.fromBufferAttribute( normals, i ); + + // transform the normal to world space + normal.applyMatrix3( normalMatrixWorld ).normalize(); + + // transform the normal to export format + output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n'; + + } + + } + + // faces + + if ( indices !== null ) { + + for ( let i = 0, l = indices.count; i < l; i += 3 ) { + + for ( let m = 0; m < 3; m ++ ) { + + const j = indices.getX( i + m ) + 1; + + face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); + + } + + // transform the face to export format + output += 'f ' + face.join( ' ' ) + '\n'; + + } + + } else { + + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { + + for ( let m = 0; m < 3; m ++ ) { + + const j = i + m + 1; + + face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); + + } + + // transform the face to export format + output += 'f ' + face.join( ' ' ) + '\n'; + + } + + } + + // update index + indexVertex += nbVertex; + indexVertexUvs += nbVertexUvs; + indexNormals += nbNormals; + + } + + function parseLine( line ) { + + let nbVertex = 0; + + const geometry = line.geometry; + const type = line.type; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + // shortcuts + const vertices = geometry.getAttribute( 'position' ); + + // name of the line object + output += 'o ' + line.name + '\n'; + + if ( vertices !== undefined ) { + + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + // transform the vertex to world space + vertex.applyMatrix4( line.matrixWorld ); + + // transform the vertex to export format + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + if ( type === 'Line' ) { + + output += 'l '; + + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { + + output += ( indexVertex + j ) + ' '; + + } + + output += '\n'; + + } + + if ( type === 'LineSegments' ) { + + for ( let j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) { + + output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n'; + + } + + } + + // update index + indexVertex += nbVertex; + + } + + function parsePoints( points ) { + + let nbVertex = 0; + + const geometry = points.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const vertices = geometry.getAttribute( 'position' ); + const colors = geometry.getAttribute( 'color' ); + + output += 'o ' + points.name + '\n'; + + if ( vertices !== undefined ) { + + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + vertex.applyMatrix4( points.matrixWorld ); + + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z; + + if ( colors !== undefined ) { + + color.fromBufferAttribute( colors, i ).convertLinearToSRGB(); + + output += ' ' + color.r + ' ' + color.g + ' ' + color.b; + + } + + output += '\n'; + + } + + output += 'p '; + + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { + + output += ( indexVertex + j ) + ' '; + + } + + output += '\n'; + + } + + // update index + indexVertex += nbVertex; + + } + + object.traverse( function ( child ) { + + if ( child.isMesh === true ) { + + parseMesh( child ); + + } + + if ( child.isLine === true ) { + + parseLine( child ); + + } + + if ( child.isPoints === true ) { + + parsePoints( child ); + + } + + } ); + + return output; + + } + +} + +export { OBJExporter }; diff --git a/jsm/exporters/PLYExporter.js b/jsm/exporters/PLYExporter.js new file mode 100644 index 0000000..a360658 --- /dev/null +++ b/jsm/exporters/PLYExporter.js @@ -0,0 +1,529 @@ +import { + Matrix3, + Vector3, + Color +} from 'three'; + +/** + * https://github.com/gkjohnson/ply-exporter-js + * + * Usage: + * const exporter = new PLYExporter(); + * + * // second argument is a list of options + * exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true }); + * + * Format Definition: + * http://paulbourke.net/dataformats/ply/ + */ + +class PLYExporter { + + parse( object, onDone, options ) { + + if ( onDone && typeof onDone === 'object' ) { + + console.warn( 'THREE.PLYExporter: The options parameter is now the third argument to the "parse" function. See the documentation for the new API.' ); + options = onDone; + onDone = undefined; + + } + + // Iterate over the valid meshes in the object + function traverseMeshes( cb ) { + + object.traverse( function ( child ) { + + if ( child.isMesh === true ) { + + const mesh = child; + const geometry = mesh.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + if ( geometry.hasAttribute( 'position' ) === true ) { + + cb( mesh, geometry ); + + } + + } + + } ); + + } + + // Default options + const defaultOptions = { + binary: false, + excludeAttributes: [], // normal, uv, color, index + littleEndian: false + }; + + options = Object.assign( defaultOptions, options ); + + const excludeAttributes = options.excludeAttributes; + let includeNormals = false; + let includeColors = false; + let includeUVs = false; + + // count the vertices, check which properties are used, + // and cache the BufferGeometry + let vertexCount = 0; + let faceCount = 0; + object.traverse( function ( child ) { + + if ( child.isMesh === true ) { + + const mesh = child; + const geometry = mesh.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); + + if ( vertices === undefined ) { + + return; + + } + + vertexCount += vertices.count; + faceCount += indices ? indices.count / 3 : vertices.count / 3; + + if ( normals !== undefined ) includeNormals = true; + + if ( uvs !== undefined ) includeUVs = true; + + if ( colors !== undefined ) includeColors = true; + + } + + } ); + + const tempColor = new Color(); + const includeIndices = excludeAttributes.indexOf( 'index' ) === - 1; + includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1; + includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1; + includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1; + + + if ( includeIndices && faceCount !== Math.floor( faceCount ) ) { + + // point cloud meshes will not have an index array and may not have a + // number of vertices that is divisble by 3 (and therefore representable + // as triangles) + console.error( + + 'PLYExporter: Failed to generate a valid PLY file with triangle indices because the ' + + 'number of indices is not divisible by 3.' + + ); + + return null; + + } + + const indexByteCount = 4; + + let header = + 'ply\n' + + `format ${ options.binary ? ( options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' ) : 'ascii' } 1.0\n` + + `element vertex ${vertexCount}\n` + + + // position + 'property float x\n' + + 'property float y\n' + + 'property float z\n'; + + if ( includeNormals === true ) { + + // normal + header += + 'property float nx\n' + + 'property float ny\n' + + 'property float nz\n'; + + } + + if ( includeUVs === true ) { + + // uvs + header += + 'property float s\n' + + 'property float t\n'; + + } + + if ( includeColors === true ) { + + // colors + header += + 'property uchar red\n' + + 'property uchar green\n' + + 'property uchar blue\n'; + + } + + if ( includeIndices === true ) { + + // faces + header += + `element face ${faceCount}\n` + + 'property list uchar int vertex_index\n'; + + } + + header += 'end_header\n'; + + + // Generate attribute data + const vertex = new Vector3(); + const normalMatrixWorld = new Matrix3(); + let result = null; + + if ( options.binary === true ) { + + // Binary File Generation + const headerBin = new TextEncoder().encode( header ); + + // 3 position values at 4 bytes + // 3 normal values at 4 bytes + // 3 color channels with 1 byte + // 2 uv values at 4 bytes + const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); + + // 1 byte shape desciptor + // 3 vertex indices at ${indexByteCount} bytes + const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; + const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); + new Uint8Array( output.buffer ).set( headerBin, 0 ); + + + let vOffset = headerBin.length; + let fOffset = headerBin.length + vertexListLength; + let writtenVertices = 0; + traverseMeshes( function ( mesh, geometry ) { + + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); + + normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); + + for ( let i = 0, l = vertices.count; i < l; i ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + vertex.applyMatrix4( mesh.matrixWorld ); + + + // Position information + output.setFloat32( vOffset, vertex.x, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.y, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.z, options.littleEndian ); + vOffset += 4; + + // Normal information + if ( includeNormals === true ) { + + if ( normals != null ) { + + vertex.fromBufferAttribute( normals, i ); + + vertex.applyMatrix3( normalMatrixWorld ).normalize(); + + output.setFloat32( vOffset, vertex.x, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.y, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.z, options.littleEndian ); + vOffset += 4; + + } else { + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + } + + } + + // UV information + if ( includeUVs === true ) { + + if ( uvs != null ) { + + output.setFloat32( vOffset, uvs.getX( i ), options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, uvs.getY( i ), options.littleEndian ); + vOffset += 4; + + } else { + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + } + + } + + // Color information + if ( includeColors === true ) { + + if ( colors != null ) { + + tempColor + .fromBufferAttribute( colors, i ) + .convertLinearToSRGB(); + + output.setUint8( vOffset, Math.floor( tempColor.r * 255 ) ); + vOffset += 1; + + output.setUint8( vOffset, Math.floor( tempColor.g * 255 ) ); + vOffset += 1; + + output.setUint8( vOffset, Math.floor( tempColor.b * 255 ) ); + vOffset += 1; + + } else { + + output.setUint8( vOffset, 255 ); + vOffset += 1; + + output.setUint8( vOffset, 255 ); + vOffset += 1; + + output.setUint8( vOffset, 255 ); + vOffset += 1; + + } + + } + + } + + if ( includeIndices === true ) { + + // Create the face list + + if ( indices !== null ) { + + for ( let i = 0, l = indices.count; i < l; i += 3 ) { + + output.setUint8( fOffset, 3 ); + fOffset += 1; + + output.setUint32( fOffset, indices.getX( i + 0 ) + writtenVertices, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, indices.getX( i + 1 ) + writtenVertices, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, indices.getX( i + 2 ) + writtenVertices, options.littleEndian ); + fOffset += indexByteCount; + + } + + } else { + + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { + + output.setUint8( fOffset, 3 ); + fOffset += 1; + + output.setUint32( fOffset, writtenVertices + i, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, writtenVertices + i + 1, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, writtenVertices + i + 2, options.littleEndian ); + fOffset += indexByteCount; + + } + + } + + } + + + // Save the amount of verts we've already written so we can offset + // the face index on the next mesh + writtenVertices += vertices.count; + + } ); + + result = output.buffer; + + } else { + + // Ascii File Generation + // count the number of vertices + let writtenVertices = 0; + let vertexList = ''; + let faceList = ''; + + traverseMeshes( function ( mesh, geometry ) { + + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); + + normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); + + // form each line + for ( let i = 0, l = vertices.count; i < l; i ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + vertex.applyMatrix4( mesh.matrixWorld ); + + + // Position information + let line = + vertex.x + ' ' + + vertex.y + ' ' + + vertex.z; + + // Normal information + if ( includeNormals === true ) { + + if ( normals != null ) { + + vertex.fromBufferAttribute( normals, i ); + + vertex.applyMatrix3( normalMatrixWorld ).normalize(); + + line += ' ' + + vertex.x + ' ' + + vertex.y + ' ' + + vertex.z; + + } else { + + line += ' 0 0 0'; + + } + + } + + // UV information + if ( includeUVs === true ) { + + if ( uvs != null ) { + + line += ' ' + + uvs.getX( i ) + ' ' + + uvs.getY( i ); + + } else { + + line += ' 0 0'; + + } + + } + + // Color information + if ( includeColors === true ) { + + if ( colors != null ) { + + tempColor + .fromBufferAttribute( colors, i ) + .convertLinearToSRGB(); + + line += ' ' + + Math.floor( tempColor.r * 255 ) + ' ' + + Math.floor( tempColor.g * 255 ) + ' ' + + Math.floor( tempColor.b * 255 ); + + } else { + + line += ' 255 255 255'; + + } + + } + + vertexList += line + '\n'; + + } + + // Create the face list + if ( includeIndices === true ) { + + if ( indices !== null ) { + + for ( let i = 0, l = indices.count; i < l; i += 3 ) { + + faceList += `3 ${ indices.getX( i + 0 ) + writtenVertices }`; + faceList += ` ${ indices.getX( i + 1 ) + writtenVertices }`; + faceList += ` ${ indices.getX( i + 2 ) + writtenVertices }\n`; + + } + + } else { + + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { + + faceList += `3 ${ writtenVertices + i } ${ writtenVertices + i + 1 } ${ writtenVertices + i + 2 }\n`; + + } + + } + + faceCount += indices ? indices.count / 3 : vertices.count / 3; + + } + + writtenVertices += vertices.count; + + } ); + + result = `${ header }${vertexList}${ includeIndices ? `${faceList}\n` : '\n' }`; + + } + + if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) ); + + return result; + + } + +} + +export { PLYExporter }; diff --git a/jsm/exporters/STLExporter.js b/jsm/exporters/STLExporter.js new file mode 100644 index 0000000..ff77e79 --- /dev/null +++ b/jsm/exporters/STLExporter.js @@ -0,0 +1,203 @@ +import { + Vector3 +} from 'three'; + +/** + * Usage: + * const exporter = new STLExporter(); + * + * // second argument is a list of options + * const data = exporter.parse( mesh, { binary: true } ); + * + */ + +class STLExporter { + + parse( scene, options = {} ) { + + const binary = options.binary !== undefined ? options.binary : false; + + // + + const objects = []; + let triangles = 0; + + scene.traverse( function ( object ) { + + if ( object.isMesh ) { + + const geometry = object.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.STLExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); + + triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 ); + + objects.push( { + object3d: object, + geometry: geometry + } ); + + } + + } ); + + let output; + let offset = 80; // skip header + + if ( binary === true ) { + + const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; + const arrayBuffer = new ArrayBuffer( bufferLength ); + output = new DataView( arrayBuffer ); + output.setUint32( offset, triangles, true ); offset += 4; + + } else { + + output = ''; + output += 'solid exported\n'; + + } + + const vA = new Vector3(); + const vB = new Vector3(); + const vC = new Vector3(); + const cb = new Vector3(); + const ab = new Vector3(); + const normal = new Vector3(); + + for ( let i = 0, il = objects.length; i < il; i ++ ) { + + const object = objects[ i ].object3d; + const geometry = objects[ i ].geometry; + + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); + + if ( index !== null ) { + + // indexed geometry + + for ( let j = 0; j < index.count; j += 3 ) { + + const a = index.getX( j + 0 ); + const b = index.getX( j + 1 ); + const c = index.getX( j + 2 ); + + writeFace( a, b, c, positionAttribute, object ); + + } + + } else { + + // non-indexed geometry + + for ( let j = 0; j < positionAttribute.count; j += 3 ) { + + const a = j + 0; + const b = j + 1; + const c = j + 2; + + writeFace( a, b, c, positionAttribute, object ); + + } + + } + + } + + if ( binary === false ) { + + output += 'endsolid exported\n'; + + } + + return output; + + function writeFace( a, b, c, positionAttribute, object ) { + + vA.fromBufferAttribute( positionAttribute, a ); + vB.fromBufferAttribute( positionAttribute, b ); + vC.fromBufferAttribute( positionAttribute, c ); + + if ( object.isSkinnedMesh === true ) { + + object.boneTransform( a, vA ); + object.boneTransform( b, vB ); + object.boneTransform( c, vC ); + + } + + vA.applyMatrix4( object.matrixWorld ); + vB.applyMatrix4( object.matrixWorld ); + vC.applyMatrix4( object.matrixWorld ); + + writeNormal( vA, vB, vC ); + + writeVertex( vA ); + writeVertex( vB ); + writeVertex( vC ); + + if ( binary === true ) { + + output.setUint16( offset, 0, true ); offset += 2; + + } else { + + output += '\t\tendloop\n'; + output += '\tendfacet\n'; + + } + + } + + function writeNormal( vA, vB, vC ) { + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ).normalize(); + + normal.copy( cb ).normalize(); + + if ( binary === true ) { + + output.setFloat32( offset, normal.x, true ); offset += 4; + output.setFloat32( offset, normal.y, true ); offset += 4; + output.setFloat32( offset, normal.z, true ); offset += 4; + + } else { + + output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n'; + output += '\t\touter loop\n'; + + } + + } + + function writeVertex( vertex ) { + + if ( binary === true ) { + + output.setFloat32( offset, vertex.x, true ); offset += 4; + output.setFloat32( offset, vertex.y, true ); offset += 4; + output.setFloat32( offset, vertex.z, true ); offset += 4; + + } else { + + output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + } + +} + +export { STLExporter }; diff --git a/jsm/exporters/USDZExporter.js b/jsm/exporters/USDZExporter.js new file mode 100644 index 0000000..71e32df --- /dev/null +++ b/jsm/exporters/USDZExporter.js @@ -0,0 +1,547 @@ +import * as fflate from '../libs/fflate.module.js'; + +class USDZExporter { + + async parse( scene ) { + + const files = {}; + const modelFileName = 'model.usda'; + + // model file should be first in USDZ archive so we init it here + files[ modelFileName ] = null; + + let output = buildHeader(); + + const materials = {}; + const textures = {}; + + scene.traverseVisible( ( object ) => { + + if ( object.isMesh ) { + + if ( object.material.isMeshStandardMaterial ) { + + const geometry = object.geometry; + const material = object.material; + + const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usd'; + + if ( ! ( geometryFileName in files ) ) { + + const meshObject = buildMeshObject( geometry ); + files[ geometryFileName ] = buildUSDFileAsString( meshObject ); + + } + + if ( ! ( material.uuid in materials ) ) { + + materials[ material.uuid ] = material; + + } + + output += buildXform( object, geometry, material ); + + } else { + + console.warn( 'THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)', object ); + + } + + } + + } ); + + output += buildMaterials( materials, textures ); + + files[ modelFileName ] = fflate.strToU8( output ); + output = null; + + for ( const id in textures ) { + + const texture = textures[ id ]; + const color = id.split( '_' )[ 1 ]; + const isRGBA = texture.format === 1023; + + const canvas = imageToCanvas( texture.image, color ); + const blob = await new Promise( resolve => canvas.toBlob( resolve, isRGBA ? 'image/png' : 'image/jpeg', 1 ) ); + + files[ `textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }` ] = new Uint8Array( await blob.arrayBuffer() ); + + } + + // 64 byte alignment + // https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109 + + let offset = 0; + + for ( const filename in files ) { + + const file = files[ filename ]; + const headerSize = 34 + filename.length; + + offset += headerSize; + + const offsetMod64 = offset & 63; + + if ( offsetMod64 !== 4 ) { + + const padLength = 64 - offsetMod64; + const padding = new Uint8Array( padLength ); + + files[ filename ] = [ file, { extra: { 12345: padding } } ]; + + } + + offset = file.length; + + } + + return fflate.zipSync( files, { level: 0 } ); + + } + +} + +function imageToCanvas( image, color ) { + + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + + const scale = 1024 / Math.max( image.width, image.height ); + + const canvas = document.createElement( 'canvas' ); + canvas.width = image.width * Math.min( 1, scale ); + canvas.height = image.height * Math.min( 1, scale ); + + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, canvas.width, canvas.height ); + + if ( color !== undefined ) { + + const hex = parseInt( color, 16 ); + + const r = ( hex >> 16 & 255 ) / 255; + const g = ( hex >> 8 & 255 ) / 255; + const b = ( hex & 255 ) / 255; + + const imagedata = context.getImageData( 0, 0, canvas.width, canvas.height ); + const data = imagedata.data; + + for ( let i = 0; i < data.length; i += 4 ) { + + data[ i + 0 ] = data[ i + 0 ] * r; + data[ i + 1 ] = data[ i + 1 ] * g; + data[ i + 2 ] = data[ i + 2 ] * b; + + } + + context.putImageData( imagedata, 0, 0 ); + + } + + return canvas; + + } + +} + +// + +const PRECISION = 7; + +function buildHeader() { + + return `#usda 1.0 +( + customLayerData = { + string creator = "Three.js USDZExporter" + } + metersPerUnit = 1 + upAxis = "Y" +) + +`; + +} + +function buildUSDFileAsString( dataToInsert ) { + + let output = buildHeader(); + output += dataToInsert; + return fflate.strToU8( output ); + +} + +// Xform + +function buildXform( object, geometry, material ) { + + const name = 'Object_' + object.id; + const transform = buildMatrix( object.matrixWorld ); + + if ( object.matrixWorld.determinant() < 0 ) { + + console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', object ); + + } + + return `def Xform "${ name }" ( + prepend references = @./geometries/Geometry_${ geometry.id }.usd@ +) +{ + matrix4d xformOp:transform = ${ transform } + uniform token[] xformOpOrder = ["xformOp:transform"] + + rel material:binding = +} + +`; + +} + +function buildMatrix( matrix ) { + + const array = matrix.elements; + + return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`; + +} + +function buildMatrixRow( array, offset ) { + + return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`; + +} + +// Mesh + +function buildMeshObject( geometry ) { + + const mesh = buildMesh( geometry ); + return ` +def "Geometry" +{ + ${mesh} +} +`; + +} + +function buildMesh( geometry ) { + + const name = 'Geometry'; + const attributes = geometry.attributes; + const count = attributes.position.count; + + return ` + def Mesh "${ name }" + { + int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }] + int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }] + normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] ( + interpolation = "vertex" + ) + point3f[] points = [${ buildVector3Array( attributes.position, count )}] + float2[] primvars:st = [${ buildVector2Array( attributes.uv, count )}] ( + interpolation = "vertex" + ) + uniform token subdivisionScheme = "none" + } +`; + +} + +function buildMeshVertexCount( geometry ) { + + const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count; + + return Array( count / 3 ).fill( 3 ).join( ', ' ); + +} + +function buildMeshVertexIndices( geometry ) { + + const index = geometry.index; + const array = []; + + if ( index !== null ) { + + for ( let i = 0; i < index.count; i ++ ) { + + array.push( index.getX( i ) ); + + } + + } else { + + const length = geometry.attributes.position.count; + + for ( let i = 0; i < length; i ++ ) { + + array.push( i ); + + } + + } + + return array.join( ', ' ); + +} + +function buildVector3Array( attribute, count ) { + + if ( attribute === undefined ) { + + console.warn( 'USDZExporter: Normals missing.' ); + return Array( count ).fill( '(0, 0, 0)' ).join( ', ' ); + + } + + const array = []; + + for ( let i = 0; i < attribute.count; i ++ ) { + + const x = attribute.getX( i ); + const y = attribute.getY( i ); + const z = attribute.getZ( i ); + + array.push( `(${ x.toPrecision( PRECISION ) }, ${ y.toPrecision( PRECISION ) }, ${ z.toPrecision( PRECISION ) })` ); + + } + + return array.join( ', ' ); + +} + +function buildVector2Array( attribute, count ) { + + if ( attribute === undefined ) { + + console.warn( 'USDZExporter: UVs missing.' ); + return Array( count ).fill( '(0, 0)' ).join( ', ' ); + + } + + const array = []; + + for ( let i = 0; i < attribute.count; i ++ ) { + + const x = attribute.getX( i ); + const y = attribute.getY( i ); + + array.push( `(${ x.toPrecision( PRECISION ) }, ${ 1 - y.toPrecision( PRECISION ) })` ); + + } + + return array.join( ', ' ); + +} + +// Materials + +function buildMaterials( materials, textures ) { + + const array = []; + + for ( const uuid in materials ) { + + const material = materials[ uuid ]; + + array.push( buildMaterial( material, textures ) ); + + } + + return `def "Materials" +{ +${ array.join( '' ) } +} + +`; + +} + +function buildMaterial( material, textures ) { + + // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html + + const pad = ' '; + const inputs = []; + const samplers = []; + + function buildTexture( texture, mapType, color ) { + + const id = texture.id + ( color ? '_' + color.getHexString() : '' ); + const isRGBA = texture.format === 1023; + + textures[ id ] = texture; + + return ` + def Shader "Transform2d_${ mapType }" ( + sdrMetadata = { + string role = "math" + } + ) + { + uniform token info:id = "UsdTransform2d" + float2 inputs:in.connect = + float2 inputs:scale = ${ buildVector2( texture.repeat ) } + float2 inputs:translation = ${ buildVector2( texture.offset ) } + float2 outputs:result + } + + def Shader "Texture_${ texture.id }_${ mapType }" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float outputs:r + float outputs:g + float outputs:b + float3 outputs:rgb + ${ material.transparent || material.alphaTest > 0.0 ? 'float outputs:a' : '' } + }`; + + } + + if ( material.map !== null ) { + + inputs.push( `${ pad }color3f inputs:diffuseColor.connect = ` ); + + if ( material.transparent ) { + + inputs.push( `${ pad }float inputs:opacity.connect = ` ); + + } else if ( material.alphaTest > 0.0 ) { + + inputs.push( `${ pad }float inputs:opacity.connect = ` ); + inputs.push( `${ pad }float inputs:opacityThreshold = ${material.alphaTest}` ); + + } + + samplers.push( buildTexture( material.map, 'diffuse', material.color ) ); + + } else { + + inputs.push( `${ pad }color3f inputs:diffuseColor = ${ buildColor( material.color ) }` ); + + } + + if ( material.emissiveMap !== null ) { + + inputs.push( `${ pad }color3f inputs:emissiveColor.connect = ` ); + + samplers.push( buildTexture( material.emissiveMap, 'emissive' ) ); + + } else if ( material.emissive.getHex() > 0 ) { + + inputs.push( `${ pad }color3f inputs:emissiveColor = ${ buildColor( material.emissive ) }` ); + + } + + if ( material.normalMap !== null ) { + + inputs.push( `${ pad }normal3f inputs:normal.connect = ` ); + + samplers.push( buildTexture( material.normalMap, 'normal' ) ); + + } + + if ( material.aoMap !== null ) { + + inputs.push( `${ pad }float inputs:occlusion.connect = ` ); + + samplers.push( buildTexture( material.aoMap, 'occlusion' ) ); + + } + + if ( material.roughnessMap !== null && material.roughness === 1 ) { + + inputs.push( `${ pad }float inputs:roughness.connect = ` ); + + samplers.push( buildTexture( material.roughnessMap, 'roughness' ) ); + + } else { + + inputs.push( `${ pad }float inputs:roughness = ${ material.roughness }` ); + + } + + if ( material.metalnessMap !== null && material.metalness === 1 ) { + + inputs.push( `${ pad }float inputs:metallic.connect = ` ); + + samplers.push( buildTexture( material.metalnessMap, 'metallic' ) ); + + } else { + + inputs.push( `${ pad }float inputs:metallic = ${ material.metalness }` ); + + } + + if ( material.alphaMap !== null ) { + + inputs.push( `${pad}float inputs:opacity.connect = ` ); + inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` ); + + samplers.push( buildTexture( material.alphaMap, 'opacity' ) ); + + } else { + + inputs.push( `${pad}float inputs:opacity = ${material.opacity}` ); + + } + + if ( material.isMeshPhysicalMaterial ) { + + inputs.push( `${ pad }float inputs:clearcoat = ${ material.clearcoat }` ); + inputs.push( `${ pad }float inputs:clearcoatRoughness = ${ material.clearcoatRoughness }` ); + inputs.push( `${ pad }float inputs:ior = ${ material.ior }` ); + + } + + return ` + def Material "Material_${ material.id }" + { + def Shader "PreviewSurface" + { + uniform token info:id = "UsdPreviewSurface" +${ inputs.join( '\n' ) } + int inputs:useSpecularWorkflow = 0 + token outputs:surface + } + + token outputs:surface.connect = + token inputs:frame:stPrimvarName = "st" + + def Shader "uvReader_st" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 inputs:fallback = (0.0, 0.0) + float2 outputs:result + } + +${ samplers.join( '\n' ) } + + } +`; + +} + +function buildColor( color ) { + + return `(${ color.r }, ${ color.g }, ${ color.b })`; + +} + +function buildVector2( vector ) { + + return `(${ vector.x }, ${ vector.y })`; + +} + +export { USDZExporter }; diff --git a/jsm/geometries/BoxLineGeometry.js b/jsm/geometries/BoxLineGeometry.js new file mode 100644 index 0000000..8f05833 --- /dev/null +++ b/jsm/geometries/BoxLineGeometry.js @@ -0,0 +1,69 @@ +import { + BufferGeometry, + Float32BufferAttribute +} from 'three'; + +class BoxLineGeometry extends BufferGeometry { + + constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { + + super(); + + widthSegments = Math.floor( widthSegments ); + heightSegments = Math.floor( heightSegments ); + depthSegments = Math.floor( depthSegments ); + + const widthHalf = width / 2; + const heightHalf = height / 2; + const depthHalf = depth / 2; + + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + const segmentDepth = depth / depthSegments; + + const vertices = []; + + let x = - widthHalf; + let y = - heightHalf; + let z = - depthHalf; + + for ( let i = 0; i <= widthSegments; i ++ ) { + + vertices.push( x, - heightHalf, - depthHalf, x, heightHalf, - depthHalf ); + vertices.push( x, heightHalf, - depthHalf, x, heightHalf, depthHalf ); + vertices.push( x, heightHalf, depthHalf, x, - heightHalf, depthHalf ); + vertices.push( x, - heightHalf, depthHalf, x, - heightHalf, - depthHalf ); + + x += segmentWidth; + + } + + for ( let i = 0; i <= heightSegments; i ++ ) { + + vertices.push( - widthHalf, y, - depthHalf, widthHalf, y, - depthHalf ); + vertices.push( widthHalf, y, - depthHalf, widthHalf, y, depthHalf ); + vertices.push( widthHalf, y, depthHalf, - widthHalf, y, depthHalf ); + vertices.push( - widthHalf, y, depthHalf, - widthHalf, y, - depthHalf ); + + y += segmentHeight; + + } + + for ( let i = 0; i <= depthSegments; i ++ ) { + + vertices.push( - widthHalf, - heightHalf, z, - widthHalf, heightHalf, z ); + vertices.push( - widthHalf, heightHalf, z, widthHalf, heightHalf, z ); + vertices.push( widthHalf, heightHalf, z, widthHalf, - heightHalf, z ); + vertices.push( widthHalf, - heightHalf, z, - widthHalf, - heightHalf, z ); + + z += segmentDepth; + + } + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + } + +} + +export { BoxLineGeometry }; diff --git a/jsm/geometries/ConvexGeometry.js b/jsm/geometries/ConvexGeometry.js new file mode 100644 index 0000000..32dbe93 --- /dev/null +++ b/jsm/geometries/ConvexGeometry.js @@ -0,0 +1,59 @@ +import { + BufferGeometry, + Float32BufferAttribute +} from 'three'; +import { ConvexHull } from '../math/ConvexHull.js'; + +class ConvexGeometry extends BufferGeometry { + + constructor( points = [] ) { + + super(); + + // buffers + + const vertices = []; + const normals = []; + + if ( ConvexHull === undefined ) { + + console.error( 'THREE.ConvexBufferGeometry: ConvexBufferGeometry relies on ConvexHull' ); + + } + + const convexHull = new ConvexHull().setFromPoints( points ); + + // generate vertices and normals + + const faces = convexHull.faces; + + for ( let i = 0; i < faces.length; i ++ ) { + + const face = faces[ i ]; + let edge = face.edge; + + // we move along a doubly-connected edge list to access all face points (see HalfEdge docs) + + do { + + const point = edge.head().point; + + vertices.push( point.x, point.y, point.z ); + normals.push( face.normal.x, face.normal.y, face.normal.z ); + + edge = edge.next; + + } while ( edge !== face.edge ); + + } + + // build geometry + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + + } + +} + +export { ConvexGeometry }; diff --git a/jsm/geometries/DecalGeometry.js b/jsm/geometries/DecalGeometry.js new file mode 100644 index 0000000..dc0bea9 --- /dev/null +++ b/jsm/geometries/DecalGeometry.js @@ -0,0 +1,363 @@ +import { + BufferGeometry, + Float32BufferAttribute, + Matrix4, + Vector3 +} from 'three'; + +/** + * You can use this geometry to create a decal mesh, that serves different kinds of purposes. + * e.g. adding unique details to models, performing dynamic visual environmental changes or covering seams. + * + * Constructor parameter: + * + * mesh — Any mesh object + * position — Position of the decal projector + * orientation — Orientation of the decal projector + * size — Size of the decal projector + * + * reference: http://blog.wolfire.com/2009/06/how-to-project-decals/ + * + */ + +class DecalGeometry extends BufferGeometry { + + constructor( mesh, position, orientation, size ) { + + super(); + + // buffers + + const vertices = []; + const normals = []; + const uvs = []; + + // helpers + + const plane = new Vector3(); + + // this matrix represents the transformation of the decal projector + + const projectorMatrix = new Matrix4(); + projectorMatrix.makeRotationFromEuler( orientation ); + projectorMatrix.setPosition( position ); + + const projectorMatrixInverse = new Matrix4(); + projectorMatrixInverse.copy( projectorMatrix ).invert(); + + // generate buffers + + generate(); + + // build geometry + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + function generate() { + + let decalVertices = []; + + const vertex = new Vector3(); + const normal = new Vector3(); + + // handle different geometry types + + if ( mesh.geometry.isGeometry === true ) { + + console.error( 'THREE.DecalGeometry no longer supports THREE.Geometry. Use BufferGeometry instead.' ); + return; + + } + + const geometry = mesh.geometry; + + const positionAttribute = geometry.attributes.position; + const normalAttribute = geometry.attributes.normal; + + // first, create an array of 'DecalVertex' objects + // three consecutive 'DecalVertex' objects represent a single face + // + // this data structure will be later used to perform the clipping + + if ( geometry.index !== null ) { + + // indexed BufferGeometry + + const index = geometry.index; + + for ( let i = 0; i < index.count; i ++ ) { + + vertex.fromBufferAttribute( positionAttribute, index.getX( i ) ); + normal.fromBufferAttribute( normalAttribute, index.getX( i ) ); + + pushDecalVertex( decalVertices, vertex, normal ); + + } + + } else { + + // non-indexed BufferGeometry + + for ( let i = 0; i < positionAttribute.count; i ++ ) { + + vertex.fromBufferAttribute( positionAttribute, i ); + normal.fromBufferAttribute( normalAttribute, i ); + + pushDecalVertex( decalVertices, vertex, normal ); + + } + + } + + // second, clip the geometry so that it doesn't extend out from the projector + + decalVertices = clipGeometry( decalVertices, plane.set( 1, 0, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( - 1, 0, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 1, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, - 1, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, 1 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, - 1 ) ); + + // third, generate final vertices, normals and uvs + + for ( let i = 0; i < decalVertices.length; i ++ ) { + + const decalVertex = decalVertices[ i ]; + + // create texture coordinates (we are still in projector space) + + uvs.push( + 0.5 + ( decalVertex.position.x / size.x ), + 0.5 + ( decalVertex.position.y / size.y ) + ); + + // transform the vertex back to world space + + decalVertex.position.applyMatrix4( projectorMatrix ); + + // now create vertex and normal buffer data + + vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z ); + normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z ); + + } + + } + + function pushDecalVertex( decalVertices, vertex, normal ) { + + // transform the vertex to world space, then to projector space + + vertex.applyMatrix4( mesh.matrixWorld ); + vertex.applyMatrix4( projectorMatrixInverse ); + + normal.transformDirection( mesh.matrixWorld ); + + decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) ); + + } + + function clipGeometry( inVertices, plane ) { + + const outVertices = []; + + const s = 0.5 * Math.abs( size.dot( plane ) ); + + // a single iteration clips one face, + // which consists of three consecutive 'DecalVertex' objects + + for ( let i = 0; i < inVertices.length; i += 3 ) { + + let total = 0; + let nV1; + let nV2; + let nV3; + let nV4; + + const d1 = inVertices[ i + 0 ].position.dot( plane ) - s; + const d2 = inVertices[ i + 1 ].position.dot( plane ) - s; + const d3 = inVertices[ i + 2 ].position.dot( plane ) - s; + + const v1Out = d1 > 0; + const v2Out = d2 > 0; + const v3Out = d3 > 0; + + // calculate, how many vertices of the face lie outside of the clipping plane + + total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 ); + + switch ( total ) { + + case 0: { + + // the entire face lies inside of the plane, no clipping needed + + outVertices.push( inVertices[ i ] ); + outVertices.push( inVertices[ i + 1 ] ); + outVertices.push( inVertices[ i + 2 ] ); + break; + + } + + case 1: { + + // one vertex lies outside of the plane, perform clipping + + if ( v1Out ) { + + nV1 = inVertices[ i + 1 ]; + nV2 = inVertices[ i + 2 ]; + nV3 = clip( inVertices[ i ], nV1, plane, s ); + nV4 = clip( inVertices[ i ], nV2, plane, s ); + + } + + if ( v2Out ) { + + nV1 = inVertices[ i ]; + nV2 = inVertices[ i + 2 ]; + nV3 = clip( inVertices[ i + 1 ], nV1, plane, s ); + nV4 = clip( inVertices[ i + 1 ], nV2, plane, s ); + + outVertices.push( nV3 ); + outVertices.push( nV2.clone() ); + outVertices.push( nV1.clone() ); + + outVertices.push( nV2.clone() ); + outVertices.push( nV3.clone() ); + outVertices.push( nV4 ); + break; + + } + + if ( v3Out ) { + + nV1 = inVertices[ i ]; + nV2 = inVertices[ i + 1 ]; + nV3 = clip( inVertices[ i + 2 ], nV1, plane, s ); + nV4 = clip( inVertices[ i + 2 ], nV2, plane, s ); + + } + + outVertices.push( nV1.clone() ); + outVertices.push( nV2.clone() ); + outVertices.push( nV3 ); + + outVertices.push( nV4 ); + outVertices.push( nV3.clone() ); + outVertices.push( nV2.clone() ); + + break; + + } + + case 2: { + + // two vertices lies outside of the plane, perform clipping + + if ( ! v1Out ) { + + nV1 = inVertices[ i ].clone(); + nV2 = clip( nV1, inVertices[ i + 1 ], plane, s ); + nV3 = clip( nV1, inVertices[ i + 2 ], plane, s ); + outVertices.push( nV1 ); + outVertices.push( nV2 ); + outVertices.push( nV3 ); + + } + + if ( ! v2Out ) { + + nV1 = inVertices[ i + 1 ].clone(); + nV2 = clip( nV1, inVertices[ i + 2 ], plane, s ); + nV3 = clip( nV1, inVertices[ i ], plane, s ); + outVertices.push( nV1 ); + outVertices.push( nV2 ); + outVertices.push( nV3 ); + + } + + if ( ! v3Out ) { + + nV1 = inVertices[ i + 2 ].clone(); + nV2 = clip( nV1, inVertices[ i ], plane, s ); + nV3 = clip( nV1, inVertices[ i + 1 ], plane, s ); + outVertices.push( nV1 ); + outVertices.push( nV2 ); + outVertices.push( nV3 ); + + } + + break; + + } + + case 3: { + + // the entire face lies outside of the plane, so let's discard the corresponding vertices + + break; + + } + + } + + } + + return outVertices; + + } + + function clip( v0, v1, p, s ) { + + const d0 = v0.position.dot( p ) - s; + const d1 = v1.position.dot( p ) - s; + + const s0 = d0 / ( d0 - d1 ); + + const v = new DecalVertex( + new Vector3( + v0.position.x + s0 * ( v1.position.x - v0.position.x ), + v0.position.y + s0 * ( v1.position.y - v0.position.y ), + v0.position.z + s0 * ( v1.position.z - v0.position.z ) + ), + new Vector3( + v0.normal.x + s0 * ( v1.normal.x - v0.normal.x ), + v0.normal.y + s0 * ( v1.normal.y - v0.normal.y ), + v0.normal.z + s0 * ( v1.normal.z - v0.normal.z ) + ) + ); + + // need to clip more values (texture coordinates)? do it this way: + // intersectpoint.value = a.value + s * ( b.value - a.value ); + + return v; + + } + + } + +} + +// helper + +class DecalVertex { + + constructor( position, normal ) { + + this.position = position; + this.normal = normal; + + } + + clone() { + + return new this.constructor( this.position.clone(), this.normal.clone() ); + + } + +} + +export { DecalGeometry, DecalVertex }; diff --git a/jsm/geometries/LightningStrike.js b/jsm/geometries/LightningStrike.js new file mode 100644 index 0000000..b8b3353 --- /dev/null +++ b/jsm/geometries/LightningStrike.js @@ -0,0 +1,1017 @@ +import { + BufferGeometry, + DynamicDrawUsage, + Float32BufferAttribute, + MathUtils, + Uint32BufferAttribute, + Vector3 +} from 'three'; +import { SimplexNoise } from '../math/SimplexNoise.js'; + +/** + * @fileoverview LightningStrike object for creating lightning strikes and voltaic arcs. + * + * + * Usage + * + * var myRay = new LightningStrike( paramsObject ); + * var myRayMesh = new THREE.Mesh( myRay, myMaterial ); + * scene.add( myRayMesh ); + * ... + * myRay.update( currentTime ); + * + * The "currentTime" can vary its rate, go forwards, backwards or even jump, but it cannot be negative. + * + * You should normally leave the ray position to (0, 0, 0). You should control it by changing the sourceOffset and destOffset parameters. + * + * + * LightningStrike parameters + * + * The paramsObject can contain any of the following parameters. + * + * Legend: + * 'LightningStrike' (also called 'ray'): An independent voltaic arc with its ramifications and defined with a set of parameters. + * 'Subray': A ramification of the ray. It is not a LightningStrike object. + * 'Segment': A linear segment piece of a subray. + * 'Leaf segment': A ray segment which cannot be smaller. + * + * + * The following parameters can be changed any time and if they vary smoothly, the ray form will also change smoothly: + * + * @param {Vector3} sourceOffset The point where the ray starts. + * + * @param {Vector3} destOffset The point where the ray ends. + * + * @param {double} timeScale The rate at wich the ray form changes in time. Default: 1 + * + * @param {double} roughness From 0 to 1. The higher the value, the more wrinkled is the ray. Default: 0.9 + * + * @param {double} straightness From 0 to 1. The higher the value, the more straight will be a subray path. Default: 0.7 + * + * @param {Vector3} up0 Ray 'up' direction at the ray starting point. Must be normalized. It should be perpendicular to the ray forward direction but it doesn't matter much. + * + * @param {Vector3} up1 Like the up0 parameter but at the end of the ray. Must be normalized. + * + * @param {double} radius0 Radius of the main ray trunk at the start point. Default: 1 + * + * @param {double} radius1 Radius of the main ray trunk at the end point. Default: 1 + * + * @param {double} radius0Factor The radius0 of a subray is this factor times the radius0 of its parent subray. Default: 0.5 + * + * @param {double} radius1Factor The radius1 of a subray is this factor times the radius1 of its parent subray. Default: 0.2 + * + * @param {minRadius} Minimum value a subray radius0 or radius1 can get. Default: 0.1 + * + * + * The following parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: + * + * @param {boolean} isEternal If true the ray never extinguishes. Otherwise its life is controlled by the 'birthTime' and 'deathTime' parameters. Default: true if any of those two parameters is undefined. + * + * @param {double} birthTime The time at which the ray starts its life and begins propagating. Only if isEternal is false. Default: None. + * + * @param {double} deathTime The time at which the ray ends vanishing and its life. Only if isEternal is false. Default: None. + * + * @param {double} propagationTimeFactor From 0 to 1. Lifetime factor at which the ray ends propagating and enters the steady phase. For example, 0.1 means it is propagating 1/10 of its lifetime. Default: 0.1 + * + * @param {double} vanishingTimeFactor From 0 to 1. Lifetime factor at which the ray ends the steady phase and begins vanishing. For example, 0.9 means it is vanishing 1/10 of its lifetime. Default: 0.9 + * + * @param {double} subrayPeriod Subrays cycle periodically. This is their time period. Default: 4 + * + * @param {double} subrayDutyCycle From 0 to 1. This is the fraction of time a subray is active. Default: 0.6 + * + * + * These parameters cannot change after lightning creation: + * + * @param {integer} maxIterations: Greater than 0. The number of ray's leaf segments is 2**maxIterations. Default: 9 + * + * @param {boolean} isStatic Set to true only for rays which won't change over time and are not attached to moving objects (Rare case). It is used to set the vertex buffers non-dynamic. You can omit calling update() for these rays. + * + * @param {integer} ramification Greater than 0. Maximum number of child subrays a subray can have. Default: 5 + * + * @param {integer} maxSubrayRecursion Greater than 0. Maximum level of recursion (subray descendant generations). Default: 3 + * + * @param {double} recursionProbability From 0 to 1. The lower the value, the less chance each new generation of subrays has to generate new subrays. Default: 0.6 + * + * @param {boolean} generateUVs If true, the ray geometry will have uv coordinates generated. u runs along the ray, and v across its perimeter. Default: false. + * + * @param {Object} randomGenerator Set here your random number generator which will seed the SimplexNoise and other decisions during ray tree creation. + * It can be used to generate repeatable rays. For that, set also the noiseSeed parameter, and each ray created with that generator and seed pair will be identical in time. + * The randomGenerator parameter should be an object with a random() function similar to Math.random, but seedable. + * It must have also a getSeed() method, which returns the current seed, and a setSeed( seed ) method, which accepts as seed a fractional number from 0 to 1, as well as any other number. + * The default value is an internal generator for some uses and Math.random for others (It is non-repeatable even if noiseSeed is supplied) + * + * @param {double} noiseSeed Seed used to make repeatable rays (see the randomGenerator) + * + * @param {function} onDecideSubrayCreation Set this to change the callback which decides subray creation. You can look at the default callback in the code (createDefaultSubrayCreationCallbacks)for more info. + * + * @param {function} onSubrayCreation This is another callback, more simple than the previous one. It can be used to adapt the form of subrays or other parameters once a subray has been created and initialized. It is used in the examples to adapt subrays to a sphere or to a plane. + * + * +*/ + +class LightningStrike extends BufferGeometry { + + constructor( rayParameters = {} ) { + + super(); + + this.type = 'LightningStrike'; + + // Set parameters, and set undefined parameters to default values + this.init( LightningStrike.copyParameters( rayParameters, rayParameters ) ); + + // Creates and populates the mesh + this.createMesh(); + + } + + static createRandomGenerator() { + + const numSeeds = 2053; + const seeds = []; + + for ( let i = 0; i < numSeeds; i ++ ) { + + seeds.push( Math.random() ); + + } + + const generator = { + + currentSeed: 0, + + random: function () { + + const value = seeds[ generator.currentSeed ]; + + generator.currentSeed = ( generator.currentSeed + 1 ) % numSeeds; + + return value; + + }, + + getSeed: function () { + + return generator.currentSeed / numSeeds; + + }, + + setSeed: function ( seed ) { + + generator.currentSeed = Math.floor( seed * numSeeds ) % numSeeds; + + } + + }; + + return generator; + + } + + static copyParameters( dest = {}, source = {} ) { + + const vecCopy = function ( v ) { + + if ( source === dest ) { + + return v; + + } else { + + return v.clone(); + + } + + }; + + dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy( source.sourceOffset ) : new Vector3( 0, 100, 0 ), + dest.destOffset = source.destOffset !== undefined ? vecCopy( source.destOffset ) : new Vector3( 0, 0, 0 ), + + dest.timeScale = source.timeScale !== undefined ? source.timeScale : 1, + dest.roughness = source.roughness !== undefined ? source.roughness : 0.9, + dest.straightness = source.straightness !== undefined ? source.straightness : 0.7, + + dest.up0 = source.up0 !== undefined ? vecCopy( source.up0 ) : new Vector3( 0, 0, 1 ); + dest.up1 = source.up1 !== undefined ? vecCopy( source.up1 ) : new Vector3( 0, 0, 1 ), + dest.radius0 = source.radius0 !== undefined ? source.radius0 : 1, + dest.radius1 = source.radius1 !== undefined ? source.radius1 : 1, + dest.radius0Factor = source.radius0Factor !== undefined ? source.radius0Factor : 0.5, + dest.radius1Factor = source.radius1Factor !== undefined ? source.radius1Factor : 0.2, + dest.minRadius = source.minRadius !== undefined ? source.minRadius : 0.2, + + // These parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: + + dest.isEternal = source.isEternal !== undefined ? source.isEternal : ( source.birthTime === undefined || source.deathTime === undefined ), + dest.birthTime = source.birthTime, + dest.deathTime = source.deathTime, + dest.propagationTimeFactor = source.propagationTimeFactor !== undefined ? source.propagationTimeFactor : 0.1, + dest.vanishingTimeFactor = source.vanishingTimeFactor !== undefined ? source.vanishingTimeFactor : 0.9, + dest.subrayPeriod = source.subrayPeriod !== undefined ? source.subrayPeriod : 4, + dest.subrayDutyCycle = source.subrayDutyCycle !== undefined ? source.subrayDutyCycle : 0.6; + + // These parameters cannot change after lightning creation: + + dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9; + dest.isStatic = source.isStatic !== undefined ? source.isStatic : false; + dest.ramification = source.ramification !== undefined ? source.ramification : 5; + dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3; + dest.recursionProbability = source.recursionProbability !== undefined ? source.recursionProbability : 0.6; + dest.generateUVs = source.generateUVs !== undefined ? source.generateUVs : false; + dest.randomGenerator = source.randomGenerator, + dest.noiseSeed = source.noiseSeed, + dest.onDecideSubrayCreation = source.onDecideSubrayCreation, + dest.onSubrayCreation = source.onSubrayCreation; + + return dest; + + } + + update( time ) { + + if ( this.isStatic ) return; + + if ( this.rayParameters.isEternal || ( this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) ) { + + this.updateMesh( time ); + + if ( time < this.subrays[ 0 ].endPropagationTime ) { + + this.state = LightningStrike.RAY_PROPAGATING; + + } else if ( time > this.subrays[ 0 ].beginVanishingTime ) { + + this.state = LightningStrike.RAY_VANISHING; + + } else { + + this.state = LightningStrike.RAY_STEADY; + + } + + this.visible = true; + + } else { + + this.visible = false; + + if ( time < this.rayParameters.birthTime ) { + + this.state = LightningStrike.RAY_UNBORN; + + } else { + + this.state = LightningStrike.RAY_EXTINGUISHED; + + } + + } + + } + + init( rayParameters ) { + + // Init all the state from the parameters + + this.rayParameters = rayParameters; + + // These parameters cannot change after lightning creation: + + this.maxIterations = rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9; + rayParameters.maxIterations = this.maxIterations; + this.isStatic = rayParameters.isStatic !== undefined ? rayParameters.isStatic : false; + rayParameters.isStatic = this.isStatic; + this.ramification = rayParameters.ramification !== undefined ? Math.floor( rayParameters.ramification ) : 5; + rayParameters.ramification = this.ramification; + this.maxSubrayRecursion = rayParameters.maxSubrayRecursion !== undefined ? Math.floor( rayParameters.maxSubrayRecursion ) : 3; + rayParameters.maxSubrayRecursion = this.maxSubrayRecursion; + this.recursionProbability = rayParameters.recursionProbability !== undefined ? rayParameters.recursionProbability : 0.6; + rayParameters.recursionProbability = this.recursionProbability; + this.generateUVs = rayParameters.generateUVs !== undefined ? rayParameters.generateUVs : false; + rayParameters.generateUVs = this.generateUVs; + + // Random generator + if ( rayParameters.randomGenerator !== undefined ) { + + this.randomGenerator = rayParameters.randomGenerator; + this.seedGenerator = rayParameters.randomGenerator; + + if ( rayParameters.noiseSeed !== undefined ) { + + this.seedGenerator.setSeed( rayParameters.noiseSeed ); + + } + + } else { + + this.randomGenerator = LightningStrike.createRandomGenerator(); + this.seedGenerator = Math; + + } + + // Ray creation callbacks + if ( rayParameters.onDecideSubrayCreation !== undefined ) { + + this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation; + + } else { + + this.createDefaultSubrayCreationCallbacks(); + + if ( rayParameters.onSubrayCreation !== undefined ) { + + this.onSubrayCreation = rayParameters.onSubrayCreation; + + } + + } + + // Internal state + + this.state = LightningStrike.RAY_INITIALIZED; + + this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) ); + rayParameters.maxSubrays = this.maxSubrays; + + this.maxRaySegments = 2 * ( 1 << this.maxIterations ); + + this.subrays = []; + + for ( let i = 0; i < this.maxSubrays; i ++ ) { + + this.subrays.push( this.createSubray() ); + + } + + this.raySegments = []; + + for ( let i = 0; i < this.maxRaySegments; i ++ ) { + + this.raySegments.push( this.createSegment() ); + + } + + this.time = 0; + this.timeFraction = 0; + this.currentSegmentCallback = null; + this.currentCreateTriangleVertices = this.generateUVs ? this.createTriangleVerticesWithUVs : this.createTriangleVerticesWithoutUVs; + this.numSubrays = 0; + this.currentSubray = null; + this.currentSegmentIndex = 0; + this.isInitialSegment = false; + this.subrayProbability = 0; + + this.currentVertex = 0; + this.currentIndex = 0; + this.currentCoordinate = 0; + this.currentUVCoordinate = 0; + this.vertices = null; + this.uvs = null; + this.indices = null; + this.positionAttribute = null; + this.uvsAttribute = null; + + this.simplexX = new SimplexNoise( this.seedGenerator ); + this.simplexY = new SimplexNoise( this.seedGenerator ); + this.simplexZ = new SimplexNoise( this.seedGenerator ); + + // Temp vectors + this.forwards = new Vector3(); + this.forwardsFill = new Vector3(); + this.side = new Vector3(); + this.down = new Vector3(); + this.middlePos = new Vector3(); + this.middleLinPos = new Vector3(); + this.newPos = new Vector3(); + this.vPos = new Vector3(); + this.cross1 = new Vector3(); + + } + + createMesh() { + + const maxDrawableSegmentsPerSubRay = 1 << this.maxIterations; + + const maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays; + const maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays; + + this.vertices = new Float32Array( maxVerts * 3 ); + this.indices = new Uint32Array( maxIndices ); + + if ( this.generateUVs ) { + + this.uvs = new Float32Array( maxVerts * 2 ); + + } + + // Populate the mesh + this.fillMesh( 0 ); + + this.setIndex( new Uint32BufferAttribute( this.indices, 1 ) ); + + this.positionAttribute = new Float32BufferAttribute( this.vertices, 3 ); + this.setAttribute( 'position', this.positionAttribute ); + + if ( this.generateUVs ) { + + this.uvsAttribute = new Float32BufferAttribute( new Float32Array( this.uvs ), 2 ); + this.setAttribute( 'uv', this.uvsAttribute ); + + } + + if ( ! this.isStatic ) { + + this.index.usage = DynamicDrawUsage; + this.positionAttribute.usage = DynamicDrawUsage; + + if ( this.generateUVs ) { + + this.uvsAttribute.usage = DynamicDrawUsage; + + } + + } + + // Store buffers for later modification + this.vertices = this.positionAttribute.array; + this.indices = this.index.array; + + if ( this.generateUVs ) { + + this.uvs = this.uvsAttribute.array; + + } + + } + + updateMesh( time ) { + + this.fillMesh( time ); + + this.drawRange.count = this.currentIndex; + + this.index.needsUpdate = true; + + this.positionAttribute.needsUpdate = true; + + if ( this.generateUVs ) { + + this.uvsAttribute.needsUpdate = true; + + } + + } + + fillMesh( time ) { + + const scope = this; + + this.currentVertex = 0; + this.currentIndex = 0; + this.currentCoordinate = 0; + this.currentUVCoordinate = 0; + + this.fractalRay( time, function fillVertices( segment ) { + + const subray = scope.currentSubray; + + if ( time < subray.birthTime ) { //&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) { + + return; + + } else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) { + + // Eternal rays don't propagate nor vanish, but its subrays do + + scope.createPrism( segment ); + + scope.onDecideSubrayCreation( segment, scope ); + + } else if ( time < subray.endPropagationTime ) { + + if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) { + + // Ray propagation has arrived to this segment + + scope.createPrism( segment ); + + scope.onDecideSubrayCreation( segment, scope ); + + } + + } else if ( time < subray.beginVanishingTime ) { + + // Ray is steady (nor propagating nor vanishing) + + scope.createPrism( segment ); + + scope.onDecideSubrayCreation( segment, scope ); + + } else { + + if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) { + + // Segment has not yet vanished + + scope.createPrism( segment ); + + } + + scope.onDecideSubrayCreation( segment, scope ); + + } + + } ); + + } + + addNewSubray( /*rayParameters*/ ) { + + return this.subrays[ this.numSubrays ++ ]; + + } + + initSubray( subray, rayParameters ) { + + subray.pos0.copy( rayParameters.sourceOffset ); + subray.pos1.copy( rayParameters.destOffset ); + subray.up0.copy( rayParameters.up0 ); + subray.up1.copy( rayParameters.up1 ); + subray.radius0 = rayParameters.radius0; + subray.radius1 = rayParameters.radius1; + subray.birthTime = rayParameters.birthTime; + subray.deathTime = rayParameters.deathTime; + subray.timeScale = rayParameters.timeScale; + subray.roughness = rayParameters.roughness; + subray.straightness = rayParameters.straightness; + subray.propagationTimeFactor = rayParameters.propagationTimeFactor; + subray.vanishingTimeFactor = rayParameters.vanishingTimeFactor; + + subray.maxIterations = this.maxIterations; + subray.seed = rayParameters.noiseSeed !== undefined ? rayParameters.noiseSeed : 0; + subray.recursion = 0; + + } + + fractalRay( time, segmentCallback ) { + + this.time = time; + this.currentSegmentCallback = segmentCallback; + this.numSubrays = 0; + + // Add the top level subray + this.initSubray( this.addNewSubray(), this.rayParameters ); + + // Process all subrays that are being generated until consuming all of them + for ( let subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex ++ ) { + + const subray = this.subrays[ subrayIndex ]; + this.currentSubray = subray; + + this.randomGenerator.setSeed( subray.seed ); + + subray.endPropagationTime = MathUtils.lerp( subray.birthTime, subray.deathTime, subray.propagationTimeFactor ); + subray.beginVanishingTime = MathUtils.lerp( subray.deathTime, subray.birthTime, 1 - subray.vanishingTimeFactor ); + + const random1 = this.randomGenerator.random; + subray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + subray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + + this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime ); + + this.currentSegmentIndex = 0; + this.isInitialSegment = true; + + const segment = this.getNewSegment(); + segment.iteration = 0; + segment.pos0.copy( subray.pos0 ); + segment.pos1.copy( subray.pos1 ); + segment.linPos0.copy( subray.linPos0 ); + segment.linPos1.copy( subray.linPos1 ); + segment.up0.copy( subray.up0 ); + segment.up1.copy( subray.up1 ); + segment.radius0 = subray.radius0; + segment.radius1 = subray.radius1; + segment.fraction0 = 0; + segment.fraction1 = 1; + segment.positionVariationFactor = 1 - subray.straightness; + + this.subrayProbability = this.ramification * Math.pow( this.recursionProbability, subray.recursion ) / ( 1 << subray.maxIterations ); + + this.fractalRayRecursive( segment ); + + } + + this.currentSegmentCallback = null; + this.currentSubray = null; + + } + + fractalRayRecursive( segment ) { + + // Leave recursion condition + if ( segment.iteration >= this.currentSubray.maxIterations ) { + + this.currentSegmentCallback( segment ); + + return; + + } + + // Interpolation + this.forwards.subVectors( segment.pos1, segment.pos0 ); + let lForwards = this.forwards.length(); + + if ( lForwards < 0.000001 ) { + + this.forwards.set( 0, 0, 0.01 ); + lForwards = this.forwards.length(); + + } + + const middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5; + const middleFraction = ( segment.fraction0 + segment.fraction1 ) * 0.5; + + const timeDimension = this.time * this.currentSubray.timeScale * Math.pow( 2, segment.iteration ); + + this.middlePos.lerpVectors( segment.pos0, segment.pos1, 0.5 ); + this.middleLinPos.lerpVectors( segment.linPos0, segment.linPos1, 0.5 ); + const p = this.middleLinPos; + + // Noise + this.newPos.set( this.simplexX.noise4d( p.x, p.y, p.z, timeDimension ), + this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ), + this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) ); + + this.newPos.multiplyScalar( segment.positionVariationFactor * lForwards ); + this.newPos.add( this.middlePos ); + + // Recursion + + const newSegment1 = this.getNewSegment(); + newSegment1.pos0.copy( segment.pos0 ); + newSegment1.pos1.copy( this.newPos ); + newSegment1.linPos0.copy( segment.linPos0 ); + newSegment1.linPos1.copy( this.middleLinPos ); + newSegment1.up0.copy( segment.up0 ); + newSegment1.up1.copy( segment.up1 ); + newSegment1.radius0 = segment.radius0; + newSegment1.radius1 = middleRadius; + newSegment1.fraction0 = segment.fraction0; + newSegment1.fraction1 = middleFraction; + newSegment1.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; + newSegment1.iteration = segment.iteration + 1; + + const newSegment2 = this.getNewSegment(); + newSegment2.pos0.copy( this.newPos ); + newSegment2.pos1.copy( segment.pos1 ); + newSegment2.linPos0.copy( this.middleLinPos ); + newSegment2.linPos1.copy( segment.linPos1 ); + this.cross1.crossVectors( segment.up0, this.forwards.normalize() ); + newSegment2.up0.crossVectors( this.forwards, this.cross1 ).normalize(); + newSegment2.up1.copy( segment.up1 ); + newSegment2.radius0 = middleRadius; + newSegment2.radius1 = segment.radius1; + newSegment2.fraction0 = middleFraction; + newSegment2.fraction1 = segment.fraction1; + newSegment2.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; + newSegment2.iteration = segment.iteration + 1; + + this.fractalRayRecursive( newSegment1 ); + + this.fractalRayRecursive( newSegment2 ); + + } + + createPrism( segment ) { + + // Creates one triangular prism and its vertices at the segment + + this.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize(); + + if ( this.isInitialSegment ) { + + this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 ); + + this.isInitialSegment = false; + + } + + this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 ); + + this.createPrismFaces(); + + } + + createTriangleVerticesWithoutUVs( pos, up, forwards, radius ) { + + // Create an equilateral triangle (only vertices) + + this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG ); + this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG ); + + const p = this.vPos; + const v = this.vertices; + + p.copy( pos ).sub( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + p.copy( pos ).add( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + p.copy( up ).multiplyScalar( radius ).add( pos ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + this.currentVertex += 3; + + } + + createTriangleVerticesWithUVs( pos, up, forwards, radius, u ) { + + // Create an equilateral triangle (only vertices) + + this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG ); + this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG ); + + const p = this.vPos; + const v = this.vertices; + const uv = this.uvs; + + p.copy( pos ).sub( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + uv[ this.currentUVCoordinate ++ ] = u; + uv[ this.currentUVCoordinate ++ ] = 0; + + p.copy( pos ).add( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + uv[ this.currentUVCoordinate ++ ] = u; + uv[ this.currentUVCoordinate ++ ] = 0.5; + + p.copy( up ).multiplyScalar( radius ).add( pos ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + uv[ this.currentUVCoordinate ++ ] = u; + uv[ this.currentUVCoordinate ++ ] = 1; + + this.currentVertex += 3; + + } + + createPrismFaces( vertex/*, index*/ ) { + + const indices = this.indices; + vertex = this.currentVertex - 6; + + indices[ this.currentIndex ++ ] = vertex + 1; + indices[ this.currentIndex ++ ] = vertex + 2; + indices[ this.currentIndex ++ ] = vertex + 5; + indices[ this.currentIndex ++ ] = vertex + 1; + indices[ this.currentIndex ++ ] = vertex + 5; + indices[ this.currentIndex ++ ] = vertex + 4; + indices[ this.currentIndex ++ ] = vertex + 0; + indices[ this.currentIndex ++ ] = vertex + 1; + indices[ this.currentIndex ++ ] = vertex + 4; + indices[ this.currentIndex ++ ] = vertex + 0; + indices[ this.currentIndex ++ ] = vertex + 4; + indices[ this.currentIndex ++ ] = vertex + 3; + indices[ this.currentIndex ++ ] = vertex + 2; + indices[ this.currentIndex ++ ] = vertex + 0; + indices[ this.currentIndex ++ ] = vertex + 3; + indices[ this.currentIndex ++ ] = vertex + 2; + indices[ this.currentIndex ++ ] = vertex + 3; + indices[ this.currentIndex ++ ] = vertex + 5; + + } + + createDefaultSubrayCreationCallbacks() { + + const random1 = this.randomGenerator.random; + + this.onDecideSubrayCreation = function ( segment, lightningStrike ) { + + // Decide subrays creation at parent (sub)ray segment + + const subray = lightningStrike.currentSubray; + + const period = lightningStrike.rayParameters.subrayPeriod; + const dutyCycle = lightningStrike.rayParameters.subrayDutyCycle; + + const phase0 = ( lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) ? - random1() * period : MathUtils.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period; + + const phase = lightningStrike.time - phase0; + const currentCycle = Math.floor( phase / period ); + + const childSubraySeed = random1() * ( currentCycle + 1 ); + + const isActive = phase % period <= dutyCycle * period; + + let probability = 0; + + if ( isActive ) { + + probability = lightningStrike.subrayProbability; + // Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0; + + } + + if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) { + + const childSubray = lightningStrike.addNewSubray(); + + const parentSeed = lightningStrike.randomGenerator.getSeed(); + childSubray.seed = childSubraySeed; + lightningStrike.randomGenerator.setSeed( childSubraySeed ); + + childSubray.recursion = subray.recursion + 1; + childSubray.maxIterations = Math.max( 1, subray.maxIterations - 1 ); + + childSubray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + childSubray.up0.copy( subray.up0 ); + childSubray.up1.copy( subray.up1 ); + childSubray.radius0 = segment.radius0 * lightningStrike.rayParameters.radius0Factor; + childSubray.radius1 = Math.min( lightningStrike.rayParameters.minRadius, segment.radius1 * lightningStrike.rayParameters.radius1Factor ); + + childSubray.birthTime = phase0 + ( currentCycle ) * period; + childSubray.deathTime = childSubray.birthTime + period * dutyCycle; + + if ( ! lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) { + + childSubray.birthTime = Math.max( childSubray.birthTime, subray.birthTime ); + childSubray.deathTime = Math.min( childSubray.deathTime, subray.deathTime ); + + } + + childSubray.timeScale = subray.timeScale * 2; + childSubray.roughness = subray.roughness; + childSubray.straightness = subray.straightness; + childSubray.propagationTimeFactor = subray.propagationTimeFactor; + childSubray.vanishingTimeFactor = subray.vanishingTimeFactor; + + lightningStrike.onSubrayCreation( segment, subray, childSubray, lightningStrike ); + + lightningStrike.randomGenerator.setSeed( parentSeed ); + + } + + }; + + const vec1Pos = new Vector3(); + const vec2Forward = new Vector3(); + const vec3Side = new Vector3(); + const vec4Up = new Vector3(); + + this.onSubrayCreation = function ( segment, parentSubray, childSubray, lightningStrike ) { + + // Decide childSubray origin and destination positions (pos0 and pos1) and possibly other properties of childSubray + + // Just use the default cone position generator + lightningStrike.subrayCylinderPosition( segment, parentSubray, childSubray, 0.5, 0.6, 0.2 ); + + }; + + this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { + + // Sets childSubray pos0 and pos1 in a cone + + childSubray.pos0.copy( segment.pos0 ); + + vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); + vec2Forward.copy( vec1Pos ).normalize(); + vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( random1() * heightFactor ) ); + const length = vec1Pos.length(); + vec3Side.crossVectors( parentSubray.up0, vec2Forward ); + const angle = 2 * Math.PI * random1(); + vec3Side.multiplyScalar( Math.cos( angle ) ); + vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) ); + + childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); + + }; + + this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { + + // Sets childSubray pos0 and pos1 in a cylinder + + childSubray.pos0.copy( segment.pos0 ); + + vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); + vec2Forward.copy( vec1Pos ).normalize(); + vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( ( 2 * random1() - 1 ) * heightFactor ) ); + const length = vec1Pos.length(); + vec3Side.crossVectors( parentSubray.up0, vec2Forward ); + const angle = 2 * Math.PI * random1(); + vec3Side.multiplyScalar( Math.cos( angle ) ); + vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) ); + + childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); + + }; + + } + + createSubray() { + + return { + + seed: 0, + maxIterations: 0, + recursion: 0, + pos0: new Vector3(), + pos1: new Vector3(), + linPos0: new Vector3(), + linPos1: new Vector3(), + up0: new Vector3(), + up1: new Vector3(), + radius0: 0, + radius1: 0, + birthTime: 0, + deathTime: 0, + timeScale: 0, + roughness: 0, + straightness: 0, + propagationTimeFactor: 0, + vanishingTimeFactor: 0, + endPropagationTime: 0, + beginVanishingTime: 0 + + }; + + } + + createSegment() { + + return { + iteration: 0, + pos0: new Vector3(), + pos1: new Vector3(), + linPos0: new Vector3(), + linPos1: new Vector3(), + up0: new Vector3(), + up1: new Vector3(), + radius0: 0, + radius1: 0, + fraction0: 0, + fraction1: 0, + positionVariationFactor: 0 + }; + + } + + getNewSegment() { + + return this.raySegments[ this.currentSegmentIndex ++ ]; + + } + + copy( source ) { + + super.copy( source ); + + this.init( LightningStrike.copyParameters( {}, source.rayParameters ) ); + + return this; + + } + + clone() { + + return new this.constructor( LightningStrike.copyParameters( {}, this.rayParameters ) ); + + } + +} + +LightningStrike.prototype.isLightningStrike = true; + +// Ray states +LightningStrike.RAY_INITIALIZED = 0; +LightningStrike.RAY_UNBORN = 1; +LightningStrike.RAY_PROPAGATING = 2; +LightningStrike.RAY_STEADY = 3; +LightningStrike.RAY_VANISHING = 4; +LightningStrike.RAY_EXTINGUISHED = 5; + +LightningStrike.COS30DEG = Math.cos( 30 * Math.PI / 180 ); +LightningStrike.SIN30DEG = Math.sin( 30 * Math.PI / 180 ); + +export { LightningStrike }; diff --git a/jsm/geometries/ParametricGeometries.js b/jsm/geometries/ParametricGeometries.js new file mode 100644 index 0000000..6716735 --- /dev/null +++ b/jsm/geometries/ParametricGeometries.js @@ -0,0 +1,254 @@ +import { + Curve, + Vector3 +} from 'three'; + +import { ParametricGeometry } from './ParametricGeometry.js'; + +/** + * Experimenting of primitive geometry creation using Surface Parametric equations + */ + +const ParametricGeometries = { + + klein: function ( v, u, target ) { + + u *= Math.PI; + v *= 2 * Math.PI; + + u = u * 2; + let x, z; + if ( u < Math.PI ) { + + x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( u ) * Math.cos( v ); + z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v ); + + } else { + + x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( v + Math.PI ); + z = - 8 * Math.sin( u ); + + } + + const y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v ); + + target.set( x, y, z ); + + }, + + plane: function ( width, height ) { + + return function ( u, v, target ) { + + const x = u * width; + const y = 0; + const z = v * height; + + target.set( x, y, z ); + + }; + + }, + + mobius: function ( u, t, target ) { + + // flat mobius strip + // http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations- + u = u - 0.5; + const v = 2 * Math.PI * t; + + const a = 2; + + const x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) ); + const y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) ); + const z = u * Math.sin( v / 2 ); + + target.set( x, y, z ); + + }, + + mobius3d: function ( u, t, target ) { + + // volumetric mobius strip + + u *= Math.PI; + t *= 2 * Math.PI; + + u = u * 2; + const phi = u / 2; + const major = 2.25, a = 0.125, b = 0.65; + + let x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi ); + const z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi ); + const y = ( major + x ) * Math.sin( u ); + x = ( major + x ) * Math.cos( u ); + + target.set( x, y, z ); + + } + +}; + + +/********************************************* + * + * Parametric Replacement for TubeGeometry + * + *********************************************/ + +ParametricGeometries.TubeGeometry = class TubeGeometry extends ParametricGeometry { + + constructor( path, segments = 64, radius = 1, segmentsRadius = 8, closed = false ) { + + const numpoints = segments + 1; + + const frames = path.computeFrenetFrames( segments, closed ), + tangents = frames.tangents, + normals = frames.normals, + binormals = frames.binormals; + + const position = new Vector3(); + + function ParametricTube( u, v, target ) { + + v *= 2 * Math.PI; + + const i = Math.floor( u * ( numpoints - 1 ) ); + + path.getPointAt( u, position ); + + const normal = normals[ i ]; + const binormal = binormals[ i ]; + + const cx = - radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + const cy = radius * Math.sin( v ); + + position.x += cx * normal.x + cy * binormal.x; + position.y += cx * normal.y + cy * binormal.y; + position.z += cx * normal.z + cy * binormal.z; + + target.copy( position ); + + } + + super( ParametricTube, segments, segmentsRadius ); + + // proxy internals + + this.tangents = tangents; + this.normals = normals; + this.binormals = binormals; + + this.path = path; + this.segments = segments; + this.radius = radius; + this.segmentsRadius = segmentsRadius; + this.closed = closed; + + } + +}; + + +/********************************************* + * + * Parametric Replacement for TorusKnotGeometry + * + *********************************************/ +ParametricGeometries.TorusKnotGeometry = class TorusKnotGeometry extends ParametricGeometries.TubeGeometry { + + constructor( radius = 200, tube = 40, segmentsT = 64, segmentsR = 8, p = 2, q = 3 ) { + + class TorusKnotCurve extends Curve { + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= Math.PI * 2; + + const r = 0.5; + + const x = ( 1 + r * Math.cos( q * t ) ) * Math.cos( p * t ); + const y = ( 1 + r * Math.cos( q * t ) ) * Math.sin( p * t ); + const z = r * Math.sin( q * t ); + + return point.set( x, y, z ).multiplyScalar( radius ); + + } + + } + + const segments = segmentsT; + const radiusSegments = segmentsR; + const extrudePath = new TorusKnotCurve(); + + super( extrudePath, segments, tube, radiusSegments, true, false ); + + this.radius = radius; + this.tube = tube; + this.segmentsT = segmentsT; + this.segmentsR = segmentsR; + this.p = p; + this.q = q; + + } + +}; + +/********************************************* + * + * Parametric Replacement for SphereGeometry + * + *********************************************/ +ParametricGeometries.SphereGeometry = class SphereGeometry extends ParametricGeometry { + + constructor( size, u, v ) { + + function sphere( u, v, target ) { + + u *= Math.PI; + v *= 2 * Math.PI; + + const x = size * Math.sin( u ) * Math.cos( v ); + const y = size * Math.sin( u ) * Math.sin( v ); + const z = size * Math.cos( u ); + + target.set( x, y, z ); + + } + + super( sphere, u, v ); + + } + +}; + + +/********************************************* + * + * Parametric Replacement for PlaneGeometry + * + *********************************************/ + +ParametricGeometries.PlaneGeometry = class PlaneGeometry extends ParametricGeometry { + + constructor( width, depth, segmentsWidth, segmentsDepth ) { + + function plane( u, v, target ) { + + const x = u * width; + const y = 0; + const z = v * depth; + + target.set( x, y, z ); + + } + + super( plane, segmentsWidth, segmentsDepth ); + + } + +}; + +export { ParametricGeometries }; diff --git a/jsm/geometries/ParametricGeometry.js b/jsm/geometries/ParametricGeometry.js new file mode 100644 index 0000000..33166f8 --- /dev/null +++ b/jsm/geometries/ParametricGeometry.js @@ -0,0 +1,135 @@ +/** + * Parametric Surfaces Geometry + * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html + */ + +import { + BufferGeometry, + Float32BufferAttribute, + Vector3 +} from 'three'; + +class ParametricGeometry extends BufferGeometry { + + constructor( func = ( u, v, target ) => target.set( u, v, Math.cos( u ) * Math.sin( v ) ), slices = 8, stacks = 8 ) { + + super(); + + this.type = 'ParametricGeometry'; + + this.parameters = { + func: func, + slices: slices, + stacks: stacks + }; + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + const EPS = 0.00001; + + const normal = new Vector3(); + + const p0 = new Vector3(), p1 = new Vector3(); + const pu = new Vector3(), pv = new Vector3(); + + if ( func.length < 3 ) { + + console.error( 'THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.' ); + + } + + // generate vertices, normals and uvs + + const sliceCount = slices + 1; + + for ( let i = 0; i <= stacks; i ++ ) { + + const v = i / stacks; + + for ( let j = 0; j <= slices; j ++ ) { + + const u = j / slices; + + // vertex + + func( u, v, p0 ); + vertices.push( p0.x, p0.y, p0.z ); + + // normal + + // approximate tangent vectors via finite differences + + if ( u - EPS >= 0 ) { + + func( u - EPS, v, p1 ); + pu.subVectors( p0, p1 ); + + } else { + + func( u + EPS, v, p1 ); + pu.subVectors( p1, p0 ); + + } + + if ( v - EPS >= 0 ) { + + func( u, v - EPS, p1 ); + pv.subVectors( p0, p1 ); + + } else { + + func( u, v + EPS, p1 ); + pv.subVectors( p1, p0 ); + + } + + // cross product of tangent vectors returns surface normal + + normal.crossVectors( pu, pv ).normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u, v ); + + } + + } + + // generate indices + + for ( let i = 0; i < stacks; i ++ ) { + + for ( let j = 0; j < slices; j ++ ) { + + const a = i * sliceCount + j; + const b = i * sliceCount + j + 1; + const c = ( i + 1 ) * sliceCount + j + 1; + const d = ( i + 1 ) * sliceCount + j; + + // faces one and two + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + +} + +export { ParametricGeometry }; diff --git a/jsm/geometries/RoundedBoxGeometry.js b/jsm/geometries/RoundedBoxGeometry.js new file mode 100644 index 0000000..8baa168 --- /dev/null +++ b/jsm/geometries/RoundedBoxGeometry.js @@ -0,0 +1,155 @@ +import { + BoxGeometry, + Vector3 +} from 'three'; + +const _tempNormal = new Vector3(); + +function getUv( faceDirVector, normal, uvAxis, projectionAxis, radius, sideLength ) { + + const totArcLength = 2 * Math.PI * radius / 4; + + // length of the planes between the arcs on each axis + const centerLength = Math.max( sideLength - 2 * radius, 0 ); + const halfArc = Math.PI / 4; + + // Get the vector projected onto the Y plane + _tempNormal.copy( normal ); + _tempNormal[ projectionAxis ] = 0; + _tempNormal.normalize(); + + // total amount of UV space alloted to a single arc + const arcUvRatio = 0.5 * totArcLength / ( totArcLength + centerLength ); + + // the distance along one arc the point is at + const arcAngleRatio = 1.0 - ( _tempNormal.angleTo( faceDirVector ) / halfArc ); + + if ( Math.sign( _tempNormal[ uvAxis ] ) === 1 ) { + + return arcAngleRatio * arcUvRatio; + + } else { + + // total amount of UV space alloted to the plane between the arcs + const lenUv = centerLength / ( totArcLength + centerLength ); + return lenUv + arcUvRatio + arcUvRatio * ( 1.0 - arcAngleRatio ); + + } + +} + +class RoundedBoxGeometry extends BoxGeometry { + + constructor( width = 1, height = 1, depth = 1, segments = 2, radius = 0.1 ) { + + // ensure segments is odd so we have a plane connecting the rounded corners + segments = segments * 2 + 1; + + // ensure radius isn't bigger than shortest side + radius = Math.min( width / 2, height / 2, depth / 2, radius ); + + super( 1, 1, 1, segments, segments, segments ); + + // if we just have one segment we're the same as a regular box + if ( segments === 1 ) return; + + const geometry2 = this.toNonIndexed(); + + this.index = null; + this.attributes.position = geometry2.attributes.position; + this.attributes.normal = geometry2.attributes.normal; + this.attributes.uv = geometry2.attributes.uv; + + // + + const position = new Vector3(); + const normal = new Vector3(); + + const box = new Vector3( width, height, depth ).divideScalar( 2 ).subScalar( radius ); + + const positions = this.attributes.position.array; + const normals = this.attributes.normal.array; + const uvs = this.attributes.uv.array; + + const faceTris = positions.length / 6; + const faceDirVector = new Vector3(); + const halfSegmentSize = 0.5 / segments; + + for ( let i = 0, j = 0; i < positions.length; i += 3, j += 2 ) { + + position.fromArray( positions, i ); + normal.copy( position ); + normal.x -= Math.sign( normal.x ) * halfSegmentSize; + normal.y -= Math.sign( normal.y ) * halfSegmentSize; + normal.z -= Math.sign( normal.z ) * halfSegmentSize; + normal.normalize(); + + positions[ i + 0 ] = box.x * Math.sign( position.x ) + normal.x * radius; + positions[ i + 1 ] = box.y * Math.sign( position.y ) + normal.y * radius; + positions[ i + 2 ] = box.z * Math.sign( position.z ) + normal.z * radius; + + normals[ i + 0 ] = normal.x; + normals[ i + 1 ] = normal.y; + normals[ i + 2 ] = normal.z; + + const side = Math.floor( i / faceTris ); + + switch ( side ) { + + case 0: // right + + // generate UVs along Z then Y + faceDirVector.set( 1, 0, 0 ); + uvs[ j + 0 ] = getUv( faceDirVector, normal, 'z', 'y', radius, depth ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height ); + break; + + case 1: // left + + // generate UVs along Z then Y + faceDirVector.set( - 1, 0, 0 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'y', radius, depth ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height ); + break; + + case 2: // top + + // generate UVs along X then Z + faceDirVector.set( 0, 1, 0 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width ); + uvs[ j + 1 ] = getUv( faceDirVector, normal, 'z', 'x', radius, depth ); + break; + + case 3: // bottom + + // generate UVs along X then Z + faceDirVector.set( 0, - 1, 0 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'x', radius, depth ); + break; + + case 4: // front + + // generate UVs along X then Y + faceDirVector.set( 0, 0, 1 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'y', radius, width ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height ); + break; + + case 5: // back + + // generate UVs along X then Y + faceDirVector.set( 0, 0, - 1 ); + uvs[ j + 0 ] = getUv( faceDirVector, normal, 'x', 'y', radius, width ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height ); + break; + + } + + } + + } + +} + +export { RoundedBoxGeometry }; diff --git a/jsm/geometries/TeapotGeometry.js b/jsm/geometries/TeapotGeometry.js new file mode 100644 index 0000000..b6b5ff1 --- /dev/null +++ b/jsm/geometries/TeapotGeometry.js @@ -0,0 +1,704 @@ +import { + BufferAttribute, + BufferGeometry, + Matrix4, + Vector3, + Vector4 +} from 'three'; + +/** + * Tessellates the famous Utah teapot database by Martin Newell into triangles. + * + * Parameters: size = 50, segments = 10, bottom = true, lid = true, body = true, + * fitLid = false, blinn = true + * + * size is a relative scale: I've scaled the teapot to fit vertically between -1 and 1. + * Think of it as a "radius". + * segments - number of line segments to subdivide each patch edge; + * 1 is possible but gives degenerates, so two is the real minimum. + * bottom - boolean, if true (default) then the bottom patches are added. Some consider + * adding the bottom heresy, so set this to "false" to adhere to the One True Way. + * lid - to remove the lid and look inside, set to true. + * body - to remove the body and leave the lid, set this and "bottom" to false. + * fitLid - the lid is a tad small in the original. This stretches it a bit so you can't + * see the teapot's insides through the gap. + * blinn - Jim Blinn scaled the original data vertically by dividing by about 1.3 to look + * nicer. If you want to see the original teapot, similar to the real-world model, set + * this to false. True by default. + * See http://en.wikipedia.org/wiki/File:Original_Utah_Teapot.jpg for the original + * real-world teapot (from http://en.wikipedia.org/wiki/Utah_teapot). + * + * Note that the bottom (the last four patches) is not flat - blame Frank Crow, not me. + * + * The teapot should normally be rendered as a double sided object, since for some + * patches both sides can be seen, e.g., the gap around the lid and inside the spout. + * + * Segments 'n' determines the number of triangles output. + * Total triangles = 32*2*n*n - 8*n [degenerates at the top and bottom cusps are deleted] + * + * size_factor # triangles + * 1 56 + * 2 240 + * 3 552 + * 4 992 + * + * 10 6320 + * 20 25440 + * 30 57360 + * + * Code converted from my ancient SPD software, http://tog.acm.org/resources/SPD/ + * Created for the Udacity course "Interactive Rendering", http://bit.ly/ericity + * YouTube video on teapot history: https://www.youtube.com/watch?v=DxMfblPzFNc + * + * See https://en.wikipedia.org/wiki/Utah_teapot for the history of the teapot + * + */ + +class TeapotGeometry extends BufferGeometry { + + constructor( size = 50, segments = 10, bottom = true, lid = true, body = true, fitLid = true, blinn = true ) { + + // 32 * 4 * 4 Bezier spline patches + const teapotPatches = [ + /*rim*/ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 3, 16, 17, 18, 7, 19, 20, 21, 11, 22, 23, 24, 15, 25, 26, 27, + 18, 28, 29, 30, 21, 31, 32, 33, 24, 34, 35, 36, 27, 37, 38, 39, + 30, 40, 41, 0, 33, 42, 43, 4, 36, 44, 45, 8, 39, 46, 47, 12, + /*body*/ + 12, 13, 14, 15, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 15, 25, 26, 27, 51, 60, 61, 62, 55, 63, 64, 65, 59, 66, 67, 68, + 27, 37, 38, 39, 62, 69, 70, 71, 65, 72, 73, 74, 68, 75, 76, 77, + 39, 46, 47, 12, 71, 78, 79, 48, 74, 80, 81, 52, 77, 82, 83, 56, + 56, 57, 58, 59, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 59, 66, 67, 68, 87, 96, 97, 98, 91, 99, 100, 101, 95, 102, 103, 104, + 68, 75, 76, 77, 98, 105, 106, 107, 101, 108, 109, 110, 104, 111, 112, 113, + 77, 82, 83, 56, 107, 114, 115, 84, 110, 116, 117, 88, 113, 118, 119, 92, + /*handle*/ + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + 123, 136, 137, 120, 127, 138, 139, 124, 131, 140, 141, 128, 135, 142, 143, 132, + 132, 133, 134, 135, 144, 145, 146, 147, 148, 149, 150, 151, 68, 152, 153, 154, + 135, 142, 143, 132, 147, 155, 156, 144, 151, 157, 158, 148, 154, 159, 160, 68, + /*spout*/ + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, + 164, 177, 178, 161, 168, 179, 180, 165, 172, 181, 182, 169, 176, 183, 184, 173, + 173, 174, 175, 176, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, + 176, 183, 184, 173, 188, 197, 198, 185, 192, 199, 200, 189, 196, 201, 202, 193, + /*lid*/ + 203, 203, 203, 203, 204, 205, 206, 207, 208, 208, 208, 208, 209, 210, 211, 212, + 203, 203, 203, 203, 207, 213, 214, 215, 208, 208, 208, 208, 212, 216, 217, 218, + 203, 203, 203, 203, 215, 219, 220, 221, 208, 208, 208, 208, 218, 222, 223, 224, + 203, 203, 203, 203, 221, 225, 226, 204, 208, 208, 208, 208, 224, 227, 228, 209, + 209, 210, 211, 212, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, + 212, 216, 217, 218, 232, 241, 242, 243, 236, 244, 245, 246, 240, 247, 248, 249, + 218, 222, 223, 224, 243, 250, 251, 252, 246, 253, 254, 255, 249, 256, 257, 258, + 224, 227, 228, 209, 252, 259, 260, 229, 255, 261, 262, 233, 258, 263, 264, 237, + /*bottom*/ + 265, 265, 265, 265, 266, 267, 268, 269, 270, 271, 272, 273, 92, 119, 118, 113, + 265, 265, 265, 265, 269, 274, 275, 276, 273, 277, 278, 279, 113, 112, 111, 104, + 265, 265, 265, 265, 276, 280, 281, 282, 279, 283, 284, 285, 104, 103, 102, 95, + 265, 265, 265, 265, 282, 286, 287, 266, 285, 288, 289, 270, 95, 94, 93, 92 + ]; + + const teapotVertices = [ + 1.4, 0, 2.4, + 1.4, - 0.784, 2.4, + 0.784, - 1.4, 2.4, + 0, - 1.4, 2.4, + 1.3375, 0, 2.53125, + 1.3375, - 0.749, 2.53125, + 0.749, - 1.3375, 2.53125, + 0, - 1.3375, 2.53125, + 1.4375, 0, 2.53125, + 1.4375, - 0.805, 2.53125, + 0.805, - 1.4375, 2.53125, + 0, - 1.4375, 2.53125, + 1.5, 0, 2.4, + 1.5, - 0.84, 2.4, + 0.84, - 1.5, 2.4, + 0, - 1.5, 2.4, + - 0.784, - 1.4, 2.4, + - 1.4, - 0.784, 2.4, + - 1.4, 0, 2.4, + - 0.749, - 1.3375, 2.53125, + - 1.3375, - 0.749, 2.53125, + - 1.3375, 0, 2.53125, + - 0.805, - 1.4375, 2.53125, + - 1.4375, - 0.805, 2.53125, + - 1.4375, 0, 2.53125, + - 0.84, - 1.5, 2.4, + - 1.5, - 0.84, 2.4, + - 1.5, 0, 2.4, + - 1.4, 0.784, 2.4, + - 0.784, 1.4, 2.4, + 0, 1.4, 2.4, + - 1.3375, 0.749, 2.53125, + - 0.749, 1.3375, 2.53125, + 0, 1.3375, 2.53125, + - 1.4375, 0.805, 2.53125, + - 0.805, 1.4375, 2.53125, + 0, 1.4375, 2.53125, + - 1.5, 0.84, 2.4, + - 0.84, 1.5, 2.4, + 0, 1.5, 2.4, + 0.784, 1.4, 2.4, + 1.4, 0.784, 2.4, + 0.749, 1.3375, 2.53125, + 1.3375, 0.749, 2.53125, + 0.805, 1.4375, 2.53125, + 1.4375, 0.805, 2.53125, + 0.84, 1.5, 2.4, + 1.5, 0.84, 2.4, + 1.75, 0, 1.875, + 1.75, - 0.98, 1.875, + 0.98, - 1.75, 1.875, + 0, - 1.75, 1.875, + 2, 0, 1.35, + 2, - 1.12, 1.35, + 1.12, - 2, 1.35, + 0, - 2, 1.35, + 2, 0, 0.9, + 2, - 1.12, 0.9, + 1.12, - 2, 0.9, + 0, - 2, 0.9, + - 0.98, - 1.75, 1.875, + - 1.75, - 0.98, 1.875, + - 1.75, 0, 1.875, + - 1.12, - 2, 1.35, + - 2, - 1.12, 1.35, + - 2, 0, 1.35, + - 1.12, - 2, 0.9, + - 2, - 1.12, 0.9, + - 2, 0, 0.9, + - 1.75, 0.98, 1.875, + - 0.98, 1.75, 1.875, + 0, 1.75, 1.875, + - 2, 1.12, 1.35, + - 1.12, 2, 1.35, + 0, 2, 1.35, + - 2, 1.12, 0.9, + - 1.12, 2, 0.9, + 0, 2, 0.9, + 0.98, 1.75, 1.875, + 1.75, 0.98, 1.875, + 1.12, 2, 1.35, + 2, 1.12, 1.35, + 1.12, 2, 0.9, + 2, 1.12, 0.9, + 2, 0, 0.45, + 2, - 1.12, 0.45, + 1.12, - 2, 0.45, + 0, - 2, 0.45, + 1.5, 0, 0.225, + 1.5, - 0.84, 0.225, + 0.84, - 1.5, 0.225, + 0, - 1.5, 0.225, + 1.5, 0, 0.15, + 1.5, - 0.84, 0.15, + 0.84, - 1.5, 0.15, + 0, - 1.5, 0.15, + - 1.12, - 2, 0.45, + - 2, - 1.12, 0.45, + - 2, 0, 0.45, + - 0.84, - 1.5, 0.225, + - 1.5, - 0.84, 0.225, + - 1.5, 0, 0.225, + - 0.84, - 1.5, 0.15, + - 1.5, - 0.84, 0.15, + - 1.5, 0, 0.15, + - 2, 1.12, 0.45, + - 1.12, 2, 0.45, + 0, 2, 0.45, + - 1.5, 0.84, 0.225, + - 0.84, 1.5, 0.225, + 0, 1.5, 0.225, + - 1.5, 0.84, 0.15, + - 0.84, 1.5, 0.15, + 0, 1.5, 0.15, + 1.12, 2, 0.45, + 2, 1.12, 0.45, + 0.84, 1.5, 0.225, + 1.5, 0.84, 0.225, + 0.84, 1.5, 0.15, + 1.5, 0.84, 0.15, + - 1.6, 0, 2.025, + - 1.6, - 0.3, 2.025, + - 1.5, - 0.3, 2.25, + - 1.5, 0, 2.25, + - 2.3, 0, 2.025, + - 2.3, - 0.3, 2.025, + - 2.5, - 0.3, 2.25, + - 2.5, 0, 2.25, + - 2.7, 0, 2.025, + - 2.7, - 0.3, 2.025, + - 3, - 0.3, 2.25, + - 3, 0, 2.25, + - 2.7, 0, 1.8, + - 2.7, - 0.3, 1.8, + - 3, - 0.3, 1.8, + - 3, 0, 1.8, + - 1.5, 0.3, 2.25, + - 1.6, 0.3, 2.025, + - 2.5, 0.3, 2.25, + - 2.3, 0.3, 2.025, + - 3, 0.3, 2.25, + - 2.7, 0.3, 2.025, + - 3, 0.3, 1.8, + - 2.7, 0.3, 1.8, + - 2.7, 0, 1.575, + - 2.7, - 0.3, 1.575, + - 3, - 0.3, 1.35, + - 3, 0, 1.35, + - 2.5, 0, 1.125, + - 2.5, - 0.3, 1.125, + - 2.65, - 0.3, 0.9375, + - 2.65, 0, 0.9375, + - 2, - 0.3, 0.9, + - 1.9, - 0.3, 0.6, + - 1.9, 0, 0.6, + - 3, 0.3, 1.35, + - 2.7, 0.3, 1.575, + - 2.65, 0.3, 0.9375, + - 2.5, 0.3, 1.125, + - 1.9, 0.3, 0.6, + - 2, 0.3, 0.9, + 1.7, 0, 1.425, + 1.7, - 0.66, 1.425, + 1.7, - 0.66, 0.6, + 1.7, 0, 0.6, + 2.6, 0, 1.425, + 2.6, - 0.66, 1.425, + 3.1, - 0.66, 0.825, + 3.1, 0, 0.825, + 2.3, 0, 2.1, + 2.3, - 0.25, 2.1, + 2.4, - 0.25, 2.025, + 2.4, 0, 2.025, + 2.7, 0, 2.4, + 2.7, - 0.25, 2.4, + 3.3, - 0.25, 2.4, + 3.3, 0, 2.4, + 1.7, 0.66, 0.6, + 1.7, 0.66, 1.425, + 3.1, 0.66, 0.825, + 2.6, 0.66, 1.425, + 2.4, 0.25, 2.025, + 2.3, 0.25, 2.1, + 3.3, 0.25, 2.4, + 2.7, 0.25, 2.4, + 2.8, 0, 2.475, + 2.8, - 0.25, 2.475, + 3.525, - 0.25, 2.49375, + 3.525, 0, 2.49375, + 2.9, 0, 2.475, + 2.9, - 0.15, 2.475, + 3.45, - 0.15, 2.5125, + 3.45, 0, 2.5125, + 2.8, 0, 2.4, + 2.8, - 0.15, 2.4, + 3.2, - 0.15, 2.4, + 3.2, 0, 2.4, + 3.525, 0.25, 2.49375, + 2.8, 0.25, 2.475, + 3.45, 0.15, 2.5125, + 2.9, 0.15, 2.475, + 3.2, 0.15, 2.4, + 2.8, 0.15, 2.4, + 0, 0, 3.15, + 0.8, 0, 3.15, + 0.8, - 0.45, 3.15, + 0.45, - 0.8, 3.15, + 0, - 0.8, 3.15, + 0, 0, 2.85, + 0.2, 0, 2.7, + 0.2, - 0.112, 2.7, + 0.112, - 0.2, 2.7, + 0, - 0.2, 2.7, + - 0.45, - 0.8, 3.15, + - 0.8, - 0.45, 3.15, + - 0.8, 0, 3.15, + - 0.112, - 0.2, 2.7, + - 0.2, - 0.112, 2.7, + - 0.2, 0, 2.7, + - 0.8, 0.45, 3.15, + - 0.45, 0.8, 3.15, + 0, 0.8, 3.15, + - 0.2, 0.112, 2.7, + - 0.112, 0.2, 2.7, + 0, 0.2, 2.7, + 0.45, 0.8, 3.15, + 0.8, 0.45, 3.15, + 0.112, 0.2, 2.7, + 0.2, 0.112, 2.7, + 0.4, 0, 2.55, + 0.4, - 0.224, 2.55, + 0.224, - 0.4, 2.55, + 0, - 0.4, 2.55, + 1.3, 0, 2.55, + 1.3, - 0.728, 2.55, + 0.728, - 1.3, 2.55, + 0, - 1.3, 2.55, + 1.3, 0, 2.4, + 1.3, - 0.728, 2.4, + 0.728, - 1.3, 2.4, + 0, - 1.3, 2.4, + - 0.224, - 0.4, 2.55, + - 0.4, - 0.224, 2.55, + - 0.4, 0, 2.55, + - 0.728, - 1.3, 2.55, + - 1.3, - 0.728, 2.55, + - 1.3, 0, 2.55, + - 0.728, - 1.3, 2.4, + - 1.3, - 0.728, 2.4, + - 1.3, 0, 2.4, + - 0.4, 0.224, 2.55, + - 0.224, 0.4, 2.55, + 0, 0.4, 2.55, + - 1.3, 0.728, 2.55, + - 0.728, 1.3, 2.55, + 0, 1.3, 2.55, + - 1.3, 0.728, 2.4, + - 0.728, 1.3, 2.4, + 0, 1.3, 2.4, + 0.224, 0.4, 2.55, + 0.4, 0.224, 2.55, + 0.728, 1.3, 2.55, + 1.3, 0.728, 2.55, + 0.728, 1.3, 2.4, + 1.3, 0.728, 2.4, + 0, 0, 0, + 1.425, 0, 0, + 1.425, 0.798, 0, + 0.798, 1.425, 0, + 0, 1.425, 0, + 1.5, 0, 0.075, + 1.5, 0.84, 0.075, + 0.84, 1.5, 0.075, + 0, 1.5, 0.075, + - 0.798, 1.425, 0, + - 1.425, 0.798, 0, + - 1.425, 0, 0, + - 0.84, 1.5, 0.075, + - 1.5, 0.84, 0.075, + - 1.5, 0, 0.075, + - 1.425, - 0.798, 0, + - 0.798, - 1.425, 0, + 0, - 1.425, 0, + - 1.5, - 0.84, 0.075, + - 0.84, - 1.5, 0.075, + 0, - 1.5, 0.075, + 0.798, - 1.425, 0, + 1.425, - 0.798, 0, + 0.84, - 1.5, 0.075, + 1.5, - 0.84, 0.075 + ]; + + super(); + + // number of segments per patch + segments = Math.max( 2, Math.floor( segments ) ); + + // Jim Blinn scaled the teapot down in size by about 1.3 for + // some rendering tests. He liked the new proportions that he kept + // the data in this form. The model was distributed with these new + // proportions and became the norm. Trivia: comparing images of the + // real teapot and the computer model, the ratio for the bowl of the + // real teapot is more like 1.25, but since 1.3 is the traditional + // value given, we use it here. + const blinnScale = 1.3; + + // scale the size to be the real scaling factor + const maxHeight = 3.15 * ( blinn ? 1 : blinnScale ); + + const maxHeight2 = maxHeight / 2; + const trueSize = size / maxHeight2; + + // Number of elements depends on what is needed. Subtract degenerate + // triangles at tip of bottom and lid out in advance. + let numTriangles = bottom ? ( 8 * segments - 4 ) * segments : 0; + numTriangles += lid ? ( 16 * segments - 4 ) * segments : 0; + numTriangles += body ? 40 * segments * segments : 0; + + const indices = new Uint32Array( numTriangles * 3 ); + + let numVertices = bottom ? 4 : 0; + numVertices += lid ? 8 : 0; + numVertices += body ? 20 : 0; + numVertices *= ( segments + 1 ) * ( segments + 1 ); + + const vertices = new Float32Array( numVertices * 3 ); + const normals = new Float32Array( numVertices * 3 ); + const uvs = new Float32Array( numVertices * 2 ); + + // Bezier form + const ms = new Matrix4(); + ms.set( + - 1.0, 3.0, - 3.0, 1.0, + 3.0, - 6.0, 3.0, 0.0, + - 3.0, 3.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0 ); + + const g = []; + + const sp = []; + const tp = []; + const dsp = []; + const dtp = []; + + // M * G * M matrix, sort of see + // http://www.cs.helsinki.fi/group/goa/mallinnus/curves/surfaces.html + const mgm = []; + + const vert = []; + const sdir = []; + const tdir = []; + + const norm = new Vector3(); + + let tcoord; + + let sval; + let tval; + let p; + let dsval = 0; + let dtval = 0; + + const normOut = new Vector3(); + + const gmx = new Matrix4(); + const tmtx = new Matrix4(); + + const vsp = new Vector4(); + const vtp = new Vector4(); + const vdsp = new Vector4(); + const vdtp = new Vector4(); + + const vsdir = new Vector3(); + const vtdir = new Vector3(); + + const mst = ms.clone(); + mst.transpose(); + + // internal function: test if triangle has any matching vertices; + // if so, don't save triangle, since it won't display anything. + const notDegenerate = ( vtx1, vtx2, vtx3 ) => // if any vertex matches, return false + ! ( ( ( vertices[ vtx1 * 3 ] === vertices[ vtx2 * 3 ] ) && + ( vertices[ vtx1 * 3 + 1 ] === vertices[ vtx2 * 3 + 1 ] ) && + ( vertices[ vtx1 * 3 + 2 ] === vertices[ vtx2 * 3 + 2 ] ) ) || + ( ( vertices[ vtx1 * 3 ] === vertices[ vtx3 * 3 ] ) && + ( vertices[ vtx1 * 3 + 1 ] === vertices[ vtx3 * 3 + 1 ] ) && + ( vertices[ vtx1 * 3 + 2 ] === vertices[ vtx3 * 3 + 2 ] ) ) || ( vertices[ vtx2 * 3 ] === vertices[ vtx3 * 3 ] ) && + ( vertices[ vtx2 * 3 + 1 ] === vertices[ vtx3 * 3 + 1 ] ) && + ( vertices[ vtx2 * 3 + 2 ] === vertices[ vtx3 * 3 + 2 ] ) ); + + + for ( let i = 0; i < 3; i ++ ) { + + mgm[ i ] = new Matrix4(); + + } + + const minPatches = body ? 0 : 20; + const maxPatches = bottom ? 32 : 28; + + const vertPerRow = segments + 1; + + let surfCount = 0; + + let vertCount = 0; + let normCount = 0; + let uvCount = 0; + + let indexCount = 0; + + for ( let surf = minPatches; surf < maxPatches; surf ++ ) { + + // lid is in the middle of the data, patches 20-27, + // so ignore it for this part of the loop if the lid is not desired + if ( lid || ( surf < 20 || surf >= 28 ) ) { + + // get M * G * M matrix for x,y,z + for ( let i = 0; i < 3; i ++ ) { + + // get control patches + for ( let r = 0; r < 4; r ++ ) { + + for ( let c = 0; c < 4; c ++ ) { + + // transposed + g[ c * 4 + r ] = teapotVertices[ teapotPatches[ surf * 16 + r * 4 + c ] * 3 + i ]; + + // is the lid to be made larger, and is this a point on the lid + // that is X or Y? + if ( fitLid && ( surf >= 20 && surf < 28 ) && ( i !== 2 ) ) { + + // increase XY size by 7.7%, found empirically. I don't + // increase Z so that the teapot will continue to fit in the + // space -1 to 1 for Y (Y is up for the final model). + g[ c * 4 + r ] *= 1.077; + + } + + // Blinn "fixed" the teapot by dividing Z by blinnScale, and that's the + // data we now use. The original teapot is taller. Fix it: + if ( ! blinn && ( i === 2 ) ) { + + g[ c * 4 + r ] *= blinnScale; + + } + + } + + } + + gmx.set( g[ 0 ], g[ 1 ], g[ 2 ], g[ 3 ], g[ 4 ], g[ 5 ], g[ 6 ], g[ 7 ], g[ 8 ], g[ 9 ], g[ 10 ], g[ 11 ], g[ 12 ], g[ 13 ], g[ 14 ], g[ 15 ] ); + + tmtx.multiplyMatrices( gmx, ms ); + mgm[ i ].multiplyMatrices( mst, tmtx ); + + } + + // step along, get points, and output + for ( let sstep = 0; sstep <= segments; sstep ++ ) { + + const s = sstep / segments; + + for ( let tstep = 0; tstep <= segments; tstep ++ ) { + + const t = tstep / segments; + + // point from basis + // get power vectors and their derivatives + for ( p = 4, sval = tval = 1.0; p --; ) { + + sp[ p ] = sval; + tp[ p ] = tval; + sval *= s; + tval *= t; + + if ( p === 3 ) { + + dsp[ p ] = dtp[ p ] = 0.0; + dsval = dtval = 1.0; + + } else { + + dsp[ p ] = dsval * ( 3 - p ); + dtp[ p ] = dtval * ( 3 - p ); + dsval *= s; + dtval *= t; + + } + + } + + vsp.fromArray( sp ); + vtp.fromArray( tp ); + vdsp.fromArray( dsp ); + vdtp.fromArray( dtp ); + + // do for x,y,z + for ( let i = 0; i < 3; i ++ ) { + + // multiply power vectors times matrix to get value + tcoord = vsp.clone(); + tcoord.applyMatrix4( mgm[ i ] ); + vert[ i ] = tcoord.dot( vtp ); + + // get s and t tangent vectors + tcoord = vdsp.clone(); + tcoord.applyMatrix4( mgm[ i ] ); + sdir[ i ] = tcoord.dot( vtp ); + + tcoord = vsp.clone(); + tcoord.applyMatrix4( mgm[ i ] ); + tdir[ i ] = tcoord.dot( vdtp ); + + } + + // find normal + vsdir.fromArray( sdir ); + vtdir.fromArray( tdir ); + norm.crossVectors( vtdir, vsdir ); + norm.normalize(); + + // if X and Z length is 0, at the cusp, so point the normal up or down, depending on patch number + if ( vert[ 0 ] === 0 && vert[ 1 ] === 0 ) { + + // if above the middle of the teapot, normal points up, else down + normOut.set( 0, vert[ 2 ] > maxHeight2 ? 1 : - 1, 0 ); + + } else { + + // standard output: rotate on X axis + normOut.set( norm.x, norm.z, - norm.y ); + + } + + // store it all + vertices[ vertCount ++ ] = trueSize * vert[ 0 ]; + vertices[ vertCount ++ ] = trueSize * ( vert[ 2 ] - maxHeight2 ); + vertices[ vertCount ++ ] = - trueSize * vert[ 1 ]; + + normals[ normCount ++ ] = normOut.x; + normals[ normCount ++ ] = normOut.y; + normals[ normCount ++ ] = normOut.z; + + uvs[ uvCount ++ ] = 1 - t; + uvs[ uvCount ++ ] = 1 - s; + + } + + } + + // save the faces + for ( let sstep = 0; sstep < segments; sstep ++ ) { + + for ( let tstep = 0; tstep < segments; tstep ++ ) { + + const v1 = surfCount * vertPerRow * vertPerRow + sstep * vertPerRow + tstep; + const v2 = v1 + 1; + const v3 = v2 + vertPerRow; + const v4 = v1 + vertPerRow; + + // Normals and UVs cannot be shared. Without clone(), you can see the consequences + // of sharing if you call geometry.applyMatrix4( matrix ). + if ( notDegenerate( v1, v2, v3 ) ) { + + indices[ indexCount ++ ] = v1; + indices[ indexCount ++ ] = v2; + indices[ indexCount ++ ] = v3; + + } + + if ( notDegenerate( v1, v3, v4 ) ) { + + indices[ indexCount ++ ] = v1; + indices[ indexCount ++ ] = v3; + indices[ indexCount ++ ] = v4; + + } + + } + + } + + // increment only if a surface was used + surfCount ++; + + } + + } + + this.setIndex( new BufferAttribute( indices, 1 ) ); + this.setAttribute( 'position', new BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new BufferAttribute( uvs, 2 ) ); + + this.computeBoundingSphere(); + + } + +} + +export { TeapotGeometry }; diff --git a/jsm/geometries/TextGeometry.js b/jsm/geometries/TextGeometry.js new file mode 100644 index 0000000..5275c03 --- /dev/null +++ b/jsm/geometries/TextGeometry.js @@ -0,0 +1,57 @@ +/** + * Text = 3D Text + * + * parameters = { + * font: , // font + * + * size: , // size of the text + * height: , // thickness to extrude text + * curveSegments: , // number of points on the curves + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into text bevel goes + * bevelSize: , // how far from text outline (including bevelOffset) is bevel + * bevelOffset: // how far from text outline does bevel start + * } + */ + +import { + ExtrudeGeometry +} from 'three'; + +class TextGeometry extends ExtrudeGeometry { + + constructor( text, parameters = {} ) { + + const font = parameters.font; + + if ( font === undefined ) { + + super(); // generate default extrude geometry + + } else { + + const shapes = font.generateShapes( text, parameters.size ); + + // translate parameters to ExtrudeGeometry API + + parameters.depth = parameters.height !== undefined ? parameters.height : 50; + + // defaults + + if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; + if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; + if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; + + super( shapes, parameters ); + + } + + this.type = 'TextGeometry'; + + } + +} + + +export { TextGeometry }; diff --git a/jsm/helpers/LightProbeHelper.js b/jsm/helpers/LightProbeHelper.js new file mode 100644 index 0000000..8120488 --- /dev/null +++ b/jsm/helpers/LightProbeHelper.js @@ -0,0 +1,130 @@ +import { + Mesh, + ShaderMaterial, + SphereGeometry +} from 'three'; + +class LightProbeHelper extends Mesh { + + constructor( lightProbe, size ) { + + const material = new ShaderMaterial( { + + type: 'LightProbeHelperMaterial', + + uniforms: { + + sh: { value: lightProbe.sh.coefficients }, // by reference + + intensity: { value: lightProbe.intensity } + + }, + + vertexShader: [ + + 'varying vec3 vNormal;', + + 'void main() {', + + ' vNormal = normalize( normalMatrix * normal );', + + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}', + + ].join( '\n' ), + + fragmentShader: [ + + '#define RECIPROCAL_PI 0.318309886', + + 'vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {', + + ' // matrix is assumed to be orthogonal', + + ' return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );', + + '}', + + '// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf', + 'vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {', + + ' // normal is assumed to have unit length', + + ' float x = normal.x, y = normal.y, z = normal.z;', + + ' // band 0', + ' vec3 result = shCoefficients[ 0 ] * 0.886227;', + + ' // band 1', + ' result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;', + ' result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;', + ' result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;', + + ' // band 2', + ' result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;', + ' result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;', + ' result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );', + ' result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;', + ' result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );', + + ' return result;', + + '}', + + 'uniform vec3 sh[ 9 ]; // sh coefficients', + + 'uniform float intensity; // light probe intensity', + + 'varying vec3 vNormal;', + + 'void main() {', + + ' vec3 normal = normalize( vNormal );', + + ' vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );', + + ' vec3 irradiance = shGetIrradianceAt( worldNormal, sh );', + + ' vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;', + + ' gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) );', + + '}' + + ].join( '\n' ) + + } ); + + const geometry = new SphereGeometry( 1, 32, 16 ); + + super( geometry, material ); + + this.lightProbe = lightProbe; + this.size = size; + this.type = 'LightProbeHelper'; + + this.onBeforeRender(); + + } + + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + + onBeforeRender() { + + this.position.copy( this.lightProbe.position ); + + this.scale.set( 1, 1, 1 ).multiplyScalar( this.size ); + + this.material.uniforms.intensity.value = this.lightProbe.intensity; + + } + +} + +export { LightProbeHelper }; diff --git a/jsm/helpers/OctreeHelper.js b/jsm/helpers/OctreeHelper.js new file mode 100644 index 0000000..88c8bd8 --- /dev/null +++ b/jsm/helpers/OctreeHelper.js @@ -0,0 +1,58 @@ +import { + LineSegments, + BufferGeometry, + Float32BufferAttribute, + LineBasicMaterial +} from 'three'; + +class OctreeHelper extends LineSegments { + + constructor( octree, color = 0xffff00 ) { + + const vertices = []; + + function traverse( tree ) { + + for ( let i = 0; i < tree.length; i ++ ) { + + const min = tree[ i ].box.min; + const max = tree[ i ].box.max; + + vertices.push( max.x, max.y, max.z ); vertices.push( min.x, max.y, max.z ); // 0, 1 + vertices.push( min.x, max.y, max.z ); vertices.push( min.x, min.y, max.z ); // 1, 2 + vertices.push( min.x, min.y, max.z ); vertices.push( max.x, min.y, max.z ); // 2, 3 + vertices.push( max.x, min.y, max.z ); vertices.push( max.x, max.y, max.z ); // 3, 0 + + vertices.push( max.x, max.y, min.z ); vertices.push( min.x, max.y, min.z ); // 4, 5 + vertices.push( min.x, max.y, min.z ); vertices.push( min.x, min.y, min.z ); // 5, 6 + vertices.push( min.x, min.y, min.z ); vertices.push( max.x, min.y, min.z ); // 6, 7 + vertices.push( max.x, min.y, min.z ); vertices.push( max.x, max.y, min.z ); // 7, 4 + + vertices.push( max.x, max.y, max.z ); vertices.push( max.x, max.y, min.z ); // 0, 4 + vertices.push( min.x, max.y, max.z ); vertices.push( min.x, max.y, min.z ); // 1, 5 + vertices.push( min.x, min.y, max.z ); vertices.push( min.x, min.y, min.z ); // 2, 6 + vertices.push( max.x, min.y, max.z ); vertices.push( max.x, min.y, min.z ); // 3, 7 + + traverse( tree[ i ].subTrees ); + + } + + } + + traverse( octree.subTrees ); + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + + this.octree = octree; + this.color = color; + + this.type = 'OctreeHelper'; + + } + +} + +export { OctreeHelper }; diff --git a/jsm/helpers/PositionalAudioHelper.js b/jsm/helpers/PositionalAudioHelper.js new file mode 100644 index 0000000..0a20ea5 --- /dev/null +++ b/jsm/helpers/PositionalAudioHelper.js @@ -0,0 +1,109 @@ +import { + BufferGeometry, + BufferAttribute, + LineBasicMaterial, + Line, + MathUtils +} from 'three'; + +class PositionalAudioHelper extends Line { + + constructor( audio, range = 1, divisionsInnerAngle = 16, divisionsOuterAngle = 2 ) { + + const geometry = new BufferGeometry(); + const divisions = divisionsInnerAngle + divisionsOuterAngle * 2; + const positions = new Float32Array( ( divisions * 3 + 3 ) * 3 ); + geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); + + const materialInnerAngle = new LineBasicMaterial( { color: 0x00ff00 } ); + const materialOuterAngle = new LineBasicMaterial( { color: 0xffff00 } ); + + super( geometry, [ materialOuterAngle, materialInnerAngle ] ); + + this.audio = audio; + this.range = range; + this.divisionsInnerAngle = divisionsInnerAngle; + this.divisionsOuterAngle = divisionsOuterAngle; + this.type = 'PositionalAudioHelper'; + + this.update(); + + } + + update() { + + const audio = this.audio; + const range = this.range; + const divisionsInnerAngle = this.divisionsInnerAngle; + const divisionsOuterAngle = this.divisionsOuterAngle; + + const coneInnerAngle = MathUtils.degToRad( audio.panner.coneInnerAngle ); + const coneOuterAngle = MathUtils.degToRad( audio.panner.coneOuterAngle ); + + const halfConeInnerAngle = coneInnerAngle / 2; + const halfConeOuterAngle = coneOuterAngle / 2; + + let start = 0; + let count = 0; + let i; + let stride; + + const geometry = this.geometry; + const positionAttribute = geometry.attributes.position; + + geometry.clearGroups(); + + // + + function generateSegment( from, to, divisions, materialIndex ) { + + const step = ( to - from ) / divisions; + + positionAttribute.setXYZ( start, 0, 0, 0 ); + count ++; + + for ( i = from; i < to; i += step ) { + + stride = start + count; + + positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range ); + positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range ); + positionAttribute.setXYZ( stride + 2, 0, 0, 0 ); + + count += 3; + + } + + geometry.addGroup( start, count, materialIndex ); + + start += count; + count = 0; + + } + + // + + generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 ); + generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 ); + generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 ); + + // + + positionAttribute.needsUpdate = true; + + if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false; + + } + + dispose() { + + this.geometry.dispose(); + this.material[ 0 ].dispose(); + this.material[ 1 ].dispose(); + + } + +} + + +export { PositionalAudioHelper }; diff --git a/jsm/helpers/RectAreaLightHelper.js b/jsm/helpers/RectAreaLightHelper.js new file mode 100644 index 0000000..416fe1b --- /dev/null +++ b/jsm/helpers/RectAreaLightHelper.js @@ -0,0 +1,85 @@ +import { + BackSide, + BufferGeometry, + Float32BufferAttribute, + Line, + LineBasicMaterial, + Mesh, + MeshBasicMaterial +} from 'three'; + +/** + * This helper must be added as a child of the light + */ + +class RectAreaLightHelper extends Line { + + constructor( light, color ) { + + const positions = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.computeBoundingSphere(); + + const material = new LineBasicMaterial( { fog: false } ); + + super( geometry, material ); + + this.light = light; + this.color = color; // optional hardwired color for the helper + this.type = 'RectAreaLightHelper'; + + // + + const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; + + const geometry2 = new BufferGeometry(); + geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); + geometry2.computeBoundingSphere(); + + this.add( new Mesh( geometry2, new MeshBasicMaterial( { side: BackSide, fog: false } ) ) ); + + } + + updateMatrixWorld() { + + this.scale.set( 0.5 * this.light.width, 0.5 * this.light.height, 1 ); + + if ( this.color !== undefined ) { + + this.material.color.set( this.color ); + this.children[ 0 ].material.color.set( this.color ); + + } else { + + this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + // prevent hue shift + const c = this.material.color; + const max = Math.max( c.r, c.g, c.b ); + if ( max > 1 ) c.multiplyScalar( 1 / max ); + + this.children[ 0 ].material.color.copy( this.material.color ); + + } + + // ignore world scale on light + this.matrixWorld.extractRotation( this.light.matrixWorld ).scale( this.scale ).copyPosition( this.light.matrixWorld ); + + this.children[ 0 ].matrixWorld.copy( this.matrixWorld ); + + } + + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); + + } + +} + +export { RectAreaLightHelper }; diff --git a/jsm/helpers/VertexNormalsHelper.js b/jsm/helpers/VertexNormalsHelper.js new file mode 100644 index 0000000..502a668 --- /dev/null +++ b/jsm/helpers/VertexNormalsHelper.js @@ -0,0 +1,95 @@ +import { + BufferGeometry, + Float32BufferAttribute, + LineSegments, + LineBasicMaterial, + Matrix3, + Vector3 +} from 'three'; + +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _normalMatrix = new Matrix3(); + +class VertexNormalsHelper extends LineSegments { + + constructor( object, size = 1, color = 0xff0000 ) { + + const geometry = new BufferGeometry(); + + const nNormals = object.geometry.attributes.normal.count; + const positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 ); + + geometry.setAttribute( 'position', positions ); + + super( geometry, new LineBasicMaterial( { color, toneMapped: false } ) ); + + this.object = object; + this.size = size; + this.type = 'VertexNormalsHelper'; + + // + + this.matrixAutoUpdate = false; + + this.update(); + + } + + update() { + + this.object.updateMatrixWorld( true ); + + _normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + const matrixWorld = this.object.matrixWorld; + + const position = this.geometry.attributes.position; + + // + + const objGeometry = this.object.geometry; + + if ( objGeometry && objGeometry.isGeometry ) { + + console.error( 'THREE.VertexNormalsHelper no longer supports Geometry. Use BufferGeometry instead.' ); + return; + + } else if ( objGeometry && objGeometry.isBufferGeometry ) { + + const objPos = objGeometry.attributes.position; + + const objNorm = objGeometry.attributes.normal; + + let idx = 0; + + // for simplicity, ignore index and drawcalls, and render every normal + + for ( let j = 0, jl = objPos.count; j < jl; j ++ ) { + + _v1.fromBufferAttribute( objPos, j ).applyMatrix4( matrixWorld ); + + _v2.fromBufferAttribute( objNorm, j ); + + _v2.applyMatrix3( _normalMatrix ).normalize().multiplyScalar( this.size ).add( _v1 ); + + position.setXYZ( idx, _v1.x, _v1.y, _v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, _v2.x, _v2.y, _v2.z ); + + idx = idx + 1; + + } + + } + + position.needsUpdate = true; + + } + +} + + +export { VertexNormalsHelper }; diff --git a/jsm/helpers/VertexTangentsHelper.js b/jsm/helpers/VertexTangentsHelper.js new file mode 100644 index 0000000..1938d7a --- /dev/null +++ b/jsm/helpers/VertexTangentsHelper.js @@ -0,0 +1,81 @@ +import { + BufferGeometry, + Float32BufferAttribute, + LineSegments, + LineBasicMaterial, + Vector3 +} from 'three'; + +const _v1 = new Vector3(); +const _v2 = new Vector3(); + +class VertexTangentsHelper extends LineSegments { + + constructor( object, size = 1, color = 0x00ffff ) { + + const geometry = new BufferGeometry(); + + const nTangents = object.geometry.attributes.tangent.count; + const positions = new Float32BufferAttribute( nTangents * 2 * 3, 3 ); + + geometry.setAttribute( 'position', positions ); + + super( geometry, new LineBasicMaterial( { color, toneMapped: false } ) ); + + this.object = object; + this.size = size; + this.type = 'VertexTangentsHelper'; + + // + + this.matrixAutoUpdate = false; + + this.update(); + + } + + update() { + + this.object.updateMatrixWorld( true ); + + const matrixWorld = this.object.matrixWorld; + + const position = this.geometry.attributes.position; + + // + + const objGeometry = this.object.geometry; + + const objPos = objGeometry.attributes.position; + + const objTan = objGeometry.attributes.tangent; + + let idx = 0; + + // for simplicity, ignore index and drawcalls, and render every tangent + + for ( let j = 0, jl = objPos.count; j < jl; j ++ ) { + + _v1.fromBufferAttribute( objPos, j ).applyMatrix4( matrixWorld ); + + _v2.fromBufferAttribute( objTan, j ); + + _v2.transformDirection( matrixWorld ).multiplyScalar( this.size ).add( _v1 ); + + position.setXYZ( idx, _v1.x, _v1.y, _v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, _v2.x, _v2.y, _v2.z ); + + idx = idx + 1; + + } + + position.needsUpdate = true; + + } + +} + +export { VertexTangentsHelper }; diff --git a/jsm/helpers/ViewHelper.js b/jsm/helpers/ViewHelper.js new file mode 100644 index 0000000..dd6c8ee --- /dev/null +++ b/jsm/helpers/ViewHelper.js @@ -0,0 +1,295 @@ +import * as THREE from 'three'; + +const vpTemp = new THREE.Vector4(); + +class ViewHelper extends THREE.Object3D { + + constructor( editorCamera, dom ) { + + super(); + + this.animating = false; + this.controls = null; + + const color1 = new THREE.Color( '#ff3653' ); + const color2 = new THREE.Color( '#8adb00' ); + const color3 = new THREE.Color( '#2c8fff' ); + + const interactiveObjects = []; + const raycaster = new THREE.Raycaster(); + const mouse = new THREE.Vector2(); + const dummy = new THREE.Object3D(); + + const camera = new THREE.OrthographicCamera( - 2, 2, 2, - 2, 0, 4 ); + camera.position.set( 0, 0, 2 ); + + const geometry = new THREE.BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 ); + + const xAxis = new THREE.Mesh( geometry, getAxisMaterial( color1 ) ); + const yAxis = new THREE.Mesh( geometry, getAxisMaterial( color2 ) ); + const zAxis = new THREE.Mesh( geometry, getAxisMaterial( color3 ) ); + + yAxis.rotation.z = Math.PI / 2; + zAxis.rotation.y = - Math.PI / 2; + + this.add( xAxis ); + this.add( zAxis ); + this.add( yAxis ); + + const posXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1, 'X' ) ); + posXAxisHelper.userData.type = 'posX'; + const posYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2, 'Y' ) ); + posYAxisHelper.userData.type = 'posY'; + const posZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3, 'Z' ) ); + posZAxisHelper.userData.type = 'posZ'; + const negXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1 ) ); + negXAxisHelper.userData.type = 'negX'; + const negYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2 ) ); + negYAxisHelper.userData.type = 'negY'; + const negZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3 ) ); + negZAxisHelper.userData.type = 'negZ'; + + posXAxisHelper.position.x = 1; + posYAxisHelper.position.y = 1; + posZAxisHelper.position.z = 1; + negXAxisHelper.position.x = - 1; + negXAxisHelper.scale.setScalar( 0.8 ); + negYAxisHelper.position.y = - 1; + negYAxisHelper.scale.setScalar( 0.8 ); + negZAxisHelper.position.z = - 1; + negZAxisHelper.scale.setScalar( 0.8 ); + + this.add( posXAxisHelper ); + this.add( posYAxisHelper ); + this.add( posZAxisHelper ); + this.add( negXAxisHelper ); + this.add( negYAxisHelper ); + this.add( negZAxisHelper ); + + interactiveObjects.push( posXAxisHelper ); + interactiveObjects.push( posYAxisHelper ); + interactiveObjects.push( posZAxisHelper ); + interactiveObjects.push( negXAxisHelper ); + interactiveObjects.push( negYAxisHelper ); + interactiveObjects.push( negZAxisHelper ); + + const point = new THREE.Vector3(); + const dim = 128; + const turnRate = 2 * Math.PI; // turn rate in angles per second + + this.render = function ( renderer ) { + + this.quaternion.copy( editorCamera.quaternion ).invert(); + this.updateMatrixWorld(); + + point.set( 0, 0, 1 ); + point.applyQuaternion( editorCamera.quaternion ); + + if ( point.x >= 0 ) { + + posXAxisHelper.material.opacity = 1; + negXAxisHelper.material.opacity = 0.5; + + } else { + + posXAxisHelper.material.opacity = 0.5; + negXAxisHelper.material.opacity = 1; + + } + + if ( point.y >= 0 ) { + + posYAxisHelper.material.opacity = 1; + negYAxisHelper.material.opacity = 0.5; + + } else { + + posYAxisHelper.material.opacity = 0.5; + negYAxisHelper.material.opacity = 1; + + } + + if ( point.z >= 0 ) { + + posZAxisHelper.material.opacity = 1; + negZAxisHelper.material.opacity = 0.5; + + } else { + + posZAxisHelper.material.opacity = 0.5; + negZAxisHelper.material.opacity = 1; + + } + + // + + const x = dom.offsetWidth - dim; + + renderer.clearDepth(); + + renderer.getViewport( vpTemp ); + renderer.setViewport( x, 0, dim, dim ); + + renderer.render( this, camera ); + + renderer.setViewport( vpTemp.x, vpTemp.y, vpTemp.z, vpTemp.w ); + + }; + + const targetPosition = new THREE.Vector3(); + const targetQuaternion = new THREE.Quaternion(); + + const q1 = new THREE.Quaternion(); + const q2 = new THREE.Quaternion(); + let radius = 0; + + this.handleClick = function ( event ) { + + if ( this.animating === true ) return false; + + const rect = dom.getBoundingClientRect(); + const offsetX = rect.left + ( dom.offsetWidth - dim ); + const offsetY = rect.top + ( dom.offsetHeight - dim ); + mouse.x = ( ( event.clientX - offsetX ) / ( rect.width - offsetX ) ) * 2 - 1; + mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1; + + raycaster.setFromCamera( mouse, camera ); + + const intersects = raycaster.intersectObjects( interactiveObjects ); + + if ( intersects.length > 0 ) { + + const intersection = intersects[ 0 ]; + const object = intersection.object; + + prepareAnimationData( object, this.controls.center ); + + this.animating = true; + + return true; + + } else { + + return false; + + } + + }; + + this.update = function ( delta ) { + + const step = delta * turnRate; + const focusPoint = this.controls.center; + + // animate position by doing a slerp and then scaling the position on the unit sphere + + q1.rotateTowards( q2, step ); + editorCamera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( focusPoint ); + + // animate orientation + + editorCamera.quaternion.rotateTowards( targetQuaternion, step ); + + if ( q1.angleTo( q2 ) === 0 ) { + + this.animating = false; + + } + + }; + + function prepareAnimationData( object, focusPoint ) { + + switch ( object.userData.type ) { + + case 'posX': + targetPosition.set( 1, 0, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI * 0.5, 0 ) ); + break; + + case 'posY': + targetPosition.set( 0, 1, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( - Math.PI * 0.5, 0, 0 ) ); + break; + + case 'posZ': + targetPosition.set( 0, 0, 1 ); + targetQuaternion.setFromEuler( new THREE.Euler() ); + break; + + case 'negX': + targetPosition.set( - 1, 0, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( 0, - Math.PI * 0.5, 0 ) ); + break; + + case 'negY': + targetPosition.set( 0, - 1, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( Math.PI * 0.5, 0, 0 ) ); + break; + + case 'negZ': + targetPosition.set( 0, 0, - 1 ); + targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) ); + break; + + default: + console.error( 'ViewHelper: Invalid axis.' ); + + } + + // + + radius = editorCamera.position.distanceTo( focusPoint ); + targetPosition.multiplyScalar( radius ).add( focusPoint ); + + dummy.position.copy( focusPoint ); + + dummy.lookAt( editorCamera.position ); + q1.copy( dummy.quaternion ); + + dummy.lookAt( targetPosition ); + q2.copy( dummy.quaternion ); + + } + + function getAxisMaterial( color ) { + + return new THREE.MeshBasicMaterial( { color: color, toneMapped: false } ); + + } + + function getSpriteMaterial( color, text = null ) { + + const canvas = document.createElement( 'canvas' ); + canvas.width = 64; + canvas.height = 64; + + const context = canvas.getContext( '2d' ); + context.beginPath(); + context.arc( 32, 32, 16, 0, 2 * Math.PI ); + context.closePath(); + context.fillStyle = color.getStyle(); + context.fill(); + + if ( text !== null ) { + + context.font = '24px Arial'; + context.textAlign = 'center'; + context.fillStyle = '#000000'; + context.fillText( text, 32, 41 ); + + } + + const texture = new THREE.CanvasTexture( canvas ); + + return new THREE.SpriteMaterial( { map: texture, toneMapped: false } ); + + } + + } + +} + +ViewHelper.prototype.isViewHelper = true; + +export { ViewHelper }; diff --git a/jsm/interactive/HTMLMesh.js b/jsm/interactive/HTMLMesh.js new file mode 100644 index 0000000..d9212e8 --- /dev/null +++ b/jsm/interactive/HTMLMesh.js @@ -0,0 +1,386 @@ +import { + CanvasTexture, + LinearFilter, + Mesh, + MeshBasicMaterial, + PlaneGeometry, + sRGBEncoding +} from 'three'; + +class HTMLMesh extends Mesh { + + constructor( dom ) { + + const texture = new HTMLTexture( dom ); + + const geometry = new PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 ); + const material = new MeshBasicMaterial( { map: texture, toneMapped: false } ); + + super( geometry, material ); + + function onEvent( event ) { + + material.map.dispatchDOMEvent( event ); + + } + + this.addEventListener( 'mousedown', onEvent ); + this.addEventListener( 'mousemove', onEvent ); + this.addEventListener( 'mouseup', onEvent ); + this.addEventListener( 'click', onEvent ); + + this.dispose = function () { + + geometry.dispose(); + material.dispose(); + + material.map.dispose(); + + this.removeEventListener( 'mousedown', onEvent ); + this.removeEventListener( 'mousemove', onEvent ); + this.removeEventListener( 'mouseup', onEvent ); + this.removeEventListener( 'click', onEvent ); + + }; + + } + +} + +class HTMLTexture extends CanvasTexture { + + constructor( dom ) { + + super( html2canvas( dom ) ); + + this.dom = dom; + + this.anisotropy = 16; + this.encoding = sRGBEncoding; + this.minFilter = LinearFilter; + this.magFilter = LinearFilter; + + // Create an observer on the DOM, and run html2canvas update in the next loop + const observer = new MutationObserver( () => { + + if ( ! this.scheduleUpdate ) { + + // ideally should use xr.requestAnimationFrame, here setTimeout to avoid passing the renderer + this.scheduleUpdate = setTimeout( () => this.update(), 16 ); + + } + + } ); + + const config = { attributes: true, childList: true, subtree: true, characterData: true }; + observer.observe( dom, config ); + + this.observer = observer; + + } + + dispatchDOMEvent( event ) { + + if ( event.data ) { + + htmlevent( this.dom, event.type, event.data.x, event.data.y ); + + } + + } + + update() { + + this.image = html2canvas( this.dom ); + this.needsUpdate = true; + + this.scheduleUpdate = null; + + } + + dispose() { + + if ( this.observer ) { + + this.observer.disconnect(); + + } + + this.scheduleUpdate = clearTimeout( this.scheduleUpdate ); + + super.dispose(); + + } + +} + + +// + +const canvases = new WeakMap(); + +function html2canvas( element ) { + + const range = document.createRange(); + + function Clipper( context ) { + + const clips = []; + let isClipping = false; + + function doClip() { + + if ( isClipping ) { + + isClipping = false; + context.restore(); + + } + + if ( clips.length === 0 ) return; + + let minX = - Infinity, minY = - Infinity; + let maxX = Infinity, maxY = Infinity; + + for ( let i = 0; i < clips.length; i ++ ) { + + const clip = clips[ i ]; + + minX = Math.max( minX, clip.x ); + minY = Math.max( minY, clip.y ); + maxX = Math.min( maxX, clip.x + clip.width ); + maxY = Math.min( maxY, clip.y + clip.height ); + + } + + context.save(); + context.beginPath(); + context.rect( minX, minY, maxX - minX, maxY - minY ); + context.clip(); + + isClipping = true; + + } + + return { + + add: function ( clip ) { + + clips.push( clip ); + doClip(); + + }, + + remove: function () { + + clips.pop(); + doClip(); + + } + + }; + + } + + function drawText( style, x, y, string ) { + + if ( string !== '' ) { + + if ( style.textTransform === 'uppercase' ) { + + string = string.toUpperCase(); + + } + + context.font = style.fontSize + ' ' + style.fontFamily; + context.textBaseline = 'top'; + context.fillStyle = style.color; + context.fillText( string, x, y ); + + } + + } + + function drawBorder( style, which, x, y, width, height ) { + + const borderWidth = style[ which + 'Width' ]; + const borderStyle = style[ which + 'Style' ]; + const borderColor = style[ which + 'Color' ]; + + if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) { + + context.strokeStyle = borderColor; + context.beginPath(); + context.moveTo( x, y ); + context.lineTo( x + width, y + height ); + context.stroke(); + + } + + } + + function drawElement( element, style ) { + + let x = 0, y = 0, width = 0, height = 0; + + if ( element.nodeType === Node.TEXT_NODE ) { + + // text + + range.selectNode( element ); + + const rect = range.getBoundingClientRect(); + + x = rect.left - offset.left - 0.5; + y = rect.top - offset.top - 0.5; + width = rect.width; + height = rect.height; + + drawText( style, x, y, element.nodeValue.trim() ); + + } else if ( element.nodeType === Node.COMMENT_NODE ) { + + return; + + } else if ( element instanceof HTMLCanvasElement ) { + + // Canvas element + if ( element.style.display === 'none' ) return; + + context.save(); + const dpr = window.devicePixelRatio; + context.scale(1/dpr, 1/dpr); + context.drawImage(element, 0, 0 ); + context.restore(); + + } else { + + if ( element.style.display === 'none' ) return; + + const rect = element.getBoundingClientRect(); + + x = rect.left - offset.left - 0.5; + y = rect.top - offset.top - 0.5; + width = rect.width; + height = rect.height; + + style = window.getComputedStyle( element ); + + const backgroundColor = style.backgroundColor; + + if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) { + + context.fillStyle = backgroundColor; + context.fillRect( x, y, width, height ); + + } + + drawBorder( style, 'borderTop', x, y, width, 0 ); + drawBorder( style, 'borderLeft', x, y, 0, height ); + drawBorder( style, 'borderBottom', x, y + height, width, 0 ); + drawBorder( style, 'borderRight', x + width, y, 0, height ); + + if ( element.type === 'color' || element.type === 'text' || element.type === 'number' ) { + + clipper.add( { x: x, y: y, width: width, height: height } ); + + drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value ); + + clipper.remove(); + + } + + } + + /* + // debug + context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 ); + context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 ); + */ + + const isClipping = style.overflow === 'auto' || style.overflow === 'hidden'; + + if ( isClipping ) clipper.add( { x: x, y: y, width: width, height: height } ); + + for ( let i = 0; i < element.childNodes.length; i ++ ) { + + drawElement( element.childNodes[ i ], style ); + + } + + if ( isClipping ) clipper.remove(); + + } + + const offset = element.getBoundingClientRect(); + + let canvas; + + if ( canvases.has( element ) ) { + + canvas = canvases.get( element ); + + } else { + + canvas = document.createElement( 'canvas' ); + canvas.width = offset.width; + canvas.height = offset.height; + + } + + const context = canvas.getContext( '2d'/*, { alpha: false }*/ ); + + const clipper = new Clipper( context ); + + // console.time( 'drawElement' ); + + drawElement( element ); + + // console.timeEnd( 'drawElement' ); + + return canvas; + +} + +function htmlevent( element, event, x, y ) { + + const mouseEventInit = { + clientX: ( x * element.offsetWidth ) + element.offsetLeft, + clientY: ( y * element.offsetHeight ) + element.offsetTop, + view: element.ownerDocument.defaultView + }; + + window.dispatchEvent( new MouseEvent( event, mouseEventInit ) ); + + const rect = element.getBoundingClientRect(); + + x = x * rect.width + rect.left; + y = y * rect.height + rect.top; + + function traverse( element ) { + + if ( element.nodeType !== Node.TEXT_NODE && element.nodeType !== Node.COMMENT_NODE ) { + + const rect = element.getBoundingClientRect(); + + if ( x > rect.left && x < rect.right && y > rect.top && y < rect.bottom ) { + + element.dispatchEvent( new MouseEvent( event, mouseEventInit ) ); + + } + + for ( let i = 0; i < element.childNodes.length; i ++ ) { + + traverse( element.childNodes[ i ] ); + + } + + } + + } + + traverse( element ); + +} + +export { HTMLMesh }; diff --git a/jsm/interactive/InteractiveGroup.js b/jsm/interactive/InteractiveGroup.js new file mode 100644 index 0000000..6ae5b82 --- /dev/null +++ b/jsm/interactive/InteractiveGroup.js @@ -0,0 +1,114 @@ +import { + Group, + Matrix4, + Raycaster, + Vector2 +} from 'three'; + +const _pointer = new Vector2(); +const _event = { type: '', data: _pointer }; + +class InteractiveGroup extends Group { + + constructor( renderer, camera ) { + + super(); + + const scope = this; + + const raycaster = new Raycaster(); + const tempMatrix = new Matrix4(); + + // Pointer Events + + const element = renderer.domElement; + + function onPointerEvent( event ) { + + event.stopPropagation(); + + _pointer.x = ( event.clientX / element.clientWidth ) * 2 - 1; + _pointer.y = - ( event.clientY / element.clientHeight ) * 2 + 1; + + raycaster.setFromCamera( _pointer, camera ); + + const intersects = raycaster.intersectObjects( scope.children, false ); + + if ( intersects.length > 0 ) { + + const intersection = intersects[ 0 ]; + + const object = intersection.object; + const uv = intersection.uv; + + _event.type = event.type; + _event.data.set( uv.x, 1 - uv.y ); + + object.dispatchEvent( _event ); + + } + + } + + element.addEventListener( 'pointerdown', onPointerEvent ); + element.addEventListener( 'pointerup', onPointerEvent ); + element.addEventListener( 'pointermove', onPointerEvent ); + element.addEventListener( 'mousedown', onPointerEvent ); + element.addEventListener( 'mouseup', onPointerEvent ); + element.addEventListener( 'mousemove', onPointerEvent ); + element.addEventListener( 'click', onPointerEvent ); + + // WebXR Controller Events + // TODO: Dispatch pointerevents too + + const events = { + 'move': 'mousemove', + 'select': 'click', + 'selectstart': 'mousedown', + 'selectend': 'mouseup' + }; + + function onXRControllerEvent( event ) { + + const controller = event.target; + + tempMatrix.identity().extractRotation( controller.matrixWorld ); + + raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); + raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); + + const intersections = raycaster.intersectObjects( scope.children, false ); + + if ( intersections.length > 0 ) { + + const intersection = intersections[ 0 ]; + + const object = intersection.object; + const uv = intersection.uv; + + _event.type = events[ event.type ]; + _event.data.set( uv.x, 1 - uv.y ); + + object.dispatchEvent( _event ); + + } + + } + + const controller1 = renderer.xr.getController( 0 ); + controller1.addEventListener( 'move', onXRControllerEvent ); + controller1.addEventListener( 'select', onXRControllerEvent ); + controller1.addEventListener( 'selectstart', onXRControllerEvent ); + controller1.addEventListener( 'selectend', onXRControllerEvent ); + + const controller2 = renderer.xr.getController( 1 ); + controller2.addEventListener( 'move', onXRControllerEvent ); + controller2.addEventListener( 'select', onXRControllerEvent ); + controller2.addEventListener( 'selectstart', onXRControllerEvent ); + controller2.addEventListener( 'selectend', onXRControllerEvent ); + + } + +} + +export { InteractiveGroup }; diff --git a/jsm/interactive/SelectionBox.js b/jsm/interactive/SelectionBox.js new file mode 100644 index 0000000..597b9b2 --- /dev/null +++ b/jsm/interactive/SelectionBox.js @@ -0,0 +1,227 @@ +import { + Frustum, + Vector3, + Matrix4, + Quaternion, +} from 'three'; + +/** + * This is a class to check whether objects are in a selection area in 3D space + */ + +const _frustum = new Frustum(); +const _center = new Vector3(); + +const _tmpPoint = new Vector3(); + +const _vecNear = new Vector3(); +const _vecTopLeft = new Vector3(); +const _vecTopRight = new Vector3(); +const _vecDownRight = new Vector3(); +const _vecDownLeft = new Vector3(); + +const _vecFarTopLeft = new Vector3(); +const _vecFarTopRight = new Vector3(); +const _vecFarDownRight = new Vector3(); +const _vecFarDownLeft = new Vector3(); + +const _vectemp1 = new Vector3(); +const _vectemp2 = new Vector3(); +const _vectemp3 = new Vector3(); + +const _matrix = new Matrix4(); +const _quaternion = new Quaternion(); +const _scale = new Vector3(); + +class SelectionBox { + + constructor( camera, scene, deep = Number.MAX_VALUE ) { + + this.camera = camera; + this.scene = scene; + this.startPoint = new Vector3(); + this.endPoint = new Vector3(); + this.collection = []; + this.instances = {}; + this.deep = deep; + + } + + select( startPoint, endPoint ) { + + this.startPoint = startPoint || this.startPoint; + this.endPoint = endPoint || this.endPoint; + this.collection = []; + + this.updateFrustum( this.startPoint, this.endPoint ); + this.searchChildInFrustum( _frustum, this.scene ); + + return this.collection; + + } + + updateFrustum( startPoint, endPoint ) { + + startPoint = startPoint || this.startPoint; + endPoint = endPoint || this.endPoint; + + // Avoid invalid frustum + + if ( startPoint.x === endPoint.x ) { + + endPoint.x += Number.EPSILON; + + } + + if ( startPoint.y === endPoint.y ) { + + endPoint.y += Number.EPSILON; + + } + + this.camera.updateProjectionMatrix(); + this.camera.updateMatrixWorld(); + + if ( this.camera.isPerspectiveCamera ) { + + _tmpPoint.copy( startPoint ); + _tmpPoint.x = Math.min( startPoint.x, endPoint.x ); + _tmpPoint.y = Math.max( startPoint.y, endPoint.y ); + endPoint.x = Math.max( startPoint.x, endPoint.x ); + endPoint.y = Math.min( startPoint.y, endPoint.y ); + + _vecNear.setFromMatrixPosition( this.camera.matrixWorld ); + _vecTopLeft.copy( _tmpPoint ); + _vecTopRight.set( endPoint.x, _tmpPoint.y, 0 ); + _vecDownRight.copy( endPoint ); + _vecDownLeft.set( _tmpPoint.x, endPoint.y, 0 ); + + _vecTopLeft.unproject( this.camera ); + _vecTopRight.unproject( this.camera ); + _vecDownRight.unproject( this.camera ); + _vecDownLeft.unproject( this.camera ); + + _vectemp1.copy( _vecTopLeft ).sub( _vecNear ); + _vectemp2.copy( _vecTopRight ).sub( _vecNear ); + _vectemp3.copy( _vecDownRight ).sub( _vecNear ); + _vectemp1.normalize(); + _vectemp2.normalize(); + _vectemp3.normalize(); + + _vectemp1.multiplyScalar( this.deep ); + _vectemp2.multiplyScalar( this.deep ); + _vectemp3.multiplyScalar( this.deep ); + _vectemp1.add( _vecNear ); + _vectemp2.add( _vecNear ); + _vectemp3.add( _vecNear ); + + const planes = _frustum.planes; + + planes[ 0 ].setFromCoplanarPoints( _vecNear, _vecTopLeft, _vecTopRight ); + planes[ 1 ].setFromCoplanarPoints( _vecNear, _vecTopRight, _vecDownRight ); + planes[ 2 ].setFromCoplanarPoints( _vecDownRight, _vecDownLeft, _vecNear ); + planes[ 3 ].setFromCoplanarPoints( _vecDownLeft, _vecTopLeft, _vecNear ); + planes[ 4 ].setFromCoplanarPoints( _vecTopRight, _vecDownRight, _vecDownLeft ); + planes[ 5 ].setFromCoplanarPoints( _vectemp3, _vectemp2, _vectemp1 ); + planes[ 5 ].normal.multiplyScalar( - 1 ); + + } else if ( this.camera.isOrthographicCamera ) { + + const left = Math.min( startPoint.x, endPoint.x ); + const top = Math.max( startPoint.y, endPoint.y ); + const right = Math.max( startPoint.x, endPoint.x ); + const down = Math.min( startPoint.y, endPoint.y ); + + _vecTopLeft.set( left, top, - 1 ); + _vecTopRight.set( right, top, - 1 ); + _vecDownRight.set( right, down, - 1 ); + _vecDownLeft.set( left, down, - 1 ); + + _vecFarTopLeft.set( left, top, 1 ); + _vecFarTopRight.set( right, top, 1 ); + _vecFarDownRight.set( right, down, 1 ); + _vecFarDownLeft.set( left, down, 1 ); + + _vecTopLeft.unproject( this.camera ); + _vecTopRight.unproject( this.camera ); + _vecDownRight.unproject( this.camera ); + _vecDownLeft.unproject( this.camera ); + + _vecFarTopLeft.unproject( this.camera ); + _vecFarTopRight.unproject( this.camera ); + _vecFarDownRight.unproject( this.camera ); + _vecFarDownLeft.unproject( this.camera ); + + const planes = _frustum.planes; + + planes[ 0 ].setFromCoplanarPoints( _vecTopLeft, _vecFarTopLeft, _vecFarTopRight ); + planes[ 1 ].setFromCoplanarPoints( _vecTopRight, _vecFarTopRight, _vecFarDownRight ); + planes[ 2 ].setFromCoplanarPoints( _vecFarDownRight, _vecFarDownLeft, _vecDownLeft ); + planes[ 3 ].setFromCoplanarPoints( _vecFarDownLeft, _vecFarTopLeft, _vecTopLeft ); + planes[ 4 ].setFromCoplanarPoints( _vecTopRight, _vecDownRight, _vecDownLeft ); + planes[ 5 ].setFromCoplanarPoints( _vecFarDownRight, _vecFarTopRight, _vecFarTopLeft ); + planes[ 5 ].normal.multiplyScalar( - 1 ); + + } else { + + console.error( 'THREE.SelectionBox: Unsupported camera type.' ); + + } + + } + + searchChildInFrustum( frustum, object ) { + + if ( object.isMesh || object.isLine || object.isPoints ) { + + if ( object.isInstancedMesh ) { + + this.instances[ object.uuid ] = []; + + for ( let instanceId = 0; instanceId < object.count; instanceId ++ ) { + + object.getMatrixAt( instanceId, _matrix ); + _matrix.decompose( _center, _quaternion, _scale ); + _center.applyMatrix4( object.matrixWorld ); + + if ( frustum.containsPoint( _center ) ) { + + this.instances[ object.uuid ].push( instanceId ); + + } + + } + + } else { + + if ( object.geometry.boundingSphere === null ) object.geometry.computeBoundingSphere(); + + _center.copy( object.geometry.boundingSphere.center ); + + _center.applyMatrix4( object.matrixWorld ); + + if ( frustum.containsPoint( _center ) ) { + + this.collection.push( object ); + + } + + } + + } + + if ( object.children.length > 0 ) { + + for ( let x = 0; x < object.children.length; x ++ ) { + + this.searchChildInFrustum( frustum, object.children[ x ] ); + + } + + } + + } + +} + +export { SelectionBox }; diff --git a/jsm/interactive/SelectionHelper.js b/jsm/interactive/SelectionHelper.js new file mode 100644 index 0000000..00153a9 --- /dev/null +++ b/jsm/interactive/SelectionHelper.js @@ -0,0 +1,95 @@ +import { + Vector2 +} from 'three'; + +class SelectionHelper { + + constructor( selectionBox, renderer, cssClassName ) { + + this.element = document.createElement( 'div' ); + this.element.classList.add( cssClassName ); + this.element.style.pointerEvents = 'none'; + + this.renderer = renderer; + + this.startPoint = new Vector2(); + this.pointTopLeft = new Vector2(); + this.pointBottomRight = new Vector2(); + + this.isDown = false; + + this.onPointerDown = function ( event ) { + + this.isDown = true; + this.onSelectStart( event ); + + }.bind( this ); + + this.onPointerMove = function ( event ) { + + if ( this.isDown ) { + + this.onSelectMove( event ); + + } + + }.bind( this ); + + this.onPointerUp = function ( ) { + + this.isDown = false; + this.onSelectOver(); + + }.bind( this ); + + this.renderer.domElement.addEventListener( 'pointerdown', this.onPointerDown ); + this.renderer.domElement.addEventListener( 'pointermove', this.onPointerMove ); + this.renderer.domElement.addEventListener( 'pointerup', this.onPointerUp ); + + } + + dispose() { + + this.renderer.domElement.removeEventListener( 'pointerdown', this.onPointerDown ); + this.renderer.domElement.removeEventListener( 'pointermove', this.onPointerMove ); + this.renderer.domElement.removeEventListener( 'pointerup', this.onPointerUp ); + + } + + onSelectStart( event ) { + + this.renderer.domElement.parentElement.appendChild( this.element ); + + this.element.style.left = event.clientX + 'px'; + this.element.style.top = event.clientY + 'px'; + this.element.style.width = '0px'; + this.element.style.height = '0px'; + + this.startPoint.x = event.clientX; + this.startPoint.y = event.clientY; + + } + + onSelectMove( event ) { + + this.pointBottomRight.x = Math.max( this.startPoint.x, event.clientX ); + this.pointBottomRight.y = Math.max( this.startPoint.y, event.clientY ); + this.pointTopLeft.x = Math.min( this.startPoint.x, event.clientX ); + this.pointTopLeft.y = Math.min( this.startPoint.y, event.clientY ); + + this.element.style.left = this.pointTopLeft.x + 'px'; + this.element.style.top = this.pointTopLeft.y + 'px'; + this.element.style.width = ( this.pointBottomRight.x - this.pointTopLeft.x ) + 'px'; + this.element.style.height = ( this.pointBottomRight.y - this.pointTopLeft.y ) + 'px'; + + } + + onSelectOver() { + + this.element.parentElement.removeChild( this.element ); + + } + +} + +export { SelectionHelper }; diff --git a/jsm/libs/OimoPhysics/OimoPhysics.js b/jsm/libs/OimoPhysics/OimoPhysics.js new file mode 100644 index 0000000..3c4eb49 --- /dev/null +++ b/jsm/libs/OimoPhysics/OimoPhysics.js @@ -0,0 +1,37071 @@ +// Generated by Haxe 4.2.0 +var oimo = oimo || {}; +if(!oimo.collision) oimo.collision = {}; +if(!oimo.collision.broadphase) oimo.collision.broadphase = {}; +oimo.collision.broadphase.BroadPhase = class oimo_collision_broadphase_BroadPhase { + constructor(type) { + this._type = type; + this._numProxies = 0; + this._proxyList = null; + this._proxyListLast = null; + this._proxyPairList = null; + this._incremental = false; + this._testCount = 0; + this._proxyPairPool = null; + this._idCount = 0; + this._convexSweep = new oimo.collision.broadphase._BroadPhase.ConvexSweepGeometry(); + this._aabb = new oimo.collision.broadphase._BroadPhase.AabbGeometry(); + this.identity = new oimo.common.Transform(); + this.zero = new oimo.common.Vec3(); + this.rayCastHit = new oimo.collision.geometry.RayCastHit(); + } + createProxy(userData,aabb) { + return null; + } + destroyProxy(proxy) { + } + moveProxy(proxy,aabb,displacement) { + } + isOverlapping(proxy1,proxy2) { + if(proxy1._aabbMinX < proxy2._aabbMaxX && proxy1._aabbMaxX > proxy2._aabbMinX && proxy1._aabbMinY < proxy2._aabbMaxY && proxy1._aabbMaxY > proxy2._aabbMinY && proxy1._aabbMinZ < proxy2._aabbMaxZ) { + return proxy1._aabbMaxZ > proxy2._aabbMinZ; + } else { + return false; + } + } + collectPairs() { + } + getProxyPairList() { + return this._proxyPairList; + } + isIncremental() { + return this._incremental; + } + getTestCount() { + return this._testCount; + } + rayCast(begin,end,callback) { + } + convexCast(convex,begin,translation,callback) { + } + aabbTest(aabb,callback) { + } +} +if(!oimo.collision.geometry) oimo.collision.geometry = {}; +oimo.collision.geometry.Geometry = class oimo_collision_geometry_Geometry { + constructor(type) { + this._type = type; + this._volume = 0; + } + _updateMass() { + } + _computeAabb(aabb,tf) { + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + return false; + } + getType() { + return this._type; + } + getVolume() { + return this._volume; + } + rayCast(begin,end,transform,hit) { + let beginLocalX; + let beginLocalY; + let beginLocalZ; + let endLocalX; + let endLocalY; + let endLocalZ; + beginLocalX = begin.x; + beginLocalY = begin.y; + beginLocalZ = begin.z; + endLocalX = end.x; + endLocalY = end.y; + endLocalZ = end.z; + beginLocalX -= transform._positionX; + beginLocalY -= transform._positionY; + beginLocalZ -= transform._positionZ; + endLocalX -= transform._positionX; + endLocalY -= transform._positionY; + endLocalZ -= transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = transform._rotation00 * beginLocalX + transform._rotation10 * beginLocalY + transform._rotation20 * beginLocalZ; + __tmp__Y = transform._rotation01 * beginLocalX + transform._rotation11 * beginLocalY + transform._rotation21 * beginLocalZ; + __tmp__Z = transform._rotation02 * beginLocalX + transform._rotation12 * beginLocalY + transform._rotation22 * beginLocalZ; + beginLocalX = __tmp__X; + beginLocalY = __tmp__Y; + beginLocalZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = transform._rotation00 * endLocalX + transform._rotation10 * endLocalY + transform._rotation20 * endLocalZ; + __tmp__Y1 = transform._rotation01 * endLocalX + transform._rotation11 * endLocalY + transform._rotation21 * endLocalZ; + __tmp__Z1 = transform._rotation02 * endLocalX + transform._rotation12 * endLocalY + transform._rotation22 * endLocalZ; + endLocalX = __tmp__X1; + endLocalY = __tmp__Y1; + endLocalZ = __tmp__Z1; + if(this._rayCastLocal(beginLocalX,beginLocalY,beginLocalZ,endLocalX,endLocalY,endLocalZ,hit)) { + let localPosX; + let localPosY; + let localPosZ; + let localNormalX; + let localNormalY; + let localNormalZ; + let v = hit.position; + localPosX = v.x; + localPosY = v.y; + localPosZ = v.z; + let v1 = hit.normal; + localNormalX = v1.x; + localNormalY = v1.y; + localNormalZ = v1.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = transform._rotation00 * localPosX + transform._rotation01 * localPosY + transform._rotation02 * localPosZ; + __tmp__Y = transform._rotation10 * localPosX + transform._rotation11 * localPosY + transform._rotation12 * localPosZ; + __tmp__Z = transform._rotation20 * localPosX + transform._rotation21 * localPosY + transform._rotation22 * localPosZ; + localPosX = __tmp__X; + localPosY = __tmp__Y; + localPosZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = transform._rotation00 * localNormalX + transform._rotation01 * localNormalY + transform._rotation02 * localNormalZ; + __tmp__Y1 = transform._rotation10 * localNormalX + transform._rotation11 * localNormalY + transform._rotation12 * localNormalZ; + __tmp__Z1 = transform._rotation20 * localNormalX + transform._rotation21 * localNormalY + transform._rotation22 * localNormalZ; + localNormalX = __tmp__X1; + localNormalY = __tmp__Y1; + localNormalZ = __tmp__Z1; + localPosX += transform._positionX; + localPosY += transform._positionY; + localPosZ += transform._positionZ; + let v2 = hit.position; + v2.x = localPosX; + v2.y = localPosY; + v2.z = localPosZ; + let v3 = hit.normal; + v3.x = localNormalX; + v3.y = localNormalY; + v3.z = localNormalZ; + return true; + } + return false; + } +} +oimo.collision.geometry.ConvexGeometry = class oimo_collision_geometry_ConvexGeometry extends oimo.collision.geometry.Geometry { + constructor(type) { + super(type); + this._gjkMargin = oimo.common.Setting.defaultGJKMargin; + this._useGjkRayCast = false; + } + getGjkMergin() { + return this._gjkMargin; + } + setGjkMergin(gjkMergin) { + if(gjkMergin < 0) { + gjkMergin = 0; + } + this._gjkMargin = gjkMergin; + } + computeLocalSupportingVertex(dir,out) { + } + rayCast(begin,end,transform,hit) { + if(this._useGjkRayCast) { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance.rayCast(this,transform,begin,end,hit); + } else { + return super.rayCast(begin,end,transform,hit); + } + } +} +if(!oimo.collision.broadphase._BroadPhase) oimo.collision.broadphase._BroadPhase = {}; +oimo.collision.broadphase._BroadPhase.ConvexSweepGeometry = class oimo_collision_broadphase__$BroadPhase_ConvexSweepGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor() { + super(-1); + } + init(c,transform,translation) { + this.c = c; + let trX; + let trY; + let trZ; + trX = translation.x; + trY = translation.y; + trZ = translation.z; + let localTrX; + let localTrY; + let localTrZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = transform._rotation00 * trX + transform._rotation10 * trY + transform._rotation20 * trZ; + __tmp__Y = transform._rotation01 * trX + transform._rotation11 * trY + transform._rotation21 * trZ; + __tmp__Z = transform._rotation02 * trX + transform._rotation12 * trY + transform._rotation22 * trZ; + localTrX = __tmp__X; + localTrY = __tmp__Y; + localTrZ = __tmp__Z; + this.localTranslation = new oimo.common.Vec3(); + let v = this.localTranslation; + v.x = localTrX; + v.y = localTrY; + v.z = localTrZ; + this._gjkMargin = c._gjkMargin; + } + computeLocalSupportingVertex(dir,out) { + this.c.computeLocalSupportingVertex(dir,out); + let v = this.localTranslation; + if(dir.x * v.x + dir.y * v.y + dir.z * v.z > 0) { + let v = this.localTranslation; + out.x += v.x; + out.y += v.y; + out.z += v.z; + } + } +} +oimo.collision.broadphase._BroadPhase.AabbGeometry = class oimo_collision_broadphase__$BroadPhase_AabbGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor() { + super(-1); + this.min = new oimo.common.Vec3(); + this.max = new oimo.common.Vec3(); + } + computeLocalSupportingVertex(dir,out) { + out.x = dir.x > 0 ? this.max.x : this.min.x; + out.y = dir.y > 0 ? this.max.y : this.min.y; + out.z = dir.z > 0 ? this.max.z : this.min.z; + } +} +oimo.collision.broadphase.BroadPhaseProxyCallback = class oimo_collision_broadphase_BroadPhaseProxyCallback { + constructor() { + } + process(proxy) { + } +} +oimo.collision.broadphase.BroadPhaseType = class oimo_collision_broadphase_BroadPhaseType { +} +oimo.collision.broadphase.Proxy = class oimo_collision_broadphase_Proxy { + constructor(userData,id) { + this.userData = userData; + this._id = id; + this._prev = null; + this._next = null; + this._aabbMinX = 0; + this._aabbMinY = 0; + this._aabbMinZ = 0; + this._aabbMaxX = 0; + this._aabbMaxY = 0; + this._aabbMaxZ = 0; + } + getId() { + return this._id; + } + getFatAabb() { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._aabbMinX; + aabb._minY = this._aabbMinY; + aabb._minZ = this._aabbMinZ; + aabb._maxX = this._aabbMaxX; + aabb._maxY = this._aabbMaxY; + aabb._maxZ = this._aabbMaxZ; + return aabb; + } + getFatAabbTo(aabb) { + aabb._minX = this._aabbMinX; + aabb._minY = this._aabbMinY; + aabb._minZ = this._aabbMinZ; + aabb._maxX = this._aabbMaxX; + aabb._maxY = this._aabbMaxY; + aabb._maxZ = this._aabbMaxZ; + } +} +oimo.collision.broadphase.ProxyPair = class oimo_collision_broadphase_ProxyPair { + constructor() { + this._p1 = null; + this._p2 = null; + } + getProxy1() { + return this._p1; + } + getProxy2() { + return this._p2; + } + getNext() { + return this._next; + } +} +if(!oimo.collision.broadphase.bruteforce) oimo.collision.broadphase.bruteforce = {}; +oimo.collision.broadphase.bruteforce.BruteForceBroadPhase = class oimo_collision_broadphase_bruteforce_BruteForceBroadPhase extends oimo.collision.broadphase.BroadPhase { + constructor() { + super(1); + this._incremental = false; + } + createProxy(userData,aabb) { + let proxy = new oimo.collision.broadphase.Proxy(userData,this._idCount++); + this._numProxies++; + if(this._proxyList == null) { + this._proxyList = proxy; + this._proxyListLast = proxy; + } else { + this._proxyListLast._next = proxy; + proxy._prev = this._proxyListLast; + this._proxyListLast = proxy; + } + proxy._aabbMinX = aabb._minX; + proxy._aabbMinY = aabb._minY; + proxy._aabbMinZ = aabb._minZ; + proxy._aabbMaxX = aabb._maxX; + proxy._aabbMaxY = aabb._maxY; + proxy._aabbMaxZ = aabb._maxZ; + return proxy; + } + destroyProxy(proxy) { + this._numProxies--; + let prev = proxy._prev; + let next = proxy._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(proxy == this._proxyList) { + this._proxyList = this._proxyList._next; + } + if(proxy == this._proxyListLast) { + this._proxyListLast = this._proxyListLast._prev; + } + proxy._next = null; + proxy._prev = null; + proxy.userData = null; + } + moveProxy(proxy,aabb,dislacement) { + proxy._aabbMinX = aabb._minX; + proxy._aabbMinY = aabb._minY; + proxy._aabbMinZ = aabb._minZ; + proxy._aabbMaxX = aabb._maxX; + proxy._aabbMaxY = aabb._maxY; + proxy._aabbMaxZ = aabb._maxZ; + } + collectPairs() { + let p = this._proxyPairList; + if(p != null) { + while(true) { + p._p1 = null; + p._p2 = null; + p = p._next; + if(!(p != null)) { + break; + } + } + this._proxyPairList._next = this._proxyPairPool; + this._proxyPairPool = this._proxyPairList; + this._proxyPairList = null; + } + this._testCount = 0; + let p1 = this._proxyList; + while(p1 != null) { + let n = p1._next; + let p2 = p1._next; + while(p2 != null) { + let n = p2._next; + this._testCount++; + if(p1._aabbMinX < p2._aabbMaxX && p1._aabbMaxX > p2._aabbMinX && p1._aabbMinY < p2._aabbMaxY && p1._aabbMaxY > p2._aabbMinY && p1._aabbMinZ < p2._aabbMaxZ && p1._aabbMaxZ > p2._aabbMinZ) { + let first = this._proxyPairPool; + if(first != null) { + this._proxyPairPool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.ProxyPair(); + } + let pp = first; + if(this._proxyPairList == null) { + this._proxyPairList = pp; + } else { + pp._next = this._proxyPairList; + this._proxyPairList = pp; + } + pp._p1 = p1; + pp._p2 = p2; + } + p2 = n; + } + p1 = n; + } + } + rayCast(begin,end,callback) { + let p1X; + let p1Y; + let p1Z; + let p2X; + let p2Y; + let p2Z; + p1X = begin.x; + p1Y = begin.y; + p1Z = begin.z; + p2X = end.x; + p2Y = end.y; + p2Z = end.z; + let p = this._proxyList; + while(p != null) { + let n = p._next; + let x1 = p1X; + let y1 = p1Y; + let z1 = p1Z; + let x2 = p2X; + let y2 = p2Y; + let z2 = p2Z; + let pminx = p._aabbMinX; + let pminy = p._aabbMinY; + let pminz = p._aabbMinZ; + let pmaxx = p._aabbMaxX; + let pmaxy = p._aabbMaxY; + let pmaxz = p._aabbMaxZ; + let tmp; + if(pminx > (x1 > x2 ? x1 : x2) || pmaxx < (x1 < x2 ? x1 : x2) || pminy > (y1 > y2 ? y1 : y2) || pmaxy < (y1 < y2 ? y1 : y2) || pminz > (z1 > z2 ? z1 : z2) || pmaxz < (z1 < z2 ? z1 : z2)) { + tmp = false; + } else { + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + let adx = dx < 0 ? -dx : dx; + let ady = dy < 0 ? -dy : dy; + let adz = dz < 0 ? -dz : dz; + let pextx = (pmaxx - pminx) * 0.5; + let pexty = (pmaxy - pminy) * 0.5; + let pextz = (pmaxz - pminz) * 0.5; + let cpx = x1 - (pmaxx + pminx) * 0.5; + let cpy = y1 - (pmaxy + pminy) * 0.5; + let cpz = z1 - (pmaxz + pminz) * 0.5; + let tmp1; + let tmp2; + let x = cpy * dz - cpz * dy; + if(!((x < 0 ? -x : x) - (pexty * adz + pextz * ady) > 0)) { + let x = cpz * dx - cpx * dz; + tmp2 = (x < 0 ? -x : x) - (pextz * adx + pextx * adz) > 0; + } else { + tmp2 = true; + } + if(!tmp2) { + let x = cpx * dy - cpy * dx; + tmp1 = (x < 0 ? -x : x) - (pextx * ady + pexty * adx) > 0; + } else { + tmp1 = true; + } + tmp = tmp1 ? false : true; + } + if(tmp) { + callback.process(p); + } + p = n; + } + } + convexCast(convex,begin,translation,callback) { + let p = this._proxyList; + while(p != null) { + let n = p._next; + let v = this._aabb.min; + v.x = p._aabbMinX; + v.y = p._aabbMinY; + v.z = p._aabbMinZ; + let v1 = this._aabb.max; + v1.x = p._aabbMaxX; + v1.y = p._aabbMaxY; + v1.z = p._aabbMaxZ; + this._convexSweep.init(convex,begin,translation); + let gjkEpa = oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + if(gjkEpa.computeClosestPointsImpl(this._convexSweep,this._aabb,begin,this.identity,null,false) == 0 && gjkEpa.distance <= 0) { + callback.process(p); + } + p = n; + } + } + aabbTest(aabb,callback) { + let p = this._proxyList; + while(p != null) { + let n = p._next; + if(aabb._minX < p._aabbMaxX && aabb._maxX > p._aabbMinX && aabb._minY < p._aabbMaxY && aabb._maxY > p._aabbMinY && aabb._minZ < p._aabbMaxZ && aabb._maxZ > p._aabbMinZ) { + callback.process(p); + } + p = n; + } + } +} +if(!oimo.collision.broadphase.bvh) oimo.collision.broadphase.bvh = {}; +oimo.collision.broadphase.bvh.BvhBroadPhase = class oimo_collision_broadphase_bvh_BvhBroadPhase extends oimo.collision.broadphase.BroadPhase { + constructor() { + super(2); + this._incremental = true; + this._tree = new oimo.collision.broadphase.bvh.BvhTree(); + this.movedProxies = new Array(1024); + this.numMovedProxies = 0; + } + collide(n1,n2) { + this._testCount++; + let l1 = n1._height == 0; + let l2 = n2._height == 0; + if(n1 == n2) { + if(l1) { + return; + } + this.collide(n1._children[0],n2); + this.collide(n1._children[1],n2); + return; + } + if(!(n1._aabbMinX < n2._aabbMaxX && n1._aabbMaxX > n2._aabbMinX && n1._aabbMinY < n2._aabbMaxY && n1._aabbMaxY > n2._aabbMinY && n1._aabbMinZ < n2._aabbMaxZ && n1._aabbMaxZ > n2._aabbMinZ)) { + return; + } + if(l1 && l2) { + let first = this._proxyPairPool; + if(first != null) { + this._proxyPairPool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.ProxyPair(); + } + let pp = first; + if(this._proxyPairList == null) { + this._proxyPairList = pp; + } else { + pp._next = this._proxyPairList; + this._proxyPairList = pp; + } + pp._p1 = n1._proxy; + pp._p2 = n2._proxy; + return; + } + if(l2 || n1._height > n2._height) { + this.collide(n1._children[0],n2); + this.collide(n1._children[1],n2); + } else { + this.collide(n2._children[0],n1); + this.collide(n2._children[1],n1); + } + } + rayCastRecursive(node,_p1X,_p1Y,_p1Z,_p2X,_p2Y,_p2Z,callback) { + let x1 = _p1X; + let y1 = _p1Y; + let z1 = _p1Z; + let x2 = _p2X; + let y2 = _p2Y; + let z2 = _p2Z; + let pminx = node._aabbMinX; + let pminy = node._aabbMinY; + let pminz = node._aabbMinZ; + let pmaxx = node._aabbMaxX; + let pmaxy = node._aabbMaxY; + let pmaxz = node._aabbMaxZ; + let tmp; + if(pminx > (x1 > x2 ? x1 : x2) || pmaxx < (x1 < x2 ? x1 : x2) || pminy > (y1 > y2 ? y1 : y2) || pmaxy < (y1 < y2 ? y1 : y2) || pminz > (z1 > z2 ? z1 : z2) || pmaxz < (z1 < z2 ? z1 : z2)) { + tmp = false; + } else { + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + let adx = dx < 0 ? -dx : dx; + let ady = dy < 0 ? -dy : dy; + let adz = dz < 0 ? -dz : dz; + let pextx = (pmaxx - pminx) * 0.5; + let pexty = (pmaxy - pminy) * 0.5; + let pextz = (pmaxz - pminz) * 0.5; + let cpx = x1 - (pmaxx + pminx) * 0.5; + let cpy = y1 - (pmaxy + pminy) * 0.5; + let cpz = z1 - (pmaxz + pminz) * 0.5; + let tmp1; + let tmp2; + let x = cpy * dz - cpz * dy; + if(!((x < 0 ? -x : x) - (pexty * adz + pextz * ady) > 0)) { + let x = cpz * dx - cpx * dz; + tmp2 = (x < 0 ? -x : x) - (pextz * adx + pextx * adz) > 0; + } else { + tmp2 = true; + } + if(!tmp2) { + let x = cpx * dy - cpy * dx; + tmp1 = (x < 0 ? -x : x) - (pextx * ady + pexty * adx) > 0; + } else { + tmp1 = true; + } + tmp = tmp1 ? false : true; + } + if(!tmp) { + return; + } + if(node._height == 0) { + callback.process(node._proxy); + return; + } + this.rayCastRecursive(node._children[0],_p1X,_p1Y,_p1Z,_p2X,_p2Y,_p2Z,callback); + this.rayCastRecursive(node._children[1],_p1X,_p1Y,_p1Z,_p2X,_p2Y,_p2Z,callback); + } + convexCastRecursive(node,convex,begin,translation,callback) { + let v = this._aabb.min; + v.x = node._aabbMinX; + v.y = node._aabbMinY; + v.z = node._aabbMinZ; + let v1 = this._aabb.max; + v1.x = node._aabbMaxX; + v1.y = node._aabbMaxY; + v1.z = node._aabbMaxZ; + this._convexSweep.init(convex,begin,translation); + let gjkEpa = oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + if(!(gjkEpa.computeClosestPointsImpl(this._convexSweep,this._aabb,begin,this.identity,null,false) == 0 && gjkEpa.distance <= 0)) { + return; + } + if(node._height == 0) { + callback.process(node._proxy); + return; + } + this.convexCastRecursive(node._children[0],convex,begin,translation,callback); + this.convexCastRecursive(node._children[1],convex,begin,translation,callback); + } + aabbTestRecursive(node,aabb,callback) { + if(!(node._aabbMinX < aabb._maxX && node._aabbMaxX > aabb._minX && node._aabbMinY < aabb._maxY && node._aabbMaxY > aabb._minY && node._aabbMinZ < aabb._maxZ && node._aabbMaxZ > aabb._minZ)) { + return; + } + if(node._height == 0) { + callback.process(node._proxy); + return; + } + this.aabbTestRecursive(node._children[0],aabb,callback); + this.aabbTestRecursive(node._children[1],aabb,callback); + } + createProxy(userData,aabb) { + let p = new oimo.collision.broadphase.bvh.BvhProxy(userData,this._idCount++); + this._numProxies++; + if(this._proxyList == null) { + this._proxyList = p; + this._proxyListLast = p; + } else { + this._proxyListLast._next = p; + p._prev = this._proxyListLast; + this._proxyListLast = p; + } + p._aabbMinX = aabb._minX; + p._aabbMinY = aabb._minY; + p._aabbMinZ = aabb._minZ; + p._aabbMaxX = aabb._maxX; + p._aabbMaxY = aabb._maxY; + p._aabbMaxZ = aabb._maxZ; + let padding = oimo.common.Setting.bvhProxyPadding; + p._aabbMinX -= padding; + p._aabbMinY -= padding; + p._aabbMinZ -= padding; + p._aabbMaxX += padding; + p._aabbMaxY += padding; + p._aabbMaxZ += padding; + let _this = this._tree; + let first = _this._nodePool; + if(first != null) { + _this._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let leaf = first; + leaf._proxy = p; + p._leaf = leaf; + leaf._aabbMinX = p._aabbMinX; + leaf._aabbMinY = p._aabbMinY; + leaf._aabbMinZ = p._aabbMinZ; + leaf._aabbMaxX = p._aabbMaxX; + leaf._aabbMaxY = p._aabbMaxY; + leaf._aabbMaxZ = p._aabbMaxZ; + _this._numLeaves++; + if(_this.leafList == null) { + _this.leafList = leaf; + _this.leafListLast = leaf; + } else { + _this.leafListLast._nextLeaf = leaf; + leaf._prevLeaf = _this.leafListLast; + _this.leafListLast = leaf; + } + if(_this._root == null) { + _this._root = leaf; + } else { + let sibling = _this._root; + while(sibling._height > 0) { + let nextStep = _this._strategy._decideInsertion(sibling,leaf); + if(nextStep == -1) { + break; + } else { + sibling = sibling._children[nextStep]; + } + } + let parent = sibling._parent; + let first = _this._nodePool; + if(first != null) { + _this._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let node = first; + if(parent == null) { + _this._root = node; + } else { + let index = sibling._childIndex; + parent._children[index] = node; + node._parent = parent; + node._childIndex = index; + } + let index = sibling._childIndex; + node._children[index] = sibling; + sibling._parent = node; + sibling._childIndex = index; + let index1 = sibling._childIndex ^ 1; + node._children[index1] = leaf; + leaf._parent = node; + leaf._childIndex = index1; + while(node != null) { + if(_this._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + if(!p._moved) { + p._moved = true; + if(this.movedProxies.length == this.numMovedProxies) { + let newArray = new Array(this.numMovedProxies << 1); + let _g = 0; + let _g1 = this.numMovedProxies; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.movedProxies[i]; + this.movedProxies[i] = null; + } + this.movedProxies = newArray; + } + this.movedProxies[this.numMovedProxies++] = p; + } + return p; + } + destroyProxy(proxy) { + this._numProxies--; + let prev = proxy._prev; + let next = proxy._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(proxy == this._proxyList) { + this._proxyList = this._proxyList._next; + } + if(proxy == this._proxyListLast) { + this._proxyListLast = this._proxyListLast._prev; + } + proxy._next = null; + proxy._prev = null; + let bvhProxy = proxy; + let _this = this._tree; + let leaf = bvhProxy._leaf; + _this._numLeaves--; + let prev1 = leaf._prevLeaf; + let next1 = leaf._nextLeaf; + if(prev1 != null) { + prev1._nextLeaf = next1; + } + if(next1 != null) { + next1._prevLeaf = prev1; + } + if(leaf == _this.leafList) { + _this.leafList = _this.leafList._nextLeaf; + } + if(leaf == _this.leafListLast) { + _this.leafListLast = _this.leafListLast._prevLeaf; + } + leaf._nextLeaf = null; + leaf._prevLeaf = null; + if(_this._root == leaf) { + _this._root = null; + } else { + let parent = leaf._parent; + let sibling = parent._children[leaf._childIndex ^ 1]; + let grandParent = parent._parent; + if(grandParent == null) { + sibling._parent = null; + sibling._childIndex = 0; + _this._root = sibling; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + } else { + sibling._parent = grandParent; + let index = parent._childIndex; + grandParent._children[index] = sibling; + sibling._parent = grandParent; + sibling._childIndex = index; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + let node = grandParent; + while(node != null) { + if(_this._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + } + bvhProxy._leaf = null; + leaf._next = null; + leaf._childIndex = 0; + leaf._children[0] = null; + leaf._children[1] = null; + leaf._childIndex = 0; + leaf._parent = null; + leaf._height = 0; + leaf._proxy = null; + leaf._next = _this._nodePool; + _this._nodePool = leaf; + bvhProxy.userData = null; + bvhProxy._next = null; + bvhProxy._prev = null; + if(bvhProxy._moved) { + bvhProxy._moved = false; + } + } + moveProxy(proxy,aabb,displacement) { + let p = proxy; + if(p._aabbMinX <= aabb._minX && p._aabbMaxX >= aabb._maxX && p._aabbMinY <= aabb._minY && p._aabbMaxY >= aabb._maxY && p._aabbMinZ <= aabb._minZ && p._aabbMaxZ >= aabb._maxZ) { + return; + } + p._aabbMinX = aabb._minX; + p._aabbMinY = aabb._minY; + p._aabbMinZ = aabb._minZ; + p._aabbMaxX = aabb._maxX; + p._aabbMaxY = aabb._maxY; + p._aabbMaxZ = aabb._maxZ; + let padding = oimo.common.Setting.bvhProxyPadding; + p._aabbMinX -= padding; + p._aabbMinY -= padding; + p._aabbMinZ -= padding; + p._aabbMaxX += padding; + p._aabbMaxY += padding; + p._aabbMaxZ += padding; + if(displacement != null) { + let dX; + let dY; + let dZ; + let zeroX; + let zeroY; + let zeroZ; + let addToMinX; + let addToMinY; + let addToMinZ; + let addToMaxX; + let addToMaxY; + let addToMaxZ; + zeroX = 0; + zeroY = 0; + zeroZ = 0; + dX = displacement.x; + dY = displacement.y; + dZ = displacement.z; + addToMinX = zeroX < dX ? zeroX : dX; + addToMinY = zeroY < dY ? zeroY : dY; + addToMinZ = zeroZ < dZ ? zeroZ : dZ; + addToMaxX = zeroX > dX ? zeroX : dX; + addToMaxY = zeroY > dY ? zeroY : dY; + addToMaxZ = zeroZ > dZ ? zeroZ : dZ; + p._aabbMinX += addToMinX; + p._aabbMinY += addToMinY; + p._aabbMinZ += addToMinZ; + p._aabbMaxX += addToMaxX; + p._aabbMaxY += addToMaxY; + p._aabbMaxZ += addToMaxZ; + } + if(!p._moved) { + p._moved = true; + if(this.movedProxies.length == this.numMovedProxies) { + let newArray = new Array(this.numMovedProxies << 1); + let _g = 0; + let _g1 = this.numMovedProxies; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.movedProxies[i]; + this.movedProxies[i] = null; + } + this.movedProxies = newArray; + } + this.movedProxies[this.numMovedProxies++] = p; + } + } + collectPairs() { + let p = this._proxyPairList; + if(p != null) { + while(true) { + p._p1 = null; + p._p2 = null; + p = p._next; + if(!(p != null)) { + break; + } + } + this._proxyPairList._next = this._proxyPairPool; + this._proxyPairPool = this._proxyPairList; + this._proxyPairList = null; + } + this._testCount = 0; + if(this._numProxies < 2) { + return; + } + let incrementalCollision = this.numMovedProxies / this._numProxies < oimo.common.Setting.bvhIncrementalCollisionThreshold; + let _g = 0; + let _g1 = this.numMovedProxies; + while(_g < _g1) { + let i = _g++; + let p = this.movedProxies[i]; + if(p._moved) { + let _this = this._tree; + let leaf = p._leaf; + _this._numLeaves--; + let prev = leaf._prevLeaf; + let next = leaf._nextLeaf; + if(prev != null) { + prev._nextLeaf = next; + } + if(next != null) { + next._prevLeaf = prev; + } + if(leaf == _this.leafList) { + _this.leafList = _this.leafList._nextLeaf; + } + if(leaf == _this.leafListLast) { + _this.leafListLast = _this.leafListLast._prevLeaf; + } + leaf._nextLeaf = null; + leaf._prevLeaf = null; + if(_this._root == leaf) { + _this._root = null; + } else { + let parent = leaf._parent; + let sibling = parent._children[leaf._childIndex ^ 1]; + let grandParent = parent._parent; + if(grandParent == null) { + sibling._parent = null; + sibling._childIndex = 0; + _this._root = sibling; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + } else { + sibling._parent = grandParent; + let index = parent._childIndex; + grandParent._children[index] = sibling; + sibling._parent = grandParent; + sibling._childIndex = index; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + let node = grandParent; + while(node != null) { + if(_this._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + } + p._leaf = null; + leaf._next = null; + leaf._childIndex = 0; + leaf._children[0] = null; + leaf._children[1] = null; + leaf._childIndex = 0; + leaf._parent = null; + leaf._height = 0; + leaf._proxy = null; + leaf._next = _this._nodePool; + _this._nodePool = leaf; + let _this1 = this._tree; + let first = _this1._nodePool; + if(first != null) { + _this1._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let leaf1 = first; + leaf1._proxy = p; + p._leaf = leaf1; + leaf1._aabbMinX = p._aabbMinX; + leaf1._aabbMinY = p._aabbMinY; + leaf1._aabbMinZ = p._aabbMinZ; + leaf1._aabbMaxX = p._aabbMaxX; + leaf1._aabbMaxY = p._aabbMaxY; + leaf1._aabbMaxZ = p._aabbMaxZ; + _this1._numLeaves++; + if(_this1.leafList == null) { + _this1.leafList = leaf1; + _this1.leafListLast = leaf1; + } else { + _this1.leafListLast._nextLeaf = leaf1; + leaf1._prevLeaf = _this1.leafListLast; + _this1.leafListLast = leaf1; + } + if(_this1._root == null) { + _this1._root = leaf1; + } else { + let sibling = _this1._root; + while(sibling._height > 0) { + let nextStep = _this1._strategy._decideInsertion(sibling,leaf1); + if(nextStep == -1) { + break; + } else { + sibling = sibling._children[nextStep]; + } + } + let parent = sibling._parent; + let first = _this1._nodePool; + if(first != null) { + _this1._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let node = first; + if(parent == null) { + _this1._root = node; + } else { + let index = sibling._childIndex; + parent._children[index] = node; + node._parent = parent; + node._childIndex = index; + } + let index = sibling._childIndex; + node._children[index] = sibling; + sibling._parent = node; + sibling._childIndex = index; + let index1 = sibling._childIndex ^ 1; + node._children[index1] = leaf1; + leaf1._parent = node; + leaf1._childIndex = index1; + while(node != null) { + if(_this1._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this1._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this1._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + if(incrementalCollision) { + this.collide(this._tree._root,p._leaf); + } + p._moved = false; + } + this.movedProxies[i] = null; + } + if(!incrementalCollision) { + this.collide(this._tree._root,this._tree._root); + } + this.numMovedProxies = 0; + } + rayCast(begin,end,callback) { + if(this._tree._root == null) { + return; + } + let p1X; + let p1Y; + let p1Z; + let p2X; + let p2Y; + let p2Z; + p1X = begin.x; + p1Y = begin.y; + p1Z = begin.z; + p2X = end.x; + p2Y = end.y; + p2Z = end.z; + this.rayCastRecursive(this._tree._root,p1X,p1Y,p1Z,p2X,p2Y,p2Z,callback); + } + convexCast(convex,begin,translation,callback) { + if(this._tree._root == null) { + return; + } + this.convexCastRecursive(this._tree._root,convex,begin,translation,callback); + } + aabbTest(aabb,callback) { + if(this._tree._root == null) { + return; + } + this.aabbTestRecursive(this._tree._root,aabb,callback); + } + getTreeBalance() { + return this._tree._getBalance(); + } +} +oimo.collision.broadphase.bvh.BvhInsertionStrategy = class oimo_collision_broadphase_bvh_BvhInsertionStrategy { +} +oimo.collision.broadphase.bvh.BvhNode = class oimo_collision_broadphase_bvh_BvhNode { + constructor() { + this._next = null; + this._prevLeaf = null; + this._nextLeaf = null; + this._children = new Array(2); + this._childIndex = 0; + this._parent = null; + this._height = 0; + this._proxy = null; + this._aabbMinX = 0; + this._aabbMinY = 0; + this._aabbMinZ = 0; + this._aabbMaxX = 0; + this._aabbMaxY = 0; + this._aabbMaxZ = 0; + } +} +oimo.collision.broadphase.bvh.BvhProxy = class oimo_collision_broadphase_bvh_BvhProxy extends oimo.collision.broadphase.Proxy { + constructor(userData,id) { + super(userData,id); + this._leaf = null; + this._moved = false; + } +} +oimo.collision.broadphase.bvh.BvhStrategy = class oimo_collision_broadphase_bvh_BvhStrategy { + constructor() { + this._insertionStrategy = 0; + this._balancingEnabled = false; + } + _decideInsertion(currentNode,leaf) { + switch(this._insertionStrategy) { + case 0: + let centerX; + let centerY; + let centerZ; + centerX = leaf._aabbMinX + leaf._aabbMaxX; + centerY = leaf._aabbMinY + leaf._aabbMaxY; + centerZ = leaf._aabbMinZ + leaf._aabbMaxZ; + let c1 = currentNode._children[0]; + let c2 = currentNode._children[1]; + let diff1X; + let diff1Y; + let diff1Z; + let diff2X; + let diff2Y; + let diff2Z; + diff1X = c1._aabbMinX + c1._aabbMaxX; + diff1Y = c1._aabbMinY + c1._aabbMaxY; + diff1Z = c1._aabbMinZ + c1._aabbMaxZ; + diff2X = c2._aabbMinX + c2._aabbMaxX; + diff2Y = c2._aabbMinY + c2._aabbMaxY; + diff2Z = c2._aabbMinZ + c2._aabbMaxZ; + diff1X -= centerX; + diff1Y -= centerY; + diff1Z -= centerZ; + diff2X -= centerX; + diff2Y -= centerY; + diff2Z -= centerZ; + if(diff1X * diff1X + diff1Y * diff1Y + diff1Z * diff1Z < diff2X * diff2X + diff2Y * diff2Y + diff2Z * diff2Z) { + return 0; + } else { + return 1; + } + break; + case 1: + let c11 = currentNode._children[0]; + let c21 = currentNode._children[1]; + let ey = currentNode._aabbMaxY - currentNode._aabbMinY; + let ez = currentNode._aabbMaxZ - currentNode._aabbMinZ; + let combinedMinX; + let combinedMinY; + let combinedMinZ; + let combinedMaxX; + let combinedMaxY; + let combinedMaxZ; + combinedMinX = currentNode._aabbMinX < leaf._aabbMinX ? currentNode._aabbMinX : leaf._aabbMinX; + combinedMinY = currentNode._aabbMinY < leaf._aabbMinY ? currentNode._aabbMinY : leaf._aabbMinY; + combinedMinZ = currentNode._aabbMinZ < leaf._aabbMinZ ? currentNode._aabbMinZ : leaf._aabbMinZ; + combinedMaxX = currentNode._aabbMaxX > leaf._aabbMaxX ? currentNode._aabbMaxX : leaf._aabbMaxX; + combinedMaxY = currentNode._aabbMaxY > leaf._aabbMaxY ? currentNode._aabbMaxY : leaf._aabbMaxY; + combinedMaxZ = currentNode._aabbMaxZ > leaf._aabbMaxZ ? currentNode._aabbMaxZ : leaf._aabbMaxZ; + let ey1 = combinedMaxY - combinedMinY; + let ez1 = combinedMaxZ - combinedMinZ; + let newArea = ((combinedMaxX - combinedMinX) * (ey1 + ez1) + ey1 * ez1) * 2; + let creatingCost = newArea * 2; + let incrementalCost = (newArea - ((currentNode._aabbMaxX - currentNode._aabbMinX) * (ey + ez) + ey * ez) * 2) * 2; + let descendingCost1; + combinedMinX = c11._aabbMinX < leaf._aabbMinX ? c11._aabbMinX : leaf._aabbMinX; + combinedMinY = c11._aabbMinY < leaf._aabbMinY ? c11._aabbMinY : leaf._aabbMinY; + combinedMinZ = c11._aabbMinZ < leaf._aabbMinZ ? c11._aabbMinZ : leaf._aabbMinZ; + combinedMaxX = c11._aabbMaxX > leaf._aabbMaxX ? c11._aabbMaxX : leaf._aabbMaxX; + combinedMaxY = c11._aabbMaxY > leaf._aabbMaxY ? c11._aabbMaxY : leaf._aabbMaxY; + combinedMaxZ = c11._aabbMaxZ > leaf._aabbMaxZ ? c11._aabbMaxZ : leaf._aabbMaxZ; + if(c11._height == 0) { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + descendingCost1 = incrementalCost + ((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2; + } else { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + let ey1 = c11._aabbMaxY - c11._aabbMinY; + let ez1 = c11._aabbMaxZ - c11._aabbMinZ; + descendingCost1 = incrementalCost + (((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2 - ((c11._aabbMaxX - c11._aabbMinX) * (ey1 + ez1) + ey1 * ez1) * 2); + } + let descendingCost2; + combinedMinX = c21._aabbMinX < leaf._aabbMinX ? c21._aabbMinX : leaf._aabbMinX; + combinedMinY = c21._aabbMinY < leaf._aabbMinY ? c21._aabbMinY : leaf._aabbMinY; + combinedMinZ = c21._aabbMinZ < leaf._aabbMinZ ? c21._aabbMinZ : leaf._aabbMinZ; + combinedMaxX = c21._aabbMaxX > leaf._aabbMaxX ? c21._aabbMaxX : leaf._aabbMaxX; + combinedMaxY = c21._aabbMaxY > leaf._aabbMaxY ? c21._aabbMaxY : leaf._aabbMaxY; + combinedMaxZ = c21._aabbMaxZ > leaf._aabbMaxZ ? c21._aabbMaxZ : leaf._aabbMaxZ; + if(c21._height == 0) { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + descendingCost2 = incrementalCost + ((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2; + } else { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + let ey1 = c21._aabbMaxY - c21._aabbMinY; + let ez1 = c21._aabbMaxZ - c21._aabbMinZ; + descendingCost2 = incrementalCost + (((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2 - ((c21._aabbMaxX - c21._aabbMinX) * (ey1 + ez1) + ey1 * ez1) * 2); + } + if(creatingCost < descendingCost1) { + if(creatingCost < descendingCost2) { + return -1; + } else { + return 1; + } + } else if(descendingCost1 < descendingCost2) { + return 0; + } else { + return 1; + } + break; + default: + console.log("src/oimo/collision/broadphase/bvh/BvhStrategy.hx:37:","invalid BVH insertion strategy: " + this._insertionStrategy); + return -1; + } + } + _splitLeaves(leaves,from,until) { + let invN = 1.0 / (until - from); + let centerMeanX; + let centerMeanY; + let centerMeanZ; + centerMeanX = 0; + centerMeanY = 0; + centerMeanZ = 0; + let _g = from; + while(_g < until) { + let leaf = leaves[_g++]; + leaf._tmpX = leaf._aabbMaxX + leaf._aabbMinX; + leaf._tmpY = leaf._aabbMaxY + leaf._aabbMinY; + leaf._tmpZ = leaf._aabbMaxZ + leaf._aabbMinZ; + centerMeanX += leaf._tmpX; + centerMeanY += leaf._tmpY; + centerMeanZ += leaf._tmpZ; + } + centerMeanX *= invN; + centerMeanY *= invN; + centerMeanZ *= invN; + let varianceX; + let varianceY; + let varianceZ; + varianceX = 0; + varianceY = 0; + varianceZ = 0; + let _g1 = from; + while(_g1 < until) { + let leaf = leaves[_g1++]; + let diffX; + let diffY; + let diffZ; + diffX = leaf._tmpX - centerMeanX; + diffY = leaf._tmpY - centerMeanY; + diffZ = leaf._tmpZ - centerMeanZ; + diffX *= diffX; + diffY *= diffY; + diffZ *= diffZ; + varianceX += diffX; + varianceY += diffY; + varianceZ += diffZ; + } + let varX = varianceX; + let varY = varianceY; + let varZ = varianceZ; + let l = from; + let r = until - 1; + if(varX > varY) { + if(varX > varZ) { + let mean = centerMeanX; + while(true) { + while(!(leaves[l]._tmpX <= mean)) ++l; + while(!(leaves[r]._tmpX >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } else { + let mean = centerMeanZ; + while(true) { + while(!(leaves[l]._tmpZ <= mean)) ++l; + while(!(leaves[r]._tmpZ >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } + } else if(varY > varZ) { + let mean = centerMeanY; + while(true) { + while(!(leaves[l]._tmpY <= mean)) ++l; + while(!(leaves[r]._tmpY >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } else { + let mean = centerMeanZ; + while(true) { + while(!(leaves[l]._tmpZ <= mean)) ++l; + while(!(leaves[r]._tmpZ >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } + return l; + } +} +oimo.collision.broadphase.bvh.BvhTree = class oimo_collision_broadphase_bvh_BvhTree { + constructor() { + this._root = null; + this._numLeaves = 0; + this._strategy = new oimo.collision.broadphase.bvh.BvhStrategy(); + this._nodePool = null; + this.leafList = null; + this.leafListLast = null; + this.tmp = new Array(1024); + } + _print(root,indent) { + if(indent == null) { + indent = ""; + } + if(root == null) { + return; + } + if(root._height == 0) { + console.log("src/oimo/collision/broadphase/bvh/BvhTree.hx:39:",indent + root._proxy._id); + } else { + this._print(root._children[0],indent + " "); + let tmp; + let sizeX; + let sizeY; + let sizeZ; + sizeX = root._aabbMaxX - root._aabbMinX; + sizeY = root._aabbMaxY - root._aabbMinY; + sizeZ = root._aabbMaxZ - root._aabbMinZ; + let y = sizeY; + let z = sizeZ; + if(sizeX * (y + z) + y * z > 0) { + let sizeX; + let sizeY; + let sizeZ; + sizeX = root._aabbMaxX - root._aabbMinX; + sizeY = root._aabbMaxY - root._aabbMinY; + sizeZ = root._aabbMaxZ - root._aabbMinZ; + let y = sizeY; + let z = sizeZ; + tmp = ((sizeX * (y + z) + y * z) * 1000 + 0.5 | 0) / 1000; + } else { + let sizeX; + let sizeY; + let sizeZ; + sizeX = root._aabbMaxX - root._aabbMinX; + sizeY = root._aabbMaxY - root._aabbMinY; + sizeZ = root._aabbMaxZ - root._aabbMinZ; + let y = sizeY; + let z = sizeZ; + tmp = ((sizeX * (y + z) + y * z) * 1000 - 0.5 | 0) / 1000; + } + console.log("src/oimo/collision/broadphase/bvh/BvhTree.hx:42:",indent + "#" + root._height + ", " + tmp); + this._print(root._children[1],indent + " "); + } + } + _getBalance() { + return this.getBalanceRecursive(this._root); + } + deleteRecursive(root) { + if(root._height == 0) { + let prev = root._prevLeaf; + let next = root._nextLeaf; + if(prev != null) { + prev._nextLeaf = next; + } + if(next != null) { + next._prevLeaf = prev; + } + if(root == this.leafList) { + this.leafList = this.leafList._nextLeaf; + } + if(root == this.leafListLast) { + this.leafListLast = this.leafListLast._prevLeaf; + } + root._nextLeaf = null; + root._prevLeaf = null; + root._proxy._leaf = null; + root._next = null; + root._childIndex = 0; + root._children[0] = null; + root._children[1] = null; + root._childIndex = 0; + root._parent = null; + root._height = 0; + root._proxy = null; + root._next = this._nodePool; + this._nodePool = root; + return; + } + this.deleteRecursive(root._children[0]); + this.deleteRecursive(root._children[1]); + root._next = null; + root._childIndex = 0; + root._children[0] = null; + root._children[1] = null; + root._childIndex = 0; + root._parent = null; + root._height = 0; + root._proxy = null; + root._next = this._nodePool; + this._nodePool = root; + } + decomposeRecursive(root) { + if(root._height == 0) { + root._childIndex = 0; + root._parent = null; + return; + } + this.decomposeRecursive(root._children[0]); + this.decomposeRecursive(root._children[1]); + root._next = null; + root._childIndex = 0; + root._children[0] = null; + root._children[1] = null; + root._childIndex = 0; + root._parent = null; + root._height = 0; + root._proxy = null; + root._next = this._nodePool; + this._nodePool = root; + } + buildTopDownRecursive(leaves,from,until) { + if(until - from == 1) { + let leaf = leaves[from]; + let proxy = leaf._proxy; + leaf._aabbMinX = proxy._aabbMinX; + leaf._aabbMinY = proxy._aabbMinY; + leaf._aabbMinZ = proxy._aabbMinZ; + leaf._aabbMaxX = proxy._aabbMaxX; + leaf._aabbMaxY = proxy._aabbMaxY; + leaf._aabbMaxZ = proxy._aabbMaxZ; + return leaf; + } + let splitAt = this._strategy._splitLeaves(leaves,from,until); + let child1 = this.buildTopDownRecursive(leaves,from,splitAt); + let child2 = this.buildTopDownRecursive(leaves,splitAt,until); + let first = this._nodePool; + if(first != null) { + this._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let parent = first; + parent._children[0] = child1; + child1._parent = parent; + child1._childIndex = 0; + parent._children[1] = child2; + child2._parent = parent; + child2._childIndex = 1; + let c1 = parent._children[0]; + let c2 = parent._children[1]; + parent._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + parent._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + parent._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + parent._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + parent._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + parent._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = parent._children[0]._height; + let h2 = parent._children[1]._height; + parent._height = (h1 > h2 ? h1 : h2) + 1; + return parent; + } + getBalanceRecursive(root) { + if(root == null || root._height == 0) { + return 0; + } + let balance = root._children[0]._height - root._children[1]._height; + if(balance < 0) { + balance = -balance; + } + return balance + this.getBalanceRecursive(root._children[0]) + this.getBalanceRecursive(root._children[1]); + } +} +oimo.collision.geometry.Aabb = class oimo_collision_geometry_Aabb { + constructor() { + this._minX = 0; + this._minY = 0; + this._minZ = 0; + this._maxX = 0; + this._maxY = 0; + this._maxZ = 0; + } + init(min,max) { + this._minX = min.x; + this._minY = min.y; + this._minZ = min.z; + this._maxX = max.x; + this._maxY = max.y; + this._maxZ = max.z; + return this; + } + getMin() { + let min = new oimo.common.Vec3(); + min.x = this._minX; + min.y = this._minY; + min.z = this._minZ; + return min; + } + getMinTo(min) { + min.x = this._minX; + min.y = this._minY; + min.z = this._minZ; + } + setMin(min) { + this._minX = min.x; + this._minY = min.y; + this._minZ = min.z; + return this; + } + getMax() { + let max = new oimo.common.Vec3(); + max.x = this._maxX; + max.y = this._maxY; + max.z = this._maxZ; + return max; + } + getMaxTo(max) { + max.x = this._maxX; + max.y = this._maxY; + max.z = this._maxZ; + } + setMax(max) { + this._maxX = max.x; + this._maxY = max.y; + this._maxZ = max.z; + return this; + } + getCenter() { + let v = new oimo.common.Vec3(); + let cX; + let cY; + let cZ; + cX = this._minX + this._maxX; + cY = this._minY + this._maxY; + cZ = this._minZ + this._maxZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + v.x = cX; + v.y = cY; + v.z = cZ; + return v; + } + getCenterTo(center) { + let cX; + let cY; + let cZ; + cX = this._minX + this._maxX; + cY = this._minY + this._maxY; + cZ = this._minZ + this._maxZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + center.x = cX; + center.y = cY; + center.z = cZ; + } + getExtents() { + let v = new oimo.common.Vec3(); + let cX; + let cY; + let cZ; + cX = this._maxX - this._minX; + cY = this._maxY - this._minY; + cZ = this._maxZ - this._minZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + v.x = cX; + v.y = cY; + v.z = cZ; + return v; + } + getExtentsTo(halfExtents) { + let cX; + let cY; + let cZ; + cX = this._maxX - this._minX; + cY = this._maxY - this._minY; + cZ = this._maxZ - this._minZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + halfExtents.x = cX; + halfExtents.y = cY; + halfExtents.z = cZ; + } + combine(other) { + this._minX = this._minX < other._minX ? this._minX : other._minX; + this._minY = this._minY < other._minY ? this._minY : other._minY; + this._minZ = this._minZ < other._minZ ? this._minZ : other._minZ; + this._maxX = this._maxX > other._maxX ? this._maxX : other._maxX; + this._maxY = this._maxY > other._maxY ? this._maxY : other._maxY; + this._maxZ = this._maxZ > other._maxZ ? this._maxZ : other._maxZ; + return this; + } + combined(other) { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._minX < other._minX ? this._minX : other._minX; + aabb._minY = this._minY < other._minY ? this._minY : other._minY; + aabb._minZ = this._minZ < other._minZ ? this._minZ : other._minZ; + aabb._maxX = this._maxX > other._maxX ? this._maxX : other._maxX; + aabb._maxY = this._maxY > other._maxY ? this._maxY : other._maxY; + aabb._maxZ = this._maxZ > other._maxZ ? this._maxZ : other._maxZ; + return aabb; + } + overlap(other) { + if(this._minX < other._maxX && this._maxX > other._minX && this._minY < other._maxY && this._maxY > other._minY && this._minZ < other._maxZ) { + return this._maxZ > other._minZ; + } else { + return false; + } + } + getIntersection(other) { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._minX > other._minX ? this._minX : other._minX; + aabb._minY = this._minY > other._minY ? this._minY : other._minY; + aabb._minZ = this._minZ > other._minZ ? this._minZ : other._minZ; + aabb._maxX = this._maxX < other._maxX ? this._maxX : other._maxX; + aabb._maxY = this._maxY < other._maxY ? this._maxY : other._maxY; + aabb._maxZ = this._maxZ < other._maxZ ? this._maxZ : other._maxZ; + return aabb; + } + getIntersectionTo(other,intersection) { + intersection._minX = this._minX > other._minX ? this._minX : other._minX; + intersection._minY = this._minY > other._minY ? this._minY : other._minY; + intersection._minZ = this._minZ > other._minZ ? this._minZ : other._minZ; + intersection._maxX = this._maxX < other._maxX ? this._maxX : other._maxX; + intersection._maxY = this._maxY < other._maxY ? this._maxY : other._maxY; + intersection._maxZ = this._maxZ < other._maxZ ? this._maxZ : other._maxZ; + } + copyFrom(aabb) { + this._minX = aabb._minX; + this._minY = aabb._minY; + this._minZ = aabb._minZ; + this._maxX = aabb._maxX; + this._maxY = aabb._maxY; + this._maxZ = aabb._maxZ; + return this; + } + clone() { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._minX; + aabb._minY = this._minY; + aabb._minZ = this._minZ; + aabb._maxX = this._maxX; + aabb._maxY = this._maxY; + aabb._maxZ = this._maxZ; + return aabb; + } +} +oimo.collision.geometry.BoxGeometry = class oimo_collision_geometry_BoxGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(halfExtents) { + super(1); + this._halfExtentsX = halfExtents.x; + this._halfExtentsY = halfExtents.y; + this._halfExtentsZ = halfExtents.z; + this._halfAxisXX = halfExtents.x; + this._halfAxisXY = 0; + this._halfAxisXZ = 0; + this._halfAxisYX = 0; + this._halfAxisYY = halfExtents.y; + this._halfAxisYZ = 0; + this._halfAxisZX = 0; + this._halfAxisZY = 0; + this._halfAxisZZ = halfExtents.z; + this._updateMass(); + let minHalfExtents = halfExtents.x < halfExtents.y ? halfExtents.z < halfExtents.x ? halfExtents.z : halfExtents.x : halfExtents.z < halfExtents.y ? halfExtents.z : halfExtents.y; + if(this._gjkMargin > minHalfExtents * 0.2) { + this._gjkMargin = minHalfExtents * 0.2; + } + } + getHalfExtents() { + let v = new oimo.common.Vec3(); + v.x = this._halfExtentsX; + v.y = this._halfExtentsY; + v.z = this._halfExtentsZ; + return v; + } + getHalfExtentsTo(halfExtents) { + halfExtents.x = this._halfExtentsX; + halfExtents.y = this._halfExtentsY; + halfExtents.z = this._halfExtentsZ; + } + _updateMass() { + this._volume = 8 * (this._halfExtentsX * this._halfExtentsY * this._halfExtentsZ); + let sqX; + let sqY; + let sqZ; + sqX = this._halfExtentsX * this._halfExtentsX; + sqY = this._halfExtentsY * this._halfExtentsY; + sqZ = this._halfExtentsZ * this._halfExtentsZ; + this._inertiaCoeff00 = 0.33333333333333331 * (sqY + sqZ); + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.33333333333333331 * (sqZ + sqX); + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.33333333333333331 * (sqX + sqY); + } + _computeAabb(aabb,tf) { + let tfxX; + let tfxY; + let tfxZ; + let tfyX; + let tfyY; + let tfyZ; + let tfzX; + let tfzY; + let tfzZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * this._halfAxisXX + tf._rotation01 * this._halfAxisXY + tf._rotation02 * this._halfAxisXZ; + __tmp__Y = tf._rotation10 * this._halfAxisXX + tf._rotation11 * this._halfAxisXY + tf._rotation12 * this._halfAxisXZ; + __tmp__Z = tf._rotation20 * this._halfAxisXX + tf._rotation21 * this._halfAxisXY + tf._rotation22 * this._halfAxisXZ; + tfxX = __tmp__X; + tfxY = __tmp__Y; + tfxZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf._rotation00 * this._halfAxisYX + tf._rotation01 * this._halfAxisYY + tf._rotation02 * this._halfAxisYZ; + __tmp__Y1 = tf._rotation10 * this._halfAxisYX + tf._rotation11 * this._halfAxisYY + tf._rotation12 * this._halfAxisYZ; + __tmp__Z1 = tf._rotation20 * this._halfAxisYX + tf._rotation21 * this._halfAxisYY + tf._rotation22 * this._halfAxisYZ; + tfyX = __tmp__X1; + tfyY = __tmp__Y1; + tfyZ = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf._rotation00 * this._halfAxisZX + tf._rotation01 * this._halfAxisZY + tf._rotation02 * this._halfAxisZZ; + __tmp__Y2 = tf._rotation10 * this._halfAxisZX + tf._rotation11 * this._halfAxisZY + tf._rotation12 * this._halfAxisZZ; + __tmp__Z2 = tf._rotation20 * this._halfAxisZX + tf._rotation21 * this._halfAxisZY + tf._rotation22 * this._halfAxisZZ; + tfzX = __tmp__X2; + tfzY = __tmp__Y2; + tfzZ = __tmp__Z2; + if(tfxX < 0) { + tfxX = -tfxX; + } + if(tfxY < 0) { + tfxY = -tfxY; + } + if(tfxZ < 0) { + tfxZ = -tfxZ; + } + if(tfyX < 0) { + tfyX = -tfyX; + } + if(tfyY < 0) { + tfyY = -tfyY; + } + if(tfyZ < 0) { + tfyZ = -tfyZ; + } + if(tfzX < 0) { + tfzX = -tfzX; + } + if(tfzY < 0) { + tfzY = -tfzY; + } + if(tfzZ < 0) { + tfzZ = -tfzZ; + } + let tfsX; + let tfsY; + let tfsZ; + tfsX = tfxX + tfyX; + tfsY = tfxY + tfyY; + tfsZ = tfxZ + tfyZ; + tfsX += tfzX; + tfsY += tfzY; + tfsZ += tfzZ; + aabb._minX = tf._positionX - tfsX; + aabb._minY = tf._positionY - tfsY; + aabb._minZ = tf._positionZ - tfsZ; + aabb._maxX = tf._positionX + tfsX; + aabb._maxY = tf._positionY + tfsY; + aabb._maxZ = tf._positionZ + tfsZ; + } + computeLocalSupportingVertex(dir,out) { + let gjkMarginsX; + let gjkMarginsY; + let gjkMarginsZ; + let coreExtentsX; + let coreExtentsY; + let coreExtentsZ; + gjkMarginsX = this._gjkMargin; + gjkMarginsY = this._gjkMargin; + gjkMarginsZ = this._gjkMargin; + if(!(gjkMarginsX < this._halfExtentsX)) { + gjkMarginsX = this._halfExtentsX; + } + if(!(gjkMarginsY < this._halfExtentsY)) { + gjkMarginsY = this._halfExtentsY; + } + if(!(gjkMarginsZ < this._halfExtentsZ)) { + gjkMarginsZ = this._halfExtentsZ; + } + coreExtentsX = this._halfExtentsX - gjkMarginsX; + coreExtentsY = this._halfExtentsY - gjkMarginsY; + coreExtentsZ = this._halfExtentsZ - gjkMarginsZ; + out.x = dir.x > 0 ? coreExtentsX : -coreExtentsX; + out.y = dir.y > 0 ? coreExtentsY : -coreExtentsY; + out.z = dir.z > 0 ? coreExtentsZ : -coreExtentsZ; + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let halfW = this._halfExtentsX; + let halfH = this._halfExtentsY; + let halfD = this._halfExtentsZ; + let dx = endX - beginX; + let dy = endY - beginY; + let dz = endZ - beginZ; + let tminx = 0; + let tminy = 0; + let tminz = 0; + let tmaxx = 1; + let tmaxy = 1; + let tmaxz = 1; + if(dx > -1e-6 && dx < 1e-6) { + if(beginX <= -halfW || beginX >= halfW) { + return false; + } + } else { + let invDx = 1 / dx; + let t1 = (-halfW - beginX) * invDx; + let t2 = (halfW - beginX) * invDx; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminx = t1; + } + if(t2 < 1) { + tmaxx = t2; + } + } + if(dy > -1e-6 && dy < 1e-6) { + if(beginY <= -halfH || beginY >= halfH) { + return false; + } + } else { + let invDy = 1 / dy; + let t1 = (-halfH - beginY) * invDy; + let t2 = (halfH - beginY) * invDy; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminy = t1; + } + if(t2 < 1) { + tmaxy = t2; + } + } + if(dz > -1e-6 && dz < 1e-6) { + if(beginZ <= -halfD || beginZ >= halfD) { + return false; + } + } else { + let invDz = 1 / dz; + let t1 = (-halfD - beginZ) * invDz; + let t2 = (halfD - beginZ) * invDz; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminz = t1; + } + if(t2 < 1) { + tmaxz = t2; + } + } + if(tminx >= 1 || tminy >= 1 || tminz >= 1 || tmaxx <= 0 || tmaxy <= 0 || tmaxz <= 0) { + return false; + } + let min = tminx; + let max = tmaxx; + let hitDirection = 0; + if(tminy > min) { + min = tminy; + hitDirection = 1; + } + if(tminz > min) { + min = tminz; + hitDirection = 2; + } + if(tmaxy < max) { + max = tmaxy; + } + if(tmaxz < max) { + max = tmaxz; + } + if(min > max) { + return false; + } + if(min == 0) { + return false; + } + switch(hitDirection) { + case 0: + hit.normal.init(dx > 0 ? -1 : 1,0,0); + break; + case 1: + hit.normal.init(0,dy > 0 ? -1 : 1,0); + break; + case 2: + hit.normal.init(0,0,dz > 0 ? -1 : 1); + break; + } + hit.position.init(beginX + min * dx,beginY + min * dy,beginZ + min * dz); + hit.fraction = min; + return true; + } +} +oimo.collision.geometry.CapsuleGeometry = class oimo_collision_geometry_CapsuleGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius,halfHeight) { + super(4); + this._radius = radius; + this._halfHeight = halfHeight; + this._gjkMargin = this._radius; + this._updateMass(); + } + getRadius() { + return this._radius; + } + getHalfHeight() { + return this._halfHeight; + } + _updateMass() { + let r2 = this._radius * this._radius; + let hh2 = this._halfHeight * this._halfHeight; + let cylinderVolume = 6.28318530717958 * r2 * this._halfHeight; + let sphereVolume = 3.14159265358979 * r2 * this._radius * 4 / 3; + this._volume = cylinderVolume + sphereVolume; + let invVolume = this._volume == 0 ? 0 : 1 / this._volume; + let inertiaXZ = invVolume * (cylinderVolume * (r2 * 0.25 + hh2 / 3) + sphereVolume * (r2 * 0.4 + this._halfHeight * this._radius * 0.75 + hh2)); + this._inertiaCoeff00 = inertiaXZ; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = invVolume * (cylinderVolume * r2 * 0.5 + sphereVolume * r2 * 0.4); + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = inertiaXZ; + } + _computeAabb(aabb,tf) { + let radVecX; + let radVecY; + let radVecZ; + radVecX = this._radius; + radVecY = this._radius; + radVecZ = this._radius; + let axisX; + let axisY; + let axisZ; + axisX = tf._rotation01; + axisY = tf._rotation11; + axisZ = tf._rotation21; + if(axisX < 0) { + axisX = -axisX; + } + if(axisY < 0) { + axisY = -axisY; + } + if(axisZ < 0) { + axisZ = -axisZ; + } + axisX *= this._halfHeight; + axisY *= this._halfHeight; + axisZ *= this._halfHeight; + radVecX += axisX; + radVecY += axisY; + radVecZ += axisZ; + aabb._minX = tf._positionX - radVecX; + aabb._minY = tf._positionY - radVecY; + aabb._minZ = tf._positionZ - radVecZ; + aabb._maxX = tf._positionX + radVecX; + aabb._maxY = tf._positionY + radVecY; + aabb._maxZ = tf._positionZ + radVecZ; + } + computeLocalSupportingVertex(dir,out) { + if(dir.y > 0) { + out.init(0,this._halfHeight,0); + } else { + out.init(0,-this._halfHeight,0); + } + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let halfH = this._halfHeight; + let dx = endX - beginX; + let dz = endZ - beginZ; + let tminxz = 0; + let tmaxxz; + let a = dx * dx + dz * dz; + let b = beginX * dx + beginZ * dz; + let c = beginX * beginX + beginZ * beginZ - this._radius * this._radius; + let D = b * b - a * c; + if(D < 0) { + return false; + } + if(a > 0) { + let sqrtD = Math.sqrt(D); + tminxz = (-b - sqrtD) / a; + tmaxxz = (-b + sqrtD) / a; + if(tminxz >= 1 || tmaxxz <= 0) { + return false; + } + } else { + if(c >= 0) { + return false; + } + tminxz = 0; + } + let crossY = beginY + (endY - beginY) * tminxz; + let min; + if(crossY > -halfH && crossY < halfH) { + if(tminxz > 0) { + min = tminxz; + let _this = hit.normal.init(beginX + dx * min,0,beginZ + dz * min); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + hit.position.init(beginX + min * dx,crossY,beginZ + min * dz); + hit.fraction = min; + return true; + } + return false; + } + let spherePosX; + let spherePosY; + let spherePosZ; + let sphereToBeginX; + let sphereToBeginY; + let sphereToBeginZ; + spherePosX = 0; + spherePosY = crossY < 0 ? -halfH : halfH; + spherePosZ = 0; + sphereToBeginX = beginX - spherePosX; + sphereToBeginY = beginY - spherePosY; + sphereToBeginZ = beginZ - spherePosZ; + let dX; + let dY; + let dZ; + dX = endX - beginX; + dY = endY - beginY; + dZ = endZ - beginZ; + a = dX * dX + dY * dY + dZ * dZ; + b = sphereToBeginX * dX + sphereToBeginY * dY + sphereToBeginZ * dZ; + c = sphereToBeginX * sphereToBeginX + sphereToBeginY * sphereToBeginY + sphereToBeginZ * sphereToBeginZ - this._radius * this._radius; + D = b * b - a * c; + if(D < 0) { + return false; + } + let t = (-b - Math.sqrt(D)) / a; + if(t < 0 || t > 1) { + return false; + } + let hitPosX; + let hitPosY; + let hitPosZ; + let hitNormalX; + let hitNormalY; + let hitNormalZ; + hitPosX = sphereToBeginX + dX * t; + hitPosY = sphereToBeginY + dY * t; + hitPosZ = sphereToBeginZ + dZ * t; + let l = hitPosX * hitPosX + hitPosY * hitPosY + hitPosZ * hitPosZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + hitNormalX = hitPosX * l; + hitNormalY = hitPosY * l; + hitNormalZ = hitPosZ * l; + hitPosX += spherePosX; + hitPosY += spherePosY; + hitPosZ += spherePosZ; + let v = hit.position; + v.x = hitPosX; + v.y = hitPosY; + v.z = hitPosZ; + let v1 = hit.normal; + v1.x = hitNormalX; + v1.y = hitNormalY; + v1.z = hitNormalZ; + hit.fraction = t; + return true; + } +} +oimo.collision.geometry.ConeGeometry = class oimo_collision_geometry_ConeGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius,halfHeight) { + super(3); + this._radius = radius; + this._halfHeight = halfHeight; + this.sinTheta = radius / Math.sqrt(radius * radius + 4 * halfHeight * halfHeight); + this.cosTheta = 2 * halfHeight / Math.sqrt(radius * radius + 4 * halfHeight * halfHeight); + this._updateMass(); + } + getRadius() { + return this._radius; + } + getHalfHeight() { + return this._halfHeight; + } + _updateMass() { + let r2 = this._radius * this._radius; + let h2 = this._halfHeight * this._halfHeight * 4; + this._volume = 3.14159265358979 * r2 * this._halfHeight * 2 / 3; + this._inertiaCoeff00 = 0.05 * (3 * r2 + 2 * h2); + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.3 * r2; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.05 * (3 * r2 + 2 * h2); + } + _computeAabb(aabb,tf) { + let axisX; + let axisY; + let axisZ; + let axis2X; + let axis2Y; + let axis2Z; + let ehX; + let ehY; + let ehZ; + let erX; + let erY; + let erZ; + axisX = tf._rotation01; + axisY = tf._rotation11; + axisZ = tf._rotation21; + axis2X = axisX * axisX; + axis2Y = axisY * axisY; + axis2Z = axisZ * axisZ; + erX = Math.sqrt(1 - axis2X); + erY = Math.sqrt(1 - axis2Y); + erZ = Math.sqrt(1 - axis2Z); + erX *= this._radius; + erY *= this._radius; + erZ *= this._radius; + ehX = axisX * this._halfHeight; + ehY = axisY * this._halfHeight; + ehZ = axisZ * this._halfHeight; + let rminX; + let rminY; + let rminZ; + let rmaxX; + let rmaxY; + let rmaxZ; + rminX = -ehX; + rminY = -ehY; + rminZ = -ehZ; + rminX -= erX; + rminY -= erY; + rminZ -= erZ; + rmaxX = -ehX; + rmaxY = -ehY; + rmaxZ = -ehZ; + rmaxX += erX; + rmaxY += erY; + rmaxZ += erZ; + let maxX; + let maxY; + let maxZ; + let minX; + let minY; + let minZ; + maxX = rminX > rmaxX ? rminX : rmaxX; + maxY = rminY > rmaxY ? rminY : rmaxY; + maxZ = rminZ > rmaxZ ? rminZ : rmaxZ; + if(!(maxX > ehX)) { + maxX = ehX; + } + if(!(maxY > ehY)) { + maxY = ehY; + } + if(!(maxZ > ehZ)) { + maxZ = ehZ; + } + minX = rminX < rmaxX ? rminX : rmaxX; + minY = rminY < rmaxY ? rminY : rmaxY; + minZ = rminZ < rmaxZ ? rminZ : rmaxZ; + if(!(minX < ehX)) { + minX = ehX; + } + if(!(minY < ehY)) { + minY = ehY; + } + if(!(minZ < ehZ)) { + minZ = ehZ; + } + aabb._minX = tf._positionX + minX; + aabb._minY = tf._positionY + minY; + aabb._minZ = tf._positionZ + minZ; + aabb._maxX = tf._positionX + maxX; + aabb._maxY = tf._positionY + maxY; + aabb._maxZ = tf._positionZ + maxZ; + } + computeLocalSupportingVertex(dir,out) { + let dx = dir.x; + let dy = dir.y; + let dz = dir.z; + if(dy > 0 && dy * dy > this.sinTheta * this.sinTheta * (dx * dx + dy * dy + dz * dz)) { + out.init(0,this._halfHeight - this._gjkMargin / this.sinTheta,0); + if(out.y < 0) { + out.y = 0; + } + return; + } + let rx = dir.x; + let rz = dir.z; + let len = rx * rx + rz * rz; + let height = 2 * this._halfHeight; + let coreRadius = (height - this._gjkMargin) / height * this._radius - this._gjkMargin / this.cosTheta; + if(coreRadius < 0) { + coreRadius = 0; + } + let invLen = len > 0 ? coreRadius / Math.sqrt(len) : 0; + let coreHalfHeight = this._halfHeight - this._gjkMargin; + if(coreHalfHeight < 0) { + coreHalfHeight = 0; + } + out.x = rx * invLen; + out.y = -coreHalfHeight; + out.z = rz * invLen; + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let p1y; + let halfH = this._halfHeight; + let dx = endX - beginX; + let dy = endY - beginY; + let dz = endZ - beginZ; + let tminy = 0; + let tmaxy = 1; + if(dy > -1e-6 && dy < 1e-6) { + if(beginY <= -halfH || beginY >= halfH) { + return false; + } + } else { + let invDy = 1 / dy; + let t1 = (-halfH - beginY) * invDy; + let t2 = (halfH - beginY) * invDy; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminy = t1; + } + if(t2 < 1) { + tmaxy = t2; + } + } + if(tminy >= 1 || tmaxy <= 0) { + return false; + } + let tminxz = 0; + let tmaxxz = 0; + p1y = beginY - halfH; + let cos2 = this.cosTheta * this.cosTheta; + let a = cos2 * (dx * dx + dy * dy + dz * dz) - dy * dy; + let b = cos2 * (beginX * dx + p1y * dy + beginZ * dz) - p1y * dy; + let c = cos2 * (beginX * beginX + p1y * p1y + beginZ * beginZ) - p1y * p1y; + let D = b * b - a * c; + if(a != 0) { + if(D < 0) { + return false; + } + let sqrtD = Math.sqrt(D); + if(a < 0) { + if(dy > 0) { + tminxz = 0; + tmaxxz = (-b + sqrtD) / a; + if(tmaxxz <= 0) { + return false; + } + } else { + tminxz = (-b - sqrtD) / a; + tmaxxz = 1; + if(tminxz >= 1) { + return false; + } + } + } else { + tminxz = (-b - sqrtD) / a; + tmaxxz = (-b + sqrtD) / a; + if(tminxz >= 1 || tmaxxz <= 0) { + return false; + } + } + } else { + let t = -c / (2 * b); + if(b > 0) { + tminxz = 0; + tmaxxz = t; + if(t <= 0) { + return false; + } + } else { + tminxz = t; + tmaxxz = 1; + if(t >= 1) { + return false; + } + } + } + p1y += halfH; + let min; + if(tmaxxz <= tminy || tmaxy <= tminxz) { + return false; + } + if(tminxz < tminy) { + min = tminy; + if(min == 0) { + return false; + } + hit.normal.init(0,dy > 0 ? -1 : 1,0); + } else { + min = tminxz; + if(min == 0) { + return false; + } + let _this = hit.normal.init(beginX + dx * min,0,beginZ + dz * min); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let s = this.cosTheta; + _this.x *= s; + _this.y *= s; + _this.z *= s; + hit.normal.y += this.sinTheta; + } + hit.position.init(beginX + min * dx,p1y + min * dy,beginZ + min * dz); + hit.fraction = min; + return true; + } +} +oimo.collision.geometry.ConvexHullGeometry = class oimo_collision_geometry_ConvexHullGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(vertices) { + super(5); + this._numVertices = vertices.length; + this._vertices = new Array(this._numVertices); + this._tmpVertices = new Array(this._numVertices); + let _g = 0; + let _g1 = this._numVertices; + while(_g < _g1) { + let i = _g++; + this._vertices[i] = vertices[i]; + this._tmpVertices[i] = new oimo.common.Vec3(); + } + this._useGjkRayCast = true; + this._updateMass(); + } + getVertices() { + return this._vertices; + } + _updateMass() { + this._volume = 1; + this._inertiaCoeff00 = 1; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 1; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 1; + let minx = this._vertices[0].x; + let miny = this._vertices[0].y; + let minz = this._vertices[0].z; + let maxx = this._vertices[0].x; + let maxy = this._vertices[0].y; + let maxz = this._vertices[0].z; + let _g = 1; + let _g1 = this._numVertices; + while(_g < _g1) { + let i = _g++; + let vx = this._vertices[i].x; + let vy = this._vertices[i].y; + let vz = this._vertices[i].z; + if(vx < minx) { + minx = vx; + } else if(vx > maxx) { + maxx = vx; + } + if(vy < miny) { + miny = vy; + } else if(vy > maxy) { + maxy = vy; + } + if(vz < minz) { + minz = vz; + } else if(vz > maxz) { + maxz = vz; + } + } + let sizex = maxx - minx; + let sizey = maxy - miny; + let sizez = maxz - minz; + this._volume = sizex * sizey * sizez; + let diffCog = ((minx + maxx) * (minx + maxx) + (miny + maxy) * (miny + maxy) + (minz + maxz) * (minz + maxz)) * 0.25; + sizex = sizex * sizex * 0.25; + sizey = sizey * sizey * 0.25; + sizez = sizez * sizez * 0.25; + this._inertiaCoeff00 = 0.33333333333333331 * (sizey + sizez) + diffCog; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.33333333333333331 * (sizez + sizex) + diffCog; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.33333333333333331 * (sizex + sizey) + diffCog; + } + _computeAabb(aabb,tf) { + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + let marginX; + let marginY; + let marginZ; + marginX = this._gjkMargin; + marginY = this._gjkMargin; + marginZ = this._gjkMargin; + let localVX; + let localVY; + let localVZ; + let v = this._vertices[0]; + localVX = v.x; + localVY = v.y; + localVZ = v.z; + let worldVX; + let worldVY; + let worldVZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * localVX + tf._rotation01 * localVY + tf._rotation02 * localVZ; + __tmp__Y = tf._rotation10 * localVX + tf._rotation11 * localVY + tf._rotation12 * localVZ; + __tmp__Z = tf._rotation20 * localVX + tf._rotation21 * localVY + tf._rotation22 * localVZ; + worldVX = __tmp__X; + worldVY = __tmp__Y; + worldVZ = __tmp__Z; + worldVX += tf._positionX; + worldVY += tf._positionY; + worldVZ += tf._positionZ; + minX = worldVX; + minY = worldVY; + minZ = worldVZ; + maxX = worldVX; + maxY = worldVY; + maxZ = worldVZ; + let _g = 1; + let _g1 = this._numVertices; + while(_g < _g1) { + let v = this._vertices[_g++]; + localVX = v.x; + localVY = v.y; + localVZ = v.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * localVX + tf._rotation01 * localVY + tf._rotation02 * localVZ; + __tmp__Y = tf._rotation10 * localVX + tf._rotation11 * localVY + tf._rotation12 * localVZ; + __tmp__Z = tf._rotation20 * localVX + tf._rotation21 * localVY + tf._rotation22 * localVZ; + worldVX = __tmp__X; + worldVY = __tmp__Y; + worldVZ = __tmp__Z; + worldVX += tf._positionX; + worldVY += tf._positionY; + worldVZ += tf._positionZ; + if(!(minX < worldVX)) { + minX = worldVX; + } + if(!(minY < worldVY)) { + minY = worldVY; + } + if(!(minZ < worldVZ)) { + minZ = worldVZ; + } + if(!(maxX > worldVX)) { + maxX = worldVX; + } + if(!(maxY > worldVY)) { + maxY = worldVY; + } + if(!(maxZ > worldVZ)) { + maxZ = worldVZ; + } + } + aabb._minX = minX - marginX; + aabb._minY = minY - marginY; + aabb._minZ = minZ - marginZ; + aabb._maxX = maxX + marginX; + aabb._maxY = maxY + marginY; + aabb._maxZ = maxZ + marginZ; + } + computeLocalSupportingVertex(dir,out) { + let _this = this._vertices[0]; + let maxDot = _this.x * dir.x + _this.y * dir.y + _this.z * dir.z; + let maxIndex = 0; + let _g = 1; + let _g1 = this._numVertices; + while(_g < _g1) { + let i = _g++; + let _this = this._vertices[i]; + let dot = _this.x * dir.x + _this.y * dir.y + _this.z * dir.z; + if(dot > maxDot) { + maxDot = dot; + maxIndex = i; + } + } + let v = this._vertices[maxIndex]; + out.x = v.x; + out.y = v.y; + out.z = v.z; + } +} +oimo.collision.geometry.CylinderGeometry = class oimo_collision_geometry_CylinderGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius,halfHeight) { + super(2); + this._radius = radius; + this._halfHeight = halfHeight; + this._updateMass(); + } + getRadius() { + return this._radius; + } + getHalfHeight() { + return this._halfHeight; + } + _updateMass() { + let r2 = this._radius * this._radius; + let h2 = this._halfHeight * this._halfHeight * 4; + this._volume = 3.14159265358979 * r2 * this._halfHeight * 2; + this._inertiaCoeff00 = 0.083333333333333329 * (3 * r2 + h2); + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.5 * r2; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.083333333333333329 * (3 * r2 + h2); + } + _computeAabb(aabb,tf) { + let axisX; + let axisY; + let axisZ; + let axis2X; + let axis2Y; + let axis2Z; + let ehX; + let ehY; + let ehZ; + let erX; + let erY; + let erZ; + axisX = tf._rotation01; + axisY = tf._rotation11; + axisZ = tf._rotation21; + if(axisX < 0) { + axisX = -axisX; + } + if(axisY < 0) { + axisY = -axisY; + } + if(axisZ < 0) { + axisZ = -axisZ; + } + axis2X = axisX * axisX; + axis2Y = axisY * axisY; + axis2Z = axisZ * axisZ; + erX = Math.sqrt(1 - axis2X); + erY = Math.sqrt(1 - axis2Y); + erZ = Math.sqrt(1 - axis2Z); + erX *= this._radius; + erY *= this._radius; + erZ *= this._radius; + ehX = axisX * this._halfHeight; + ehY = axisY * this._halfHeight; + ehZ = axisZ * this._halfHeight; + let maxX; + let maxY; + let maxZ; + maxX = erX + ehX; + maxY = erY + ehY; + maxZ = erZ + ehZ; + aabb._minX = tf._positionX - maxX; + aabb._minY = tf._positionY - maxY; + aabb._minZ = tf._positionZ - maxZ; + aabb._maxX = tf._positionX + maxX; + aabb._maxY = tf._positionY + maxY; + aabb._maxZ = tf._positionZ + maxZ; + } + computeLocalSupportingVertex(dir,out) { + let rx = dir.x; + let rz = dir.z; + let len = rx * rx + rz * rz; + let coreRadius = this._radius - this._gjkMargin; + if(coreRadius < 0) { + coreRadius = 0; + } + let invLen = len > 0 ? coreRadius / Math.sqrt(len) : 0; + let coreHeight = this._halfHeight - this._gjkMargin; + if(coreHeight < 0) { + coreHeight = 0; + } + out.x = rx * invLen; + out.y = dir.y > 0 ? coreHeight : -coreHeight; + out.z = rz * invLen; + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let halfH = this._halfHeight; + let dx = endX - beginX; + let dy = endY - beginY; + let dz = endZ - beginZ; + let tminy = 0; + let tmaxy = 1; + if(dy > -1e-6 && dy < 1e-6) { + if(beginY <= -halfH || beginY >= halfH) { + return false; + } + } else { + let invDy = 1 / dy; + let t1 = (-halfH - beginY) * invDy; + let t2 = (halfH - beginY) * invDy; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminy = t1; + } + if(t2 < 1) { + tmaxy = t2; + } + } + if(tminy >= 1 || tmaxy <= 0) { + return false; + } + let tminxz = 0; + let tmaxxz; + let a = dx * dx + dz * dz; + let b = beginX * dx + beginZ * dz; + let c = beginX * beginX + beginZ * beginZ - this._radius * this._radius; + let D = b * b - a * c; + if(D < 0) { + return false; + } + if(a > 0) { + let sqrtD = Math.sqrt(D); + tminxz = (-b - sqrtD) / a; + tmaxxz = (-b + sqrtD) / a; + if(tminxz >= 1 || tmaxxz <= 0) { + return false; + } + } else { + if(c >= 0) { + return false; + } + tminxz = 0; + tmaxxz = 1; + } + let min; + if(tmaxxz <= tminy || tmaxy <= tminxz) { + return false; + } + if(tminxz < tminy) { + min = tminy; + if(min == 0) { + return false; + } + hit.normal.init(0,dy > 0 ? -1 : 1,0); + } else { + min = tminxz; + if(min == 0) { + return false; + } + let _this = hit.normal.init(beginX + dx * min,0,beginZ + dz * min); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + } + hit.position.init(beginX + min * dx,beginY + min * dy,beginZ + min * dz); + hit.fraction = min; + return true; + } +} +oimo.collision.geometry.GeometryType = class oimo_collision_geometry_GeometryType { +} +oimo.collision.geometry.RayCastHit = class oimo_collision_geometry_RayCastHit { + constructor() { + this.position = new oimo.common.Vec3(); + this.normal = new oimo.common.Vec3(); + this.fraction = 0; + } +} +oimo.collision.geometry.SphereGeometry = class oimo_collision_geometry_SphereGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius) { + super(0); + this._radius = radius; + this._gjkMargin = this._radius; + this._updateMass(); + } + getRadius() { + return this._radius; + } + _updateMass() { + this._volume = 4.1887902047863861 * this._radius * this._radius * this._radius; + this._inertiaCoeff00 = 0.4 * this._radius * this._radius; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.4 * this._radius * this._radius; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.4 * this._radius * this._radius; + } + _computeAabb(aabb,tf) { + let radVecX; + let radVecY; + let radVecZ; + radVecX = this._radius; + radVecY = this._radius; + radVecZ = this._radius; + aabb._minX = tf._positionX - radVecX; + aabb._minY = tf._positionY - radVecY; + aabb._minZ = tf._positionZ - radVecZ; + aabb._maxX = tf._positionX + radVecX; + aabb._maxY = tf._positionY + radVecY; + aabb._maxZ = tf._positionZ + radVecZ; + } + computeLocalSupportingVertex(dir,out) { + out.zero(); + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let dX; + let dY; + let dZ; + dX = endX - beginX; + dY = endY - beginY; + dZ = endZ - beginZ; + let a = dX * dX + dY * dY + dZ * dZ; + let b = beginX * dX + beginY * dY + beginZ * dZ; + let D = b * b - a * (beginX * beginX + beginY * beginY + beginZ * beginZ - this._radius * this._radius); + if(D < 0) { + return false; + } + let t = (-b - Math.sqrt(D)) / a; + if(t < 0 || t > 1) { + return false; + } + let hitPosX; + let hitPosY; + let hitPosZ; + let hitNormalX; + let hitNormalY; + let hitNormalZ; + hitPosX = beginX + dX * t; + hitPosY = beginY + dY * t; + hitPosZ = beginZ + dZ * t; + let l = hitPosX * hitPosX + hitPosY * hitPosY + hitPosZ * hitPosZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + hitNormalX = hitPosX * l; + hitNormalY = hitPosY * l; + hitNormalZ = hitPosZ * l; + let v = hit.position; + v.x = hitPosX; + v.y = hitPosY; + v.z = hitPosZ; + let v1 = hit.normal; + v1.x = hitNormalX; + v1.y = hitNormalY; + v1.z = hitNormalZ; + hit.fraction = t; + return true; + } +} +if(!oimo.collision.narrowphase) oimo.collision.narrowphase = {}; +oimo.collision.narrowphase.CollisionMatrix = class oimo_collision_narrowphase_CollisionMatrix { + constructor() { + this.detectors = new Array(8); + this.detectors[0] = new Array(8); + this.detectors[1] = new Array(8); + this.detectors[2] = new Array(8); + this.detectors[3] = new Array(8); + this.detectors[4] = new Array(8); + this.detectors[5] = new Array(8); + let gjkEpaDetector = new oimo.collision.narrowphase.detector.GjkEpaDetector(); + this.detectors[0][0] = new oimo.collision.narrowphase.detector.SphereSphereDetector(); + this.detectors[0][1] = new oimo.collision.narrowphase.detector.SphereBoxDetector(false); + this.detectors[0][2] = gjkEpaDetector; + this.detectors[0][3] = gjkEpaDetector; + this.detectors[0][4] = new oimo.collision.narrowphase.detector.SphereCapsuleDetector(false); + this.detectors[0][5] = gjkEpaDetector; + this.detectors[1][0] = new oimo.collision.narrowphase.detector.SphereBoxDetector(true); + this.detectors[1][1] = new oimo.collision.narrowphase.detector.BoxBoxDetector(); + this.detectors[1][2] = gjkEpaDetector; + this.detectors[1][3] = gjkEpaDetector; + this.detectors[1][4] = gjkEpaDetector; + this.detectors[1][5] = gjkEpaDetector; + this.detectors[2][0] = gjkEpaDetector; + this.detectors[2][1] = gjkEpaDetector; + this.detectors[2][2] = gjkEpaDetector; + this.detectors[2][3] = gjkEpaDetector; + this.detectors[2][4] = gjkEpaDetector; + this.detectors[2][5] = gjkEpaDetector; + this.detectors[3][0] = gjkEpaDetector; + this.detectors[3][1] = gjkEpaDetector; + this.detectors[3][2] = gjkEpaDetector; + this.detectors[3][3] = gjkEpaDetector; + this.detectors[3][4] = gjkEpaDetector; + this.detectors[3][5] = gjkEpaDetector; + this.detectors[4][0] = new oimo.collision.narrowphase.detector.SphereCapsuleDetector(true); + this.detectors[4][1] = gjkEpaDetector; + this.detectors[4][2] = gjkEpaDetector; + this.detectors[4][3] = gjkEpaDetector; + this.detectors[4][4] = new oimo.collision.narrowphase.detector.CapsuleCapsuleDetector(); + this.detectors[4][5] = gjkEpaDetector; + this.detectors[5][0] = gjkEpaDetector; + this.detectors[5][1] = gjkEpaDetector; + this.detectors[5][2] = gjkEpaDetector; + this.detectors[5][3] = gjkEpaDetector; + this.detectors[5][4] = gjkEpaDetector; + this.detectors[5][5] = gjkEpaDetector; + } + getDetector(geomType1,geomType2) { + return this.detectors[geomType1][geomType2]; + } +} +oimo.collision.narrowphase.DetectorResult = class oimo_collision_narrowphase_DetectorResult { + constructor() { + this.numPoints = 0; + this.normal = new oimo.common.Vec3(); + this.points = new Array(oimo.common.Setting.maxManifoldPoints); + this.incremental = false; + let _g = 0; + let _g1 = oimo.common.Setting.maxManifoldPoints; + while(_g < _g1) this.points[_g++] = new oimo.collision.narrowphase.DetectorResultPoint(); + } + getMaxDepth() { + let max = 0; + let _g = 0; + let _g1 = this.numPoints; + while(_g < _g1) { + let i = _g++; + if(this.points[i].depth > max) { + max = this.points[i].depth; + } + } + return max; + } + clear() { + this.numPoints = 0; + let _g = 0; + let _g1 = this.points; + while(_g < _g1.length) { + let p = _g1[_g]; + ++_g; + p.position1.zero(); + p.position2.zero(); + p.depth = 0; + p.id = 0; + } + this.normal.zero(); + } +} +oimo.collision.narrowphase.DetectorResultPoint = class oimo_collision_narrowphase_DetectorResultPoint { + constructor() { + this.position1 = new oimo.common.Vec3(); + this.position2 = new oimo.common.Vec3(); + this.depth = 0; + this.id = 0; + } +} +if(!oimo.collision.narrowphase.detector) oimo.collision.narrowphase.detector = {}; +oimo.collision.narrowphase.detector.Detector = class oimo_collision_narrowphase_detector_Detector { + constructor(swapped) { + this.swapped = swapped; + } + setNormal(result,nX,nY,nZ) { + let v = result.normal; + v.x = nX; + v.y = nY; + v.z = nZ; + if(this.swapped) { + let _this = result.normal; + _this.x = -_this.x; + _this.y = -_this.y; + _this.z = -_this.z; + } + } + addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,depth,id) { + let p = result.points[result.numPoints++]; + p.depth = depth; + p.id = id; + if(this.swapped) { + let v = p.position1; + v.x = pos2X; + v.y = pos2Y; + v.z = pos2Z; + let v1 = p.position2; + v1.x = pos1X; + v1.y = pos1Y; + v1.z = pos1Z; + } else { + let v = p.position1; + v.x = pos1X; + v.y = pos1Y; + v.z = pos1Z; + let v1 = p.position2; + v1.x = pos2X; + v1.y = pos2Y; + v1.z = pos2Z; + } + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + } + detect(result,geom1,geom2,transform1,transform2,cachedData) { + result.numPoints = 0; + let _g = 0; + let _g1 = result.points; + while(_g < _g1.length) { + let p = _g1[_g]; + ++_g; + p.position1.zero(); + p.position2.zero(); + p.depth = 0; + p.id = 0; + } + result.normal.zero(); + if(this.swapped) { + this.detectImpl(result,geom2,geom1,transform2,transform1,cachedData); + } else { + this.detectImpl(result,geom1,geom2,transform1,transform2,cachedData); + } + } +} +oimo.collision.narrowphase.detector.BoxBoxDetector = class oimo_collision_narrowphase_detector_BoxBoxDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + this.clipper = new oimo.collision.narrowphase.detector._BoxBoxDetector.FaceClipper(); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let b1 = geom1; + let b2 = geom2; + result.incremental = false; + let c1X; + let c1Y; + let c1Z; + let c2X; + let c2Y; + let c2Z; + let c12X; + let c12Y; + let c12Z; + c1X = tf1._positionX; + c1Y = tf1._positionY; + c1Z = tf1._positionZ; + c2X = tf2._positionX; + c2Y = tf2._positionY; + c2Z = tf2._positionZ; + c12X = c2X - c1X; + c12Y = c2Y - c1Y; + c12Z = c2Z - c1Z; + let x1X; + let x1Y; + let x1Z; + let y1X; + let y1Y; + let y1Z; + let z1X; + let z1Y; + let z1Z; + let x2X; + let x2Y; + let x2Z; + let y2X; + let y2Y; + let y2Z; + let z2X; + let z2Y; + let z2Z; + x1X = tf1._rotation00; + x1Y = tf1._rotation10; + x1Z = tf1._rotation20; + y1X = tf1._rotation01; + y1Y = tf1._rotation11; + y1Z = tf1._rotation21; + z1X = tf1._rotation02; + z1Y = tf1._rotation12; + z1Z = tf1._rotation22; + x2X = tf2._rotation00; + x2Y = tf2._rotation10; + x2Z = tf2._rotation20; + y2X = tf2._rotation01; + y2Y = tf2._rotation11; + y2Z = tf2._rotation21; + z2X = tf2._rotation02; + z2Y = tf2._rotation12; + z2Z = tf2._rotation22; + let w1 = b1._halfExtentsX; + let h1 = b1._halfExtentsY; + let d1 = b1._halfExtentsZ; + let w2 = b2._halfExtentsX; + let h2 = b2._halfExtentsY; + let d2 = b2._halfExtentsZ; + let sx1X; + let sx1Y; + let sx1Z; + let sy1X; + let sy1Y; + let sy1Z; + let sz1X; + let sz1Y; + let sz1Z; + let sx2X; + let sx2Y; + let sx2Z; + let sy2X; + let sy2Y; + let sy2Z; + let sz2X; + let sz2Y; + let sz2Z; + sx1X = x1X * w1; + sx1Y = x1Y * w1; + sx1Z = x1Z * w1; + sy1X = y1X * h1; + sy1Y = y1Y * h1; + sy1Z = y1Z * h1; + sz1X = z1X * d1; + sz1Y = z1Y * d1; + sz1Z = z1Z * d1; + sx2X = x2X * w2; + sx2Y = x2Y * w2; + sx2Z = x2Z * w2; + sy2X = y2X * h2; + sy2Y = y2Y * h2; + sy2Z = y2Z * h2; + sz2X = z2X * d2; + sz2Y = z2Y * d2; + sz2Z = z2Z * d2; + let mDepth = 1e65536; + let mId = -1; + let mSign = 0; + let mAxisX; + let mAxisY; + let mAxisZ; + mAxisX = 0; + mAxisY = 0; + mAxisZ = 0; + let proj1 = w1; + let dx = x1X * sx2X + x1Y * sx2Y + x1Z * sx2Z; + let dy = x1X * sy2X + x1Y * sy2Y + x1Z * sy2Z; + let dz = x1X * sz2X + x1Y * sz2Y + x1Z * sz2Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + if(dz < 0) { + dz = -dz; + } + let proj2 = dx + dy + dz; + let projC12 = x1X * c12X + x1Y * c12Y + x1Z * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < 1e65536) { + mDepth = depth; + mId = 0; + mAxisX = x1X; + mAxisY = x1Y; + mAxisZ = x1Z; + mSign = neg ? -1 : 1; + } + } else { + return; + } + proj1 = h1; + let dx1 = y1X * sx2X + y1Y * sx2Y + y1Z * sx2Z; + let dy1 = y1X * sy2X + y1Y * sy2Y + y1Z * sy2Z; + let dz1 = y1X * sz2X + y1Y * sz2Y + y1Z * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + if(dz1 < 0) { + dz1 = -dz1; + } + proj2 = dx1 + dy1 + dz1; + projC12 = y1X * c12X + y1Y * c12Y + y1Z * c12Z; + let sum1 = proj1 + proj2; + let neg1 = projC12 < 0; + let abs1 = neg1 ? -projC12 : projC12; + if(abs1 < sum1) { + let depth = sum1 - abs1; + if(depth < mDepth) { + mDepth = depth; + mId = 1; + mAxisX = y1X; + mAxisY = y1Y; + mAxisZ = y1Z; + mSign = neg1 ? -1 : 1; + } + } else { + return; + } + proj1 = d1; + let dx2 = z1X * sx2X + z1Y * sx2Y + z1Z * sx2Z; + let dy2 = z1X * sy2X + z1Y * sy2Y + z1Z * sy2Z; + let dz2 = z1X * sz2X + z1Y * sz2Y + z1Z * sz2Z; + if(dx2 < 0) { + dx2 = -dx2; + } + if(dy2 < 0) { + dy2 = -dy2; + } + if(dz2 < 0) { + dz2 = -dz2; + } + proj2 = dx2 + dy2 + dz2; + projC12 = z1X * c12X + z1Y * c12Y + z1Z * c12Z; + let sum2 = proj1 + proj2; + let neg2 = projC12 < 0; + let abs2 = neg2 ? -projC12 : projC12; + if(abs2 < sum2) { + let depth = sum2 - abs2; + if(depth < mDepth) { + mDepth = depth; + mId = 2; + mAxisX = z1X; + mAxisY = z1Y; + mAxisZ = z1Z; + mSign = neg2 ? -1 : 1; + } + } else { + return; + } + if(mDepth > oimo.common.Setting.linearSlop) { + mDepth -= oimo.common.Setting.linearSlop; + } else { + mDepth = 0; + } + let dx3 = x2X * sx1X + x2Y * sx1Y + x2Z * sx1Z; + let dy3 = x2X * sy1X + x2Y * sy1Y + x2Z * sy1Z; + let dz3 = x2X * sz1X + x2Y * sz1Y + x2Z * sz1Z; + if(dx3 < 0) { + dx3 = -dx3; + } + if(dy3 < 0) { + dy3 = -dy3; + } + if(dz3 < 0) { + dz3 = -dz3; + } + proj1 = dx3 + dy3 + dz3; + proj2 = w2; + projC12 = x2X * c12X + x2Y * c12Y + x2Z * c12Z; + let sum3 = proj1 + proj2; + let neg3 = projC12 < 0; + let abs3 = neg3 ? -projC12 : projC12; + if(abs3 < sum3) { + let depth = sum3 - abs3; + if(depth < mDepth) { + mDepth = depth; + mId = 3; + mAxisX = x2X; + mAxisY = x2Y; + mAxisZ = x2Z; + mSign = neg3 ? -1 : 1; + } + } else { + return; + } + let dx4 = y2X * sx1X + y2Y * sx1Y + y2Z * sx1Z; + let dy4 = y2X * sy1X + y2Y * sy1Y + y2Z * sy1Z; + let dz4 = y2X * sz1X + y2Y * sz1Y + y2Z * sz1Z; + if(dx4 < 0) { + dx4 = -dx4; + } + if(dy4 < 0) { + dy4 = -dy4; + } + if(dz4 < 0) { + dz4 = -dz4; + } + proj1 = dx4 + dy4 + dz4; + proj2 = h2; + projC12 = y2X * c12X + y2Y * c12Y + y2Z * c12Z; + let sum4 = proj1 + proj2; + let neg4 = projC12 < 0; + let abs4 = neg4 ? -projC12 : projC12; + if(abs4 < sum4) { + let depth = sum4 - abs4; + if(depth < mDepth) { + mDepth = depth; + mId = 4; + mAxisX = y2X; + mAxisY = y2Y; + mAxisZ = y2Z; + mSign = neg4 ? -1 : 1; + } + } else { + return; + } + let dx5 = z2X * sx1X + z2Y * sx1Y + z2Z * sx1Z; + let dy5 = z2X * sy1X + z2Y * sy1Y + z2Z * sy1Z; + let dz5 = z2X * sz1X + z2Y * sz1Y + z2Z * sz1Z; + if(dx5 < 0) { + dx5 = -dx5; + } + if(dy5 < 0) { + dy5 = -dy5; + } + if(dz5 < 0) { + dz5 = -dz5; + } + proj1 = dx5 + dy5 + dz5; + proj2 = d2; + projC12 = z2X * c12X + z2Y * c12Y + z2Z * c12Z; + let sum5 = proj1 + proj2; + let neg5 = projC12 < 0; + let abs5 = neg5 ? -projC12 : projC12; + if(abs5 < sum5) { + let depth = sum5 - abs5; + if(depth < mDepth) { + mDepth = depth; + mId = 5; + mAxisX = z2X; + mAxisY = z2Y; + mAxisZ = z2Z; + mSign = neg5 ? -1 : 1; + } + } else { + return; + } + if(mDepth > oimo.common.Setting.linearSlop) { + mDepth -= oimo.common.Setting.linearSlop; + } else { + mDepth = 0; + } + let edgeAxisX; + let edgeAxisY; + let edgeAxisZ; + edgeAxisX = x1Y * x2Z - x1Z * x2Y; + edgeAxisY = x1Z * x2X - x1X * x2Z; + edgeAxisZ = x1X * x2Y - x1Y * x2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 6; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = x1Y * y2Z - x1Z * y2Y; + edgeAxisY = x1Z * y2X - x1X * y2Z; + edgeAxisZ = x1X * y2Y - x1Y * y2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 7; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = x1Y * z2Z - x1Z * z2Y; + edgeAxisY = x1Z * z2X - x1X * z2Z; + edgeAxisZ = x1X * z2Y - x1Y * z2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 8; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = y1Y * x2Z - y1Z * x2Y; + edgeAxisY = y1Z * x2X - y1X * x2Z; + edgeAxisZ = y1X * x2Y - y1Y * x2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 9; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = y1Y * y2Z - y1Z * y2Y; + edgeAxisY = y1Z * y2X - y1X * y2Z; + edgeAxisZ = y1X * y2Y - y1Y * y2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 10; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = y1Y * z2Z - y1Z * z2Y; + edgeAxisY = y1Z * z2X - y1X * z2Z; + edgeAxisZ = y1X * z2Y - y1Y * z2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 11; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = z1Y * x2Z - z1Z * x2Y; + edgeAxisY = z1Z * x2X - z1X * x2Z; + edgeAxisZ = z1X * x2Y - z1Y * x2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 12; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = z1Y * y2Z - z1Z * y2Y; + edgeAxisY = z1Z * y2X - z1X * y2Z; + edgeAxisZ = z1X * y2Y - z1Y * y2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 13; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = z1Y * z2Z - z1Z * z2Y; + edgeAxisY = z1Z * z2X - z1X * z2Z; + edgeAxisZ = z1X * z2Y - z1Y * z2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 14; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + if(mId >= 6) { + mAxisX *= mSign; + mAxisY *= mSign; + mAxisZ *= mSign; + let id1 = (mId - 6) / 3 | 0; + let id2 = mId - 6 - id1 * 3; + let p1X; + let p1Y; + let p1Z; + let p2X; + let p2Y; + let p2Z; + let d1X; + let d1Y; + let d1Z; + let d2X; + let d2Y; + let d2Z; + switch(id1) { + case 0: + d1X = x1X; + d1Y = x1Y; + d1Z = x1Z; + let signY = sz1X * mAxisX + sz1Y * mAxisY + sz1Z * mAxisZ > 0; + if(sy1X * mAxisX + sy1Y * mAxisY + sy1Z * mAxisZ > 0) { + if(signY) { + p1X = sy1X + sz1X; + p1Y = sy1Y + sz1Y; + p1Z = sy1Z + sz1Z; + } else { + p1X = sy1X - sz1X; + p1Y = sy1Y - sz1Y; + p1Z = sy1Z - sz1Z; + } + } else if(signY) { + p1X = sz1X - sy1X; + p1Y = sz1Y - sy1Y; + p1Z = sz1Z - sy1Z; + } else { + p1X = sy1X + sz1X; + p1Y = sy1Y + sz1Y; + p1Z = sy1Z + sz1Z; + p1X = -p1X; + p1Y = -p1Y; + p1Z = -p1Z; + } + break; + case 1: + d1X = y1X; + d1Y = y1Y; + d1Z = y1Z; + let signY1 = sz1X * mAxisX + sz1Y * mAxisY + sz1Z * mAxisZ > 0; + if(sx1X * mAxisX + sx1Y * mAxisY + sx1Z * mAxisZ > 0) { + if(signY1) { + p1X = sx1X + sz1X; + p1Y = sx1Y + sz1Y; + p1Z = sx1Z + sz1Z; + } else { + p1X = sx1X - sz1X; + p1Y = sx1Y - sz1Y; + p1Z = sx1Z - sz1Z; + } + } else if(signY1) { + p1X = sz1X - sx1X; + p1Y = sz1Y - sx1Y; + p1Z = sz1Z - sx1Z; + } else { + p1X = sx1X + sz1X; + p1Y = sx1Y + sz1Y; + p1Z = sx1Z + sz1Z; + p1X = -p1X; + p1Y = -p1Y; + p1Z = -p1Z; + } + break; + default: + d1X = z1X; + d1Y = z1Y; + d1Z = z1Z; + let signY2 = sy1X * mAxisX + sy1Y * mAxisY + sy1Z * mAxisZ > 0; + if(sx1X * mAxisX + sx1Y * mAxisY + sx1Z * mAxisZ > 0) { + if(signY2) { + p1X = sx1X + sy1X; + p1Y = sx1Y + sy1Y; + p1Z = sx1Z + sy1Z; + } else { + p1X = sx1X - sy1X; + p1Y = sx1Y - sy1Y; + p1Z = sx1Z - sy1Z; + } + } else if(signY2) { + p1X = sy1X - sx1X; + p1Y = sy1Y - sx1Y; + p1Z = sy1Z - sx1Z; + } else { + p1X = sx1X + sy1X; + p1Y = sx1Y + sy1Y; + p1Z = sx1Z + sy1Z; + p1X = -p1X; + p1Y = -p1Y; + p1Z = -p1Z; + } + } + p1X = c1X + p1X; + p1Y = c1Y + p1Y; + p1Z = c1Z + p1Z; + switch(id2) { + case 0: + d2X = x2X; + d2Y = x2Y; + d2Z = x2Z; + let signY3 = sz2X * mAxisX + sz2Y * mAxisY + sz2Z * mAxisZ > 0; + if(sy2X * mAxisX + sy2Y * mAxisY + sy2Z * mAxisZ > 0) { + if(signY3) { + p2X = sy2X + sz2X; + p2Y = sy2Y + sz2Y; + p2Z = sy2Z + sz2Z; + } else { + p2X = sy2X - sz2X; + p2Y = sy2Y - sz2Y; + p2Z = sy2Z - sz2Z; + } + } else if(signY3) { + p2X = sz2X - sy2X; + p2Y = sz2Y - sy2Y; + p2Z = sz2Z - sy2Z; + } else { + p2X = sy2X + sz2X; + p2Y = sy2Y + sz2Y; + p2Z = sy2Z + sz2Z; + p2X = -p2X; + p2Y = -p2Y; + p2Z = -p2Z; + } + break; + case 1: + d2X = y2X; + d2Y = y2Y; + d2Z = y2Z; + let signY4 = sz2X * mAxisX + sz2Y * mAxisY + sz2Z * mAxisZ > 0; + if(sx2X * mAxisX + sx2Y * mAxisY + sx2Z * mAxisZ > 0) { + if(signY4) { + p2X = sx2X + sz2X; + p2Y = sx2Y + sz2Y; + p2Z = sx2Z + sz2Z; + } else { + p2X = sx2X - sz2X; + p2Y = sx2Y - sz2Y; + p2Z = sx2Z - sz2Z; + } + } else if(signY4) { + p2X = sz2X - sx2X; + p2Y = sz2Y - sx2Y; + p2Z = sz2Z - sx2Z; + } else { + p2X = sx2X + sz2X; + p2Y = sx2Y + sz2Y; + p2Z = sx2Z + sz2Z; + p2X = -p2X; + p2Y = -p2Y; + p2Z = -p2Z; + } + break; + default: + d2X = z2X; + d2Y = z2Y; + d2Z = z2Z; + let signY5 = sy2X * mAxisX + sy2Y * mAxisY + sy2Z * mAxisZ > 0; + if(sx2X * mAxisX + sx2Y * mAxisY + sx2Z * mAxisZ > 0) { + if(signY5) { + p2X = sx2X + sy2X; + p2Y = sx2Y + sy2Y; + p2Z = sx2Z + sy2Z; + } else { + p2X = sx2X - sy2X; + p2Y = sx2Y - sy2Y; + p2Z = sx2Z - sy2Z; + } + } else if(signY5) { + p2X = sy2X - sx2X; + p2Y = sy2Y - sx2Y; + p2Z = sy2Z - sx2Z; + } else { + p2X = sx2X + sy2X; + p2Y = sx2Y + sy2Y; + p2Z = sx2Z + sy2Z; + p2X = -p2X; + p2Y = -p2Y; + p2Z = -p2Z; + } + } + p2X = c2X - p2X; + p2Y = c2Y - p2Y; + p2Z = c2Z - p2Z; + let rX; + let rY; + let rZ; + rX = p1X - p2X; + rY = p1Y - p2Y; + rZ = p1Z - p2Z; + let dot12 = d1X * d2X + d1Y * d2Y + d1Z * d2Z; + let dot1r = d1X * rX + d1Y * rY + d1Z * rZ; + let dot2r = d2X * rX + d2Y * rY + d2Z * rZ; + let invDet = 1 / (1 - dot12 * dot12); + let t1 = (dot12 * dot2r - dot1r) * invDet; + let t2 = (dot2r - dot12 * dot1r) * invDet; + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + cp1X = p1X + d1X * t1; + cp1Y = p1Y + d1Y * t1; + cp1Z = p1Z + d1Z * t1; + cp2X = p2X + d2X * t2; + cp2Y = p2Y + d2Y * t2; + cp2Z = p2Z + d2Z * t2; + let normalX; + let normalY; + let normalZ; + normalX = -mAxisX; + normalY = -mAxisY; + normalZ = -mAxisZ; + this.setNormal(result,normalX,normalY,normalZ); + this.addPoint(result,cp1X,cp1Y,cp1Z,cp2X,cp2Y,cp2Z,mDepth,4); + return; + } + let tmpX; + let tmpY; + let tmpZ; + let swapped; + if(mId >= 3) { + mSign = -mSign; + c12X = -c12X; + c12Y = -c12Y; + c12Z = -c12Z; + + + + + w1 = w2; + + + h1 = h2; + + + d1 = d2; + + + + + c1X = c2X; + c1Y = c2Y; + c1Z = c2Z; + + + + tmpX = x1X; + tmpY = x1Y; + tmpZ = x1Z; + x1X = x2X; + x1Y = x2Y; + x1Z = x2Z; + x2X = tmpX; + x2Y = tmpY; + x2Z = tmpZ; + tmpX = y1X; + tmpY = y1Y; + tmpZ = y1Z; + y1X = y2X; + y1Y = y2Y; + y1Z = y2Z; + y2X = tmpX; + y2Y = tmpY; + y2Z = tmpZ; + tmpX = z1X; + tmpY = z1Y; + tmpZ = z1Z; + z1X = z2X; + z1Y = z2Y; + z1Z = z2Z; + z2X = tmpX; + z2Y = tmpY; + z2Z = tmpZ; + tmpX = sx1X; + tmpY = sx1Y; + tmpZ = sx1Z; + sx1X = sx2X; + sx1Y = sx2Y; + sx1Z = sx2Z; + sx2X = tmpX; + sx2Y = tmpY; + sx2Z = tmpZ; + tmpX = sy1X; + tmpY = sy1Y; + tmpZ = sy1Z; + sy1X = sy2X; + sy1Y = sy2Y; + sy1Z = sy2Z; + sy2X = tmpX; + sy2Y = tmpY; + sy2Z = tmpZ; + tmpX = sz1X; + tmpY = sz1Y; + tmpZ = sz1Z; + sz1X = sz2X; + sz1Y = sz2Y; + sz1Z = sz2Z; + sz2X = tmpX; + sz2Y = tmpY; + sz2Z = tmpZ; + mId -= 3; + swapped = true; + } else { + swapped = false; + } + let refCenterX; + let refCenterY; + let refCenterZ; + let refNormalX; + let refNormalY; + let refNormalZ; + let refXX; + let refXY; + let refXZ; + let refYX; + let refYY; + let refYZ; + let refW; + let refH; + switch(mId) { + case 0: + refCenterX = sx1X; + refCenterY = sx1Y; + refCenterZ = sx1Z; + refNormalX = x1X; + refNormalY = x1Y; + refNormalZ = x1Z; + refXX = y1X; + refXY = y1Y; + refXZ = y1Z; + refYX = z1X; + refYY = z1Y; + refYZ = z1Z; + refW = h1; + refH = d1; + break; + case 1: + refCenterX = sy1X; + refCenterY = sy1Y; + refCenterZ = sy1Z; + refNormalX = y1X; + refNormalY = y1Y; + refNormalZ = y1Z; + refXX = z1X; + refXY = z1Y; + refXZ = z1Z; + refYX = x1X; + refYY = x1Y; + refYZ = x1Z; + refW = d1; + refH = w1; + break; + default: + refCenterX = sz1X; + refCenterY = sz1Y; + refCenterZ = sz1Z; + refNormalX = z1X; + refNormalY = z1Y; + refNormalZ = z1Z; + refXX = x1X; + refXY = x1Y; + refXZ = x1Z; + refYX = y1X; + refYY = y1Y; + refYZ = y1Z; + refW = w1; + refH = h1; + } + if(mSign < 0) { + refCenterX = -refCenterX; + refCenterY = -refCenterY; + refCenterZ = -refCenterZ; + refNormalX = -refNormalX; + refNormalY = -refNormalY; + refNormalZ = -refNormalZ; + tmpX = refXX; + tmpY = refXY; + tmpZ = refXZ; + refXX = refYX; + refXY = refYY; + refXZ = refYZ; + refYX = tmpX; + refYY = tmpY; + refYZ = tmpZ; + let tmp = refW; + refW = refH; + refH = tmp; + } + refCenterX += c1X; + refCenterY += c1Y; + refCenterZ += c1Z; + let minIncDot = 1; + let incId = 0; + let incDot = refNormalX * x2X + refNormalY * x2Y + refNormalZ * x2Z; + if(incDot < minIncDot) { + minIncDot = incDot; + incId = 0; + } + if(-incDot < minIncDot) { + minIncDot = -incDot; + incId = 1; + } + incDot = refNormalX * y2X + refNormalY * y2Y + refNormalZ * y2Z; + if(incDot < minIncDot) { + minIncDot = incDot; + incId = 2; + } + if(-incDot < minIncDot) { + minIncDot = -incDot; + incId = 3; + } + incDot = refNormalX * z2X + refNormalY * z2Y + refNormalZ * z2Z; + if(incDot < minIncDot) { + minIncDot = incDot; + incId = 4; + } + if(-incDot < minIncDot) { + + incId = 5; + } + let incV1X; + let incV1Y; + let incV1Z; + let incV2X; + let incV2Y; + let incV2Z; + let incV3X; + let incV3Y; + let incV3Z; + let incV4X; + let incV4Y; + let incV4Z; + switch(incId) { + case 0: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sx2X - sy2X; + incV2Y = sx2Y - sy2Y; + incV2Z = sx2Z - sy2Z; + incV2X += sz2X; + incV2Y += sz2Y; + incV2Z += sz2Z; + incV3X = sx2X - sy2X; + incV3Y = sx2Y - sy2Y; + incV3Z = sx2Z - sy2Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sx2X + sy2X; + incV4Y = sx2Y + sy2Y; + incV4Z = sx2Z + sy2Z; + incV4X -= sz2X; + incV4Y -= sz2Y; + incV4Z -= sz2Z; + break; + case 1: + incV1X = sy2X - sx2X; + incV1Y = sy2Y - sx2Y; + incV1Z = sy2Z - sx2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sy2X - sx2X; + incV2Y = sy2Y - sx2Y; + incV2Z = sy2Z - sx2Z; + incV2X -= sz2X; + incV2Y -= sz2Y; + incV2Z -= sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sx2X + sy2X; + incV4Y = sx2Y + sy2Y; + incV4Z = sx2Z + sy2Z; + incV4X = -incV4X; + incV4Y = -incV4Y; + incV4Z = -incV4Z; + incV4X += sz2X; + incV4Y += sz2Y; + incV4Z += sz2Z; + break; + case 2: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sx2X + sy2X; + incV2Y = sx2Y + sy2Y; + incV2Z = sx2Z + sy2Z; + incV2X -= sz2X; + incV2Y -= sz2Y; + incV2Z -= sz2Z; + incV3X = sy2X - sx2X; + incV3Y = sy2Y - sx2Y; + incV3Z = sy2Z - sx2Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sy2X - sx2X; + incV4Y = sy2Y - sx2Y; + incV4Z = sy2Z - sx2Z; + incV4X += sz2X; + incV4Y += sz2Y; + incV4Z += sz2Z; + break; + case 3: + incV1X = sx2X - sy2X; + incV1Y = sx2Y - sy2Y; + incV1Z = sx2Z - sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sx2X + sy2X; + incV2Y = sx2Y + sy2Y; + incV2Z = sx2Z + sy2Z; + incV2X = -incV2X; + incV2Y = -incV2Y; + incV2Z = -incV2Z; + incV2X += sz2X; + incV2Y += sz2Y; + incV2Z += sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sx2X - sy2X; + incV4Y = sx2Y - sy2Y; + incV4Z = sx2Z - sy2Z; + incV4X -= sz2X; + incV4Y -= sz2Y; + incV4Z -= sz2Z; + break; + case 4: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sy2X - sx2X; + incV2Y = sy2Y - sx2Y; + incV2Z = sy2Z - sx2Z; + incV2X += sz2X; + incV2Y += sz2Y; + incV2Z += sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X += sz2X; + incV3Y += sz2Y; + incV3Z += sz2Z; + incV4X = sx2X - sy2X; + incV4Y = sx2Y - sy2Y; + incV4Z = sx2Z - sy2Z; + incV4X += sz2X; + incV4Y += sz2Y; + incV4Z += sz2Z; + break; + default: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X -= sz2X; + incV1Y -= sz2Y; + incV1Z -= sz2Z; + incV2X = sx2X - sy2X; + incV2Y = sx2Y - sy2Y; + incV2Z = sx2Z - sy2Z; + incV2X -= sz2X; + incV2Y -= sz2Y; + incV2Z -= sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sy2X - sx2X; + incV4Y = sy2Y - sx2Y; + incV4Z = sy2Z - sx2Z; + incV4X -= sz2X; + incV4Y -= sz2Y; + incV4Z -= sz2Z; + } + incV1X += c12X; + incV1Y += c12Y; + incV1Z += c12Z; + incV2X += c12X; + incV2Y += c12Y; + incV2Z += c12Z; + incV3X += c12X; + incV3Y += c12Y; + incV3Z += c12Z; + incV4X += c12X; + incV4Y += c12Y; + incV4Z += c12Z; + let _this = this.clipper; + _this.w = refW; + _this.h = refH; + _this.numVertices = 0; + _this.numTmpVertices = 0; + let _this1 = this.clipper; + let _this2 = _this1.vertices[_this1.numVertices++]; + _this2.x = incV1X * refXX + incV1Y * refXY + incV1Z * refXZ; + _this2.y = incV1X * refYX + incV1Y * refYY + incV1Z * refYZ; + _this2.wx = incV1X; + _this2.wy = incV1Y; + _this2.wz = incV1Z; + let _this3 = this.clipper; + let _this4 = _this3.vertices[_this3.numVertices++]; + _this4.x = incV2X * refXX + incV2Y * refXY + incV2Z * refXZ; + _this4.y = incV2X * refYX + incV2Y * refYY + incV2Z * refYZ; + _this4.wx = incV2X; + _this4.wy = incV2Y; + _this4.wz = incV2Z; + let _this5 = this.clipper; + let _this6 = _this5.vertices[_this5.numVertices++]; + _this6.x = incV3X * refXX + incV3Y * refXY + incV3Z * refXZ; + _this6.y = incV3X * refYX + incV3Y * refYY + incV3Z * refYZ; + _this6.wx = incV3X; + _this6.wy = incV3Y; + _this6.wz = incV3Z; + let _this7 = this.clipper; + let _this8 = _this7.vertices[_this7.numVertices++]; + _this8.x = incV4X * refXX + incV4Y * refXY + incV4Z * refXZ; + _this8.y = incV4X * refYX + incV4Y * refYY + incV4Z * refYZ; + _this8.wx = incV4X; + _this8.wy = incV4Y; + _this8.wz = incV4Z; + this.clipper.clip(); + this.clipper.reduce(); + let normalX; + let normalY; + let normalZ; + if(swapped) { + normalX = refNormalX; + normalY = refNormalY; + normalZ = refNormalZ; + } else { + normalX = -refNormalX; + normalY = -refNormalY; + normalZ = -refNormalZ; + } + this.setNormal(result,normalX,normalY,normalZ); + let _g = 0; + let _g1 = this.clipper.numVertices; + while(_g < _g1) { + let i = _g++; + let v = this.clipper.vertices[i]; + let clippedVertexX; + let clippedVertexY; + let clippedVertexZ; + clippedVertexX = v.wx; + clippedVertexY = v.wy; + clippedVertexZ = v.wz; + clippedVertexX += c1X; + clippedVertexY += c1Y; + clippedVertexZ += c1Z; + let clippedVertexToRefCenterX; + let clippedVertexToRefCenterY; + let clippedVertexToRefCenterZ; + clippedVertexToRefCenterX = refCenterX - clippedVertexX; + clippedVertexToRefCenterY = refCenterY - clippedVertexY; + clippedVertexToRefCenterZ = refCenterZ - clippedVertexZ; + let depth = clippedVertexToRefCenterX * refNormalX + clippedVertexToRefCenterY * refNormalY + clippedVertexToRefCenterZ * refNormalZ; + let clippedVertexOnRefFaceX; + let clippedVertexOnRefFaceY; + let clippedVertexOnRefFaceZ; + clippedVertexOnRefFaceX = clippedVertexX + refNormalX * depth; + clippedVertexOnRefFaceY = clippedVertexY + refNormalY * depth; + clippedVertexOnRefFaceZ = clippedVertexZ + refNormalZ * depth; + if(depth > -oimo.common.Setting.contactPersistenceThreshold) { + if(swapped) { + this.addPoint(result,clippedVertexX,clippedVertexY,clippedVertexZ,clippedVertexOnRefFaceX,clippedVertexOnRefFaceY,clippedVertexOnRefFaceZ,depth,i); + } else { + this.addPoint(result,clippedVertexOnRefFaceX,clippedVertexOnRefFaceY,clippedVertexOnRefFaceZ,clippedVertexX,clippedVertexY,clippedVertexZ,depth,i); + } + } + } + } +} +if(!oimo.collision.narrowphase.detector._BoxBoxDetector) oimo.collision.narrowphase.detector._BoxBoxDetector = {}; +oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex = class oimo_collision_narrowphase_detector__$BoxBoxDetector_IncidentVertex { + constructor() { + this.x = 0; + this.y = 0; + this.wx = 0; + this.wy = 0; + this.wz = 0; + } +} +oimo.collision.narrowphase.detector._BoxBoxDetector.FaceClipper = class oimo_collision_narrowphase_detector__$BoxBoxDetector_FaceClipper { + constructor() { + this.w = 0; + this.h = 0; + this.numVertices = 0; + this.numTmpVertices = 0; + this.vertices = new Array(8); + this.tmpVertices = new Array(8); + this.vertices[0] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[0] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[1] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[1] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[2] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[2] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[3] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[3] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[4] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[4] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[5] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[5] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[6] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[6] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[7] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[7] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + } + clip() { + let _g = 0; + let _g1 = this.numVertices; + while(_g < _g1) { + let i = _g++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.w + v1.x; + let s2 = this.w + v2.x; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + let _g2 = 0; + let _g3 = this.numVertices; + while(_g2 < _g3) { + let i = _g2++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.w - v1.x; + let s2 = this.w - v2.x; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp1 = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp1; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + let _g4 = 0; + let _g5 = this.numVertices; + while(_g4 < _g5) { + let i = _g4++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.h + v1.y; + let s2 = this.h + v2.y; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp2 = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp2; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + let _g6 = 0; + let _g7 = this.numVertices; + while(_g6 < _g7) { + let i = _g6++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.h - v1.y; + let s2 = this.h - v2.y; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp3 = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp3; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + } + reduce() { + if(this.numVertices < 4) { + return; + } + let max1 = -1e65536; + let min1 = 1e65536; + let max2 = -1e65536; + let min2 = 1e65536; + let max1V = null; + let min1V = null; + let max2V = null; + let min2V = null; + let e1x = 1; + let e1y = 1; + let e2x = -1; + let e2y = 1; + let _g = 0; + let _g1 = this.numVertices; + while(_g < _g1) { + let v = this.vertices[_g++]; + let dot1 = v.x * e1x + v.y * e1y; + let dot2 = v.x * e2x + v.y * e2y; + if(dot1 > max1) { + max1 = dot1; + max1V = v; + } + if(dot1 < min1) { + min1 = dot1; + min1V = v; + } + if(dot2 > max2) { + max2 = dot2; + max2V = v; + } + if(dot2 < min2) { + min2 = dot2; + min2V = v; + } + } + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = max1V.x; + _this.y = max1V.y; + _this.wx = max1V.wx; + _this.wy = max1V.wy; + _this.wz = max1V.wz; + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = max2V.x; + _this1.y = max2V.y; + _this1.wx = max2V.wx; + _this1.wy = max2V.wy; + _this1.wz = max2V.wz; + let _this2 = this.tmpVertices[this.numTmpVertices++]; + _this2.x = min1V.x; + _this2.y = min1V.y; + _this2.wx = min1V.wx; + _this2.wy = min1V.wy; + _this2.wz = min1V.wz; + let _this3 = this.tmpVertices[this.numTmpVertices++]; + _this3.x = min2V.x; + _this3.y = min2V.y; + _this3.wx = min2V.wx; + _this3.wy = min2V.wy; + _this3.wz = min2V.wz; + let tmp = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + } +} +oimo.collision.narrowphase.detector.BoxBoxDetectorMacro = class oimo_collision_narrowphase_detector_BoxBoxDetectorMacro { +} +oimo.collision.narrowphase.detector.CachedDetectorData = class oimo_collision_narrowphase_detector_CachedDetectorData { + constructor() { + } + _clear() { + if(this._gjkCache != null) { + this._gjkCache.clear(); + } + } +} +oimo.collision.narrowphase.detector.CapsuleCapsuleDetector = class oimo_collision_narrowphase_detector_CapsuleCapsuleDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let c1 = geom1; + let c2 = geom2; + result.incremental = false; + let axis1X; + let axis1Y; + let axis1Z; + let axis2X; + let axis2Y; + let axis2Z; + axis1X = tf1._rotation01; + axis1Y = tf1._rotation11; + axis1Z = tf1._rotation21; + axis2X = tf2._rotation01; + axis2Y = tf2._rotation11; + axis2Z = tf2._rotation21; + let hh1 = c1._halfHeight; + let hh2 = c2._halfHeight; + let r1 = c1._radius; + let r2 = c2._radius; + let p1X; + let p1Y; + let p1Z; + let q1X; + let q1Y; + let q1Z; + let p2X; + let p2Y; + let p2Z; + let q2X; + let q2Y; + let q2Z; + p1X = tf1._positionX + axis1X * -hh1; + p1Y = tf1._positionY + axis1Y * -hh1; + p1Z = tf1._positionZ + axis1Z * -hh1; + q1X = tf1._positionX + axis1X * hh1; + q1Y = tf1._positionY + axis1Y * hh1; + q1Z = tf1._positionZ + axis1Z * hh1; + p2X = tf2._positionX + axis2X * -hh2; + p2Y = tf2._positionY + axis2Y * -hh2; + p2Z = tf2._positionZ + axis2Z * -hh2; + q2X = tf2._positionX + axis2X * hh2; + q2Y = tf2._positionY + axis2Y * hh2; + q2Z = tf2._positionZ + axis2Z * hh2; + let p12X; + let p12Y; + let p12Z; + p12X = p1X - p2X; + p12Y = p1Y - p2Y; + p12Z = p1Z - p2Z; + let d1X; + let d1Y; + let d1Z; + let d2X; + let d2Y; + let d2Z; + d1X = q1X - p1X; + d1Y = q1Y - p1Y; + d1Z = q1Z - p1Z; + d2X = q2X - p2X; + d2Y = q2Y - p2Y; + d2Z = q2Z - p2Z; + let p21d1 = -(p12X * d1X + p12Y * d1Y + p12Z * d1Z); + let p12d2 = p12X * d2X + p12Y * d2Y + p12Z * d2Z; + let d11 = hh1 * hh1 * 4; + let d12 = d1X * d2X + d1Y * d2Y + d1Z * d2Z; + let d22 = hh2 * hh2 * 4; + let t1; + let t2; + if(d11 == 0 && d22 == 0) { + t1 = 0; + t2 = 0; + } else if(d11 == 0) { + t1 = 0; + + if(p12d2 < 0) { + t2 = 0; + } else if(p12d2 > d22) { + t2 = 1; + } else { + t2 = p12d2 / d22; + } + } else if(d22 == 0) { + t2 = 0; + + if(p21d1 < 0) { + t1 = 0; + } else if(p21d1 > d11) { + t1 = 1; + } else { + t1 = p21d1 / d11; + } + } else { + let det = d11 * d22 - d12 * d12; + if(det == 0) { + t1 = 0; + } else { + t1 = d12 * p12d2 + d22 * p21d1; + if(t1 < 0) { + t1 = 0; + } else if(t1 > det) { + t1 = 1; + } else { + t1 /= det; + } + } + t2 = t1 * d12 + p12d2; + if(t2 < 0) { + t2 = 0; + + if(p21d1 < 0) { + t1 = 0; + } else if(p21d1 > d11) { + t1 = 1; + } else { + t1 = p21d1 / d11; + } + } else if(t2 > d22) { + t2 = 1; + t1 = d12 + p21d1; + if(t1 < 0) { + t1 = 0; + } else if(t1 > d11) { + t1 = 1; + } else { + t1 /= d11; + } + } else { + t2 /= d22; + } + } + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + cp1X = p1X + d1X * t1; + cp1Y = p1Y + d1Y * t1; + cp1Z = p1Z + d1Z * t1; + cp2X = p2X + d2X * t2; + cp2Y = p2Y + d2Y * t2; + cp2Z = p2Z + d2Z * t2; + let dX; + let dY; + let dZ; + dX = cp1X - cp2X; + dY = cp1Y - cp2Y; + dZ = cp1Z - cp2Z; + let len2 = dX * dX + dY * dY + dZ * dZ; + if(len2 >= (r1 + r2) * (r1 + r2)) { + return; + } + let len = Math.sqrt(len2); + let nX; + let nY; + let nZ; + if(len > 0) { + nX = dX * (1 / len); + nY = dY * (1 / len); + nZ = dZ * (1 / len); + } else { + nX = 1; + nY = 0; + nZ = 0; + } + this.setNormal(result,nX,nY,nZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = cp1X + nX * -r1; + pos1Y = cp1Y + nY * -r1; + pos1Z = cp1Z + nZ * -r1; + pos2X = cp2X + nX * r2; + pos2Y = cp2Y + nY * r2; + pos2Z = cp2Z + nZ * r2; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r1 + r2 - len,0); + } +} +oimo.collision.narrowphase.detector.GjkEpaDetector = class oimo_collision_narrowphase_detector_GjkEpaDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let gjkEpa = oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + let g1 = geom1; + let g2 = geom2; + let status = gjkEpa.computeClosestPointsImpl(g1,g2,tf1,tf2,oimo.common.Setting.enableGJKCaching ? cachedData : null,true); + result.incremental = true; + if(status != oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.SUCCEEDED) { + console.log("src/oimo/collision/narrowphase/detector/GjkEpaDetector.hx:28:","GJK/EPA failed: status=" + status); + return; + } + if(gjkEpa.distance > g1._gjkMargin + g2._gjkMargin) { + return; + } + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + let v = gjkEpa.closestPoint1; + pos1X = v.x; + pos1Y = v.y; + pos1Z = v.z; + let v1 = gjkEpa.closestPoint2; + pos2X = v1.x; + pos2Y = v1.y; + pos2Z = v1.z; + let normalX; + let normalY; + let normalZ; + normalX = pos1X - pos2X; + normalY = pos1Y - pos2Y; + normalZ = pos1Z - pos2Z; + if(normalX * normalX + normalY * normalY + normalZ * normalZ == 0) { + return; + } + if(gjkEpa.distance < 0) { + normalX = -normalX; + normalY = -normalY; + normalZ = -normalZ; + } + let l = normalX * normalX + normalY * normalY + normalZ * normalZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + normalX *= l; + normalY *= l; + normalZ *= l; + this.setNormal(result,normalX,normalY,normalZ); + pos1X += normalX * -g1._gjkMargin; + pos1Y += normalY * -g1._gjkMargin; + pos1Z += normalZ * -g1._gjkMargin; + pos2X += normalX * g2._gjkMargin; + pos2Y += normalY * g2._gjkMargin; + pos2Z += normalZ * g2._gjkMargin; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,g1._gjkMargin + g2._gjkMargin - gjkEpa.distance,0); + } +} +oimo.collision.narrowphase.detector.SphereBoxDetector = class oimo_collision_narrowphase_detector_SphereBoxDetector extends oimo.collision.narrowphase.detector.Detector { + constructor(swapped) { + super(swapped); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let b = geom2; + result.incremental = false; + let halfExtX; + let halfExtY; + let halfExtZ; + let negHalfExtX; + let negHalfExtY; + let negHalfExtZ; + halfExtX = b._halfExtentsX; + halfExtY = b._halfExtentsY; + halfExtZ = b._halfExtentsZ; + negHalfExtX = -halfExtX; + negHalfExtY = -halfExtY; + negHalfExtZ = -halfExtZ; + let r = geom1._radius; + let boxToSphereX; + let boxToSphereY; + let boxToSphereZ; + boxToSphereX = tf1._positionX - tf2._positionX; + boxToSphereY = tf1._positionY - tf2._positionY; + boxToSphereZ = tf1._positionZ - tf2._positionZ; + let boxToSphereInBoxX; + let boxToSphereInBoxY; + let boxToSphereInBoxZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf2._rotation00 * boxToSphereX + tf2._rotation10 * boxToSphereY + tf2._rotation20 * boxToSphereZ; + __tmp__Y = tf2._rotation01 * boxToSphereX + tf2._rotation11 * boxToSphereY + tf2._rotation21 * boxToSphereZ; + __tmp__Z = tf2._rotation02 * boxToSphereX + tf2._rotation12 * boxToSphereY + tf2._rotation22 * boxToSphereZ; + boxToSphereInBoxX = __tmp__X; + boxToSphereInBoxY = __tmp__Y; + boxToSphereInBoxZ = __tmp__Z; + if(negHalfExtX < boxToSphereInBoxX && halfExtX > boxToSphereInBoxX && negHalfExtY < boxToSphereInBoxY && halfExtY > boxToSphereInBoxY && negHalfExtZ < boxToSphereInBoxZ && halfExtZ > boxToSphereInBoxZ) { + let sphereToBoxSurfaceX; + let sphereToBoxSurfaceY; + let sphereToBoxSurfaceZ; + sphereToBoxSurfaceX = boxToSphereInBoxX < 0 ? -boxToSphereInBoxX : boxToSphereInBoxX; + sphereToBoxSurfaceY = boxToSphereInBoxY < 0 ? -boxToSphereInBoxY : boxToSphereInBoxY; + sphereToBoxSurfaceZ = boxToSphereInBoxZ < 0 ? -boxToSphereInBoxZ : boxToSphereInBoxZ; + sphereToBoxSurfaceX = halfExtX - sphereToBoxSurfaceX; + sphereToBoxSurfaceY = halfExtY - sphereToBoxSurfaceY; + sphereToBoxSurfaceZ = halfExtZ - sphereToBoxSurfaceZ; + let normalInBoxX; + let normalInBoxY; + let normalInBoxZ; + let distX = sphereToBoxSurfaceX; + let distY = sphereToBoxSurfaceY; + let distZ = sphereToBoxSurfaceZ; + let depth; + let projectionMaskX; + let projectionMaskY; + let projectionMaskZ; + if(distX < distY) { + if(distX < distZ) { + if(boxToSphereInBoxX > 0) { + normalInBoxX = 1; + normalInBoxY = 0; + normalInBoxZ = 0; + } else { + normalInBoxX = -1; + normalInBoxY = 0; + normalInBoxZ = 0; + } + projectionMaskX = 0; + projectionMaskY = 1; + projectionMaskZ = 1; + depth = distX; + } else { + if(boxToSphereInBoxZ > 0) { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = 1; + } else { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = -1; + } + projectionMaskX = 1; + projectionMaskY = 1; + projectionMaskZ = 0; + depth = distZ; + } + } else if(distY < distZ) { + if(boxToSphereInBoxY > 0) { + normalInBoxX = 0; + normalInBoxY = 1; + normalInBoxZ = 0; + } else { + normalInBoxX = 0; + normalInBoxY = -1; + normalInBoxZ = 0; + } + projectionMaskX = 1; + projectionMaskY = 0; + projectionMaskZ = 1; + depth = distY; + } else { + if(boxToSphereInBoxZ > 0) { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = 1; + } else { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = -1; + } + projectionMaskX = 1; + projectionMaskY = 1; + projectionMaskZ = 0; + depth = distZ; + } + let baseX; + let baseY; + let baseZ; + baseX = projectionMaskX * boxToSphereInBoxX; + baseY = projectionMaskY * boxToSphereInBoxY; + baseZ = projectionMaskZ * boxToSphereInBoxZ; + let boxToClosestPointInBoxX; + let boxToClosestPointInBoxY; + let boxToClosestPointInBoxZ; + boxToClosestPointInBoxX = normalInBoxX * halfExtX; + boxToClosestPointInBoxY = normalInBoxY * halfExtY; + boxToClosestPointInBoxZ = normalInBoxZ * halfExtZ; + boxToClosestPointInBoxX += baseX; + boxToClosestPointInBoxY += baseY; + boxToClosestPointInBoxZ += baseZ; + let boxToClosestPointX; + let boxToClosestPointY; + let boxToClosestPointZ; + let normalX; + let normalY; + let normalZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf2._rotation00 * boxToClosestPointInBoxX + tf2._rotation01 * boxToClosestPointInBoxY + tf2._rotation02 * boxToClosestPointInBoxZ; + __tmp__Y = tf2._rotation10 * boxToClosestPointInBoxX + tf2._rotation11 * boxToClosestPointInBoxY + tf2._rotation12 * boxToClosestPointInBoxZ; + __tmp__Z = tf2._rotation20 * boxToClosestPointInBoxX + tf2._rotation21 * boxToClosestPointInBoxY + tf2._rotation22 * boxToClosestPointInBoxZ; + boxToClosestPointX = __tmp__X; + boxToClosestPointY = __tmp__Y; + boxToClosestPointZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * normalInBoxX + tf2._rotation01 * normalInBoxY + tf2._rotation02 * normalInBoxZ; + __tmp__Y1 = tf2._rotation10 * normalInBoxX + tf2._rotation11 * normalInBoxY + tf2._rotation12 * normalInBoxZ; + __tmp__Z1 = tf2._rotation20 * normalInBoxX + tf2._rotation21 * normalInBoxY + tf2._rotation22 * normalInBoxZ; + normalX = __tmp__X1; + normalY = __tmp__Y1; + normalZ = __tmp__Z1; + this.setNormal(result,normalX,normalY,normalZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = tf1._positionX + normalX * -r; + pos1Y = tf1._positionY + normalY * -r; + pos1Z = tf1._positionZ + normalZ * -r; + pos2X = tf2._positionX + boxToClosestPointX; + pos2Y = tf2._positionY + boxToClosestPointY; + pos2Z = tf2._positionZ + boxToClosestPointZ; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,depth,0); + return; + } + let boxToClosestPointInBoxX; + let boxToClosestPointInBoxY; + let boxToClosestPointInBoxZ; + halfExtX -= 1e-9; + halfExtY -= 1e-9; + halfExtZ -= 1e-9; + negHalfExtX += 1e-9; + negHalfExtY += 1e-9; + negHalfExtZ += 1e-9; + boxToClosestPointInBoxX = boxToSphereInBoxX < halfExtX ? boxToSphereInBoxX : halfExtX; + boxToClosestPointInBoxY = boxToSphereInBoxY < halfExtY ? boxToSphereInBoxY : halfExtY; + boxToClosestPointInBoxZ = boxToSphereInBoxZ < halfExtZ ? boxToSphereInBoxZ : halfExtZ; + if(!(boxToClosestPointInBoxX > negHalfExtX)) { + boxToClosestPointInBoxX = negHalfExtX; + } + if(!(boxToClosestPointInBoxY > negHalfExtY)) { + boxToClosestPointInBoxY = negHalfExtY; + } + if(!(boxToClosestPointInBoxZ > negHalfExtZ)) { + boxToClosestPointInBoxZ = negHalfExtZ; + } + let closestPointToSphereInBoxX; + let closestPointToSphereInBoxY; + let closestPointToSphereInBoxZ; + closestPointToSphereInBoxX = boxToSphereInBoxX - boxToClosestPointInBoxX; + closestPointToSphereInBoxY = boxToSphereInBoxY - boxToClosestPointInBoxY; + closestPointToSphereInBoxZ = boxToSphereInBoxZ - boxToClosestPointInBoxZ; + let dist = closestPointToSphereInBoxX * closestPointToSphereInBoxX + closestPointToSphereInBoxY * closestPointToSphereInBoxY + closestPointToSphereInBoxZ * closestPointToSphereInBoxZ; + if(dist >= r * r) { + return; + } + dist = Math.sqrt(dist); + let boxToClosestPointX; + let boxToClosestPointY; + let boxToClosestPointZ; + let closestPointToSphereX; + let closestPointToSphereY; + let closestPointToSphereZ; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * boxToClosestPointInBoxX + tf2._rotation01 * boxToClosestPointInBoxY + tf2._rotation02 * boxToClosestPointInBoxZ; + __tmp__Y1 = tf2._rotation10 * boxToClosestPointInBoxX + tf2._rotation11 * boxToClosestPointInBoxY + tf2._rotation12 * boxToClosestPointInBoxZ; + __tmp__Z1 = tf2._rotation20 * boxToClosestPointInBoxX + tf2._rotation21 * boxToClosestPointInBoxY + tf2._rotation22 * boxToClosestPointInBoxZ; + boxToClosestPointX = __tmp__X1; + boxToClosestPointY = __tmp__Y1; + boxToClosestPointZ = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf2._rotation00 * closestPointToSphereInBoxX + tf2._rotation01 * closestPointToSphereInBoxY + tf2._rotation02 * closestPointToSphereInBoxZ; + __tmp__Y2 = tf2._rotation10 * closestPointToSphereInBoxX + tf2._rotation11 * closestPointToSphereInBoxY + tf2._rotation12 * closestPointToSphereInBoxZ; + __tmp__Z2 = tf2._rotation20 * closestPointToSphereInBoxX + tf2._rotation21 * closestPointToSphereInBoxY + tf2._rotation22 * closestPointToSphereInBoxZ; + closestPointToSphereX = __tmp__X2; + closestPointToSphereY = __tmp__Y2; + closestPointToSphereZ = __tmp__Z2; + let normalX; + let normalY; + let normalZ; + let l = closestPointToSphereX * closestPointToSphereX + closestPointToSphereY * closestPointToSphereY + closestPointToSphereZ * closestPointToSphereZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + normalX = closestPointToSphereX * l; + normalY = closestPointToSphereY * l; + normalZ = closestPointToSphereZ * l; + this.setNormal(result,normalX,normalY,normalZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = tf1._positionX + normalX * -r; + pos1Y = tf1._positionY + normalY * -r; + pos1Z = tf1._positionZ + normalZ * -r; + pos2X = tf2._positionX + boxToClosestPointX; + pos2Y = tf2._positionY + boxToClosestPointY; + pos2Z = tf2._positionZ + boxToClosestPointZ; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r - dist,0); + } +} +oimo.collision.narrowphase.detector.SphereCapsuleDetector = class oimo_collision_narrowphase_detector_SphereCapsuleDetector extends oimo.collision.narrowphase.detector.Detector { + constructor(swapped) { + super(swapped); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let c2 = geom2; + result.incremental = false; + let hh2 = c2._halfHeight; + let r1 = geom1._radius; + let r2 = c2._radius; + let axis2X; + let axis2Y; + let axis2Z; + axis2X = tf2._rotation01; + axis2Y = tf2._rotation11; + axis2Z = tf2._rotation21; + let cp1X; + let cp1Y; + let cp1Z; + cp1X = tf1._positionX; + cp1Y = tf1._positionY; + cp1Z = tf1._positionZ; + let p2X; + let p2Y; + let p2Z; + let q2X; + let q2Y; + let q2Z; + p2X = tf2._positionX + axis2X * -hh2; + p2Y = tf2._positionY + axis2Y * -hh2; + p2Z = tf2._positionZ + axis2Z * -hh2; + q2X = tf2._positionX + axis2X * hh2; + q2Y = tf2._positionY + axis2Y * hh2; + q2Z = tf2._positionZ + axis2Z * hh2; + let p12X; + let p12Y; + let p12Z; + p12X = cp1X - p2X; + p12Y = cp1Y - p2Y; + p12Z = cp1Z - p2Z; + let d2X; + let d2Y; + let d2Z; + d2X = q2X - p2X; + d2Y = q2Y - p2Y; + d2Z = q2Z - p2Z; + let d22 = hh2 * hh2 * 4; + let t = p12X * d2X + p12Y * d2Y + p12Z * d2Z; + if(t < 0) { + t = 0; + } else if(t > d22) { + t = 1; + } else { + t /= d22; + } + let cp2X; + let cp2Y; + let cp2Z; + cp2X = p2X + d2X * t; + cp2Y = p2Y + d2Y * t; + cp2Z = p2Z + d2Z * t; + let dX; + let dY; + let dZ; + dX = cp1X - cp2X; + dY = cp1Y - cp2Y; + dZ = cp1Z - cp2Z; + let len2 = dX * dX + dY * dY + dZ * dZ; + if(len2 >= (r1 + r2) * (r1 + r2)) { + return; + } + let len = Math.sqrt(len2); + let nX; + let nY; + let nZ; + if(len > 0) { + nX = dX * (1 / len); + nY = dY * (1 / len); + nZ = dZ * (1 / len); + } else { + nX = 1; + nY = 0; + nZ = 0; + } + this.setNormal(result,nX,nY,nZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = cp1X + nX * -r1; + pos1Y = cp1Y + nY * -r1; + pos1Z = cp1Z + nZ * -r1; + pos2X = cp2X + nX * r2; + pos2Y = cp2Y + nY * r2; + pos2Z = cp2Z + nZ * r2; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r1 + r2 - len,0); + } +} +oimo.collision.narrowphase.detector.SphereSphereDetector = class oimo_collision_narrowphase_detector_SphereSphereDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + result.incremental = false; + let dX; + let dY; + let dZ; + dX = tf1._positionX - tf2._positionX; + dY = tf1._positionY - tf2._positionY; + dZ = tf1._positionZ - tf2._positionZ; + let r1 = geom1._radius; + let r2 = geom2._radius; + let len2 = dX * dX + dY * dY + dZ * dZ; + if(len2 >= (r1 + r2) * (r1 + r2)) { + return; + } + let len = Math.sqrt(len2); + let nX; + let nY; + let nZ; + if(len > 0) { + nX = dX * (1 / len); + nY = dY * (1 / len); + nZ = dZ * (1 / len); + } else { + nX = 1; + nY = 0; + nZ = 0; + } + this.setNormal(result,nX,nY,nZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = tf1._positionX + nX * -r1; + pos1Y = tf1._positionY + nY * -r1; + pos1Z = tf1._positionZ + nZ * -r1; + pos2X = tf2._positionX + nX * r2; + pos2Y = tf2._positionY + nY * r2; + pos2Z = tf2._positionZ + nZ * r2; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r1 + r2 - len,0); + } +} +if(!oimo.collision.narrowphase.detector.gjkepa) oimo.collision.narrowphase.detector.gjkepa = {}; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedron = class oimo_collision_narrowphase_detector_gjkepa_EpaPolyhedron { + constructor() { + this._vertices = new Array(oimo.common.Setting.maxEPAVertices); + this._center = new oimo.common.Vec3(); + this._numVertices = 0; + this._triangleList = null; + this._triangleListLast = null; + this._numTriangles = 0; + this._trianglePool = null; + this._vertexPool = null; + } + dumpHoleEdge(first) { + } + validate() { + let t = this._triangleList; + while(t != null) { + t._vertices[0]._tmpEdgeLoopOuterTriangle = null; + t._vertices[0]._tmpEdgeLoopNext = null; + if(t._adjacentPairIndex[0] == -1) { + this._status = 2; + return false; + } + if(t._adjacentTriangles[0] == null) { + this._status = 3; + return false; + } + t._vertices[1]._tmpEdgeLoopOuterTriangle = null; + t._vertices[1]._tmpEdgeLoopNext = null; + if(t._adjacentPairIndex[1] == -1) { + this._status = 2; + return false; + } + if(t._adjacentTriangles[1] == null) { + this._status = 3; + return false; + } + t._vertices[2]._tmpEdgeLoopOuterTriangle = null; + t._vertices[2]._tmpEdgeLoopNext = null; + if(t._adjacentPairIndex[2] == -1) { + this._status = 2; + return false; + } + if(t._adjacentTriangles[2] == null) { + this._status = 3; + return false; + } + t = t._next; + } + return true; + } + findEdgeLoop(id,base,from) { + if(base._tmpDfsId == id) { + return; + } + base._tmpDfsId = id; + let _this = base.tmp; + _this.x = from.x; + _this.y = from.y; + _this.z = from.z; + let v = base._vertices[0].v; + _this.x -= v.x; + _this.y -= v.y; + _this.z -= v.z; + let _this1 = base.tmp; + let v1 = base._normal; + base._tmpDfsVisible = _this1.x * v1.x + _this1.y * v1.y + _this1.z * v1.z > 0; + if(!base._tmpDfsVisible) { + this._status = 6; + return; + } + let _g = 0; + while(_g < 3) { + let i = _g++; + let t = base._adjacentTriangles[i]; + if(t == null) { + continue; + } + let _this = t.tmp; + _this.x = from.x; + _this.y = from.y; + _this.z = from.z; + let v = t._vertices[0].v; + _this.x -= v.x; + _this.y -= v.y; + _this.z -= v.z; + let _this1 = t.tmp; + let v1 = t._normal; + t._tmpDfsVisible = _this1.x * v1.x + _this1.y * v1.y + _this1.z * v1.z > 0; + if(t._tmpDfsVisible) { + this.findEdgeLoop(id,t,from); + } else { + let v1 = base._vertices[i]; + v1._tmpEdgeLoopNext = base._vertices[base._nextIndex[i]]; + v1._tmpEdgeLoopOuterTriangle = t; + } + } + let triangle = base._adjacentTriangles[0]; + if(triangle != null) { + let pairIndex = base._adjacentPairIndex[0]; + triangle._adjacentTriangles[pairIndex] = null; + triangle._adjacentPairIndex[pairIndex] = -1; + base._adjacentTriangles[0] = null; + base._adjacentPairIndex[0] = -1; + } + let triangle1 = base._adjacentTriangles[1]; + if(triangle1 != null) { + let pairIndex = base._adjacentPairIndex[1]; + triangle1._adjacentTriangles[pairIndex] = null; + triangle1._adjacentPairIndex[pairIndex] = -1; + base._adjacentTriangles[1] = null; + base._adjacentPairIndex[1] = -1; + } + let triangle2 = base._adjacentTriangles[2]; + if(triangle2 != null) { + let pairIndex = base._adjacentPairIndex[2]; + triangle2._adjacentTriangles[pairIndex] = null; + triangle2._adjacentPairIndex[pairIndex] = -1; + base._adjacentTriangles[2] = null; + base._adjacentPairIndex[2] = -1; + } + this._numTriangles--; + let prev = base._prev; + let next = base._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(base == this._triangleList) { + this._triangleList = this._triangleList._next; + } + if(base == this._triangleListLast) { + this._triangleListLast = this._triangleListLast._prev; + } + base._next = null; + base._prev = null; + base.removeReferences(); + base._next = this._trianglePool; + this._trianglePool = base; + } + _init(v1,v2,v3,v4) { + this._status = 0; + this._numVertices = 4; + this._vertices[0] = v1; + this._vertices[1] = v2; + this._vertices[2] = v3; + this._vertices[3] = v4; + let _this = this._center; + let v = v1.v; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let v5 = v2.v; + _this.x += v5.x; + _this.y += v5.y; + _this.z += v5.z; + let v6 = v3.v; + _this.x += v6.x; + _this.y += v6.y; + _this.z += v6.z; + let v7 = v4.v; + _this.x += v7.x; + _this.y += v7.y; + _this.z += v7.z; + _this.x *= 0.25; + _this.y *= 0.25; + _this.z *= 0.25; + let first = this._trianglePool; + if(first != null) { + this._trianglePool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t1 = first; + let first1 = this._trianglePool; + if(first1 != null) { + this._trianglePool = first1._next; + first1._next = null; + } else { + first1 = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t2 = first1; + let first2 = this._trianglePool; + if(first2 != null) { + this._trianglePool = first2._next; + first2._next = null; + } else { + first2 = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t3 = first2; + let first3 = this._trianglePool; + if(first3 != null) { + this._trianglePool = first3._next; + first3._next = null; + } else { + first3 = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t4 = first3; + if(!t1.init(v1,v2,v3,this._center,true)) { + this._status = 1; + } + if(!t2.init(v1,v2,v4,this._center,true)) { + this._status = 1; + } + if(!t3.init(v1,v3,v4,this._center,true)) { + this._status = 1; + } + if(!t4.init(v2,v3,v4,this._center,true)) { + this._status = 1; + } + if(!t1.setAdjacentTriangle(t2)) { + this._status = 1; + } + if(!t1.setAdjacentTriangle(t3)) { + this._status = 1; + } + if(!t1.setAdjacentTriangle(t4)) { + this._status = 1; + } + if(!t2.setAdjacentTriangle(t3)) { + this._status = 1; + } + if(!t2.setAdjacentTriangle(t4)) { + this._status = 1; + } + if(!t3.setAdjacentTriangle(t4)) { + this._status = 1; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t1; + this._triangleListLast = t1; + } else { + this._triangleListLast._next = t1; + t1._prev = this._triangleListLast; + this._triangleListLast = t1; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t2; + this._triangleListLast = t2; + } else { + this._triangleListLast._next = t2; + t2._prev = this._triangleListLast; + this._triangleListLast = t2; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t3; + this._triangleListLast = t3; + } else { + this._triangleListLast._next = t3; + t3._prev = this._triangleListLast; + this._triangleListLast = t3; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t4; + this._triangleListLast = t4; + } else { + this._triangleListLast._next = t4; + t4._prev = this._triangleListLast; + this._triangleListLast = t4; + } + return this._status == 0; + } + _addVertex(vertex,base) { + this._vertices[this._numVertices++] = vertex; + let v1 = base._vertices[0]; + this.findEdgeLoop(this._numVertices,base,vertex.v); + if(this._status != 0) { + return false; + } + let v = v1; + let prevT = null; + let firstT = null; + while(true) { + if(v._tmpEdgeLoopNext == null) { + this._dumpAsObjModel(); + this._status = 4; + return false; + } + if(v._tmpEdgeLoopOuterTriangle == null) { + this._status = 5; + return false; + } + let first = this._trianglePool; + if(first != null) { + this._trianglePool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t = first; + if(firstT == null) { + firstT = t; + } + if(!t.init(v,v._tmpEdgeLoopNext,vertex,this._center,false)) { + this._status = 1; + } + if(this._status != 0) { + return false; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t; + this._triangleListLast = t; + } else { + this._triangleListLast._next = t; + t._prev = this._triangleListLast; + this._triangleListLast = t; + } + if(!t.setAdjacentTriangle(v._tmpEdgeLoopOuterTriangle)) { + this._status = 1; + } + if(prevT != null) { + if(!t.setAdjacentTriangle(prevT)) { + this._status = 1; + } + } + prevT = t; + v = v._tmpEdgeLoopNext; + if(!(v != v1)) { + break; + } + } + if(!prevT.setAdjacentTriangle(firstT)) { + this._status = 1; + } + if(this._status == 0) { + return this.validate(); + } else { + return false; + } + } + _dumpAsObjModel() { + } +} +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState = class oimo_collision_narrowphase_detector_gjkepa_EpaPolyhedronState { +} +oimo.collision.narrowphase.detector.gjkepa.EpaTriangle = class oimo_collision_narrowphase_detector_gjkepa_EpaTriangle { + constructor() { + this.id = ++oimo.collision.narrowphase.detector.gjkepa.EpaTriangle.count; + this._next = null; + this._prev = null; + this._normal = new oimo.common.Vec3(); + this._distanceSq = 0; + this._tmpDfsId = 0; + this._tmpDfsVisible = false; + this._vertices = new Array(3); + this._adjacentTriangles = new Array(3); + this._adjacentPairIndex = new Array(3); + this.tmp = new oimo.common.Vec3(); + this._nextIndex = new Array(3); + this._nextIndex[0] = 1; + this._nextIndex[1] = 2; + this._nextIndex[2] = 0; + } + init(vertex1,vertex2,vertex3,center,autoCheck) { + if(autoCheck == null) { + autoCheck = false; + } + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let vcX; + let vcY; + let vcZ; + let v = vertex1.v; + v1X = v.x; + v1Y = v.y; + v1Z = v.z; + let v1 = vertex2.v; + v2X = v1.x; + v2Y = v1.y; + v2Z = v1.z; + let v2 = vertex3.v; + v3X = v2.x; + v3Y = v2.y; + v3Z = v2.z; + vcX = center.x; + vcY = center.y; + vcZ = center.z; + let v12X; + let v12Y; + let v12Z; + let v13X; + let v13Y; + let v13Z; + let vc1X; + let vc1Y; + let vc1Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v13X = v3X - v1X; + v13Y = v3Y - v1Y; + v13Z = v3Z - v1Z; + vc1X = v1X - vcX; + vc1Y = v1Y - vcY; + vc1Z = v1Z - vcZ; + let inorX; + let inorY; + let inorZ; + inorX = v12Y * v13Z - v12Z * v13Y; + inorY = v12Z * v13X - v12X * v13Z; + inorZ = v12X * v13Y - v12Y * v13X; + let inverted = false; + if(vc1X * inorX + vc1Y * inorY + vc1Z * inorZ < 0) { + if(autoCheck) { + let tmp = vertex2; + vertex2 = vertex3; + vertex3 = tmp; + inorX *= -1; + inorY *= -1; + inorZ *= -1; + } else { + inverted = true; + } + } + this._vertices[0] = vertex1; + this._vertices[1] = vertex2; + this._vertices[2] = vertex3; + let v3 = this._normal; + v3.x = inorX; + v3.y = inorY; + v3.z = inorZ; + let vec1 = vertex1.v; + let vec2 = vertex2.v; + let vec3 = vertex3.v; + let out = this.tmp; + let v1X1; + let v1Y1; + let v1Z1; + let v2X1; + let v2Y1; + let v2Z1; + let v3X1; + let v3Y1; + let v3Z1; + let v12X1; + let v12Y1; + let v12Z1; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X1 = vec1.x; + v1Y1 = vec1.y; + v1Z1 = vec1.z; + v2X1 = vec2.x; + v2Y1 = vec2.y; + v2Z1 = vec2.z; + v3X1 = vec3.x; + v3Y1 = vec3.y; + v3Z1 = vec3.z; + v12X1 = v2X1 - v1X1; + v12Y1 = v2Y1 - v1Y1; + v12Z1 = v2Z1 - v1Z1; + v23X = v3X1 - v2X1; + v23Y = v3Y1 - v2Y1; + v23Z = v3Z1 - v2Z1; + v31X = v1X1 - v3X1; + v31Y = v1Y1 - v3Y1; + v31Z = v1Z1 - v3Z1; + let nX; + let nY; + let nZ; + nX = v12Y1 * v23Z - v12Z1 * v23Y; + nY = v12Z1 * v23X - v12X1 * v23Z; + nZ = v12X1 * v23Y - v12Y1 * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y1 * nZ - v12Z1 * nY; + n12Y = v12Z1 * nX - v12X1 * nZ; + n12Z = v12X1 * nY - v12Y1 * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X1 * n12X + v1Y1 * n12Y + v1Z1 * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + } + mind = out.x * out.x + out.y * out.y + out.z * out.z; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + if(v2X1 * n23X + v2Y1 * n23Y + v2Z1 * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(v3X1 * n31X + v3Y1 * n31Y + v3Z1 * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(mind > 0) { + out.x = minvX; + out.y = minvY; + out.z = minvZ; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X1 * nX + v1Y1 * nY + v1Z1 * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + out.x = minvX; + out.y = minvY; + out.z = minvZ; + } + let _this = this.tmp; + this._distanceSq = _this.x * _this.x + _this.y * _this.y + _this.z * _this.z; + this._adjacentTriangles[0] = null; + this._adjacentTriangles[1] = null; + this._adjacentTriangles[2] = null; + this._adjacentPairIndex[0] = -1; + this._adjacentPairIndex[1] = -1; + this._adjacentPairIndex[2] = -1; + return !inverted; + } + setAdjacentTriangle(triangle) { + let count = 0; + if(this._vertices[0] == triangle._vertices[this._nextIndex[0]] && this._vertices[this._nextIndex[0]] == triangle._vertices[0]) { + this._adjacentTriangles[0] = triangle; + this._adjacentPairIndex[0] = 0; + triangle._adjacentTriangles[0] = this; + triangle._adjacentPairIndex[0] = 0; + count = 1; + } + if(this._vertices[0] == triangle._vertices[this._nextIndex[1]] && this._vertices[this._nextIndex[0]] == triangle._vertices[1]) { + this._adjacentTriangles[0] = triangle; + this._adjacentPairIndex[0] = 1; + triangle._adjacentTriangles[1] = this; + triangle._adjacentPairIndex[1] = 0; + ++count; + } + if(this._vertices[0] == triangle._vertices[this._nextIndex[2]] && this._vertices[this._nextIndex[0]] == triangle._vertices[2]) { + this._adjacentTriangles[0] = triangle; + this._adjacentPairIndex[0] = 2; + triangle._adjacentTriangles[2] = this; + triangle._adjacentPairIndex[2] = 0; + ++count; + } + if(this._vertices[1] == triangle._vertices[this._nextIndex[0]] && this._vertices[this._nextIndex[1]] == triangle._vertices[0]) { + this._adjacentTriangles[1] = triangle; + this._adjacentPairIndex[1] = 0; + triangle._adjacentTriangles[0] = this; + triangle._adjacentPairIndex[0] = 1; + ++count; + } + if(this._vertices[1] == triangle._vertices[this._nextIndex[1]] && this._vertices[this._nextIndex[1]] == triangle._vertices[1]) { + this._adjacentTriangles[1] = triangle; + this._adjacentPairIndex[1] = 1; + triangle._adjacentTriangles[1] = this; + triangle._adjacentPairIndex[1] = 1; + ++count; + } + if(this._vertices[1] == triangle._vertices[this._nextIndex[2]] && this._vertices[this._nextIndex[1]] == triangle._vertices[2]) { + this._adjacentTriangles[1] = triangle; + this._adjacentPairIndex[1] = 2; + triangle._adjacentTriangles[2] = this; + triangle._adjacentPairIndex[2] = 1; + ++count; + } + if(this._vertices[2] == triangle._vertices[this._nextIndex[0]] && this._vertices[this._nextIndex[2]] == triangle._vertices[0]) { + this._adjacentTriangles[2] = triangle; + this._adjacentPairIndex[2] = 0; + triangle._adjacentTriangles[0] = this; + triangle._adjacentPairIndex[0] = 2; + ++count; + } + if(this._vertices[2] == triangle._vertices[this._nextIndex[1]] && this._vertices[this._nextIndex[2]] == triangle._vertices[1]) { + this._adjacentTriangles[2] = triangle; + this._adjacentPairIndex[2] = 1; + triangle._adjacentTriangles[1] = this; + triangle._adjacentPairIndex[1] = 2; + ++count; + } + if(this._vertices[2] == triangle._vertices[this._nextIndex[2]] && this._vertices[this._nextIndex[2]] == triangle._vertices[2]) { + this._adjacentTriangles[2] = triangle; + this._adjacentPairIndex[2] = 2; + triangle._adjacentTriangles[2] = this; + triangle._adjacentPairIndex[2] = 2; + ++count; + } + if(count != 1) { + return false; + } + return true; + } + removeAdjacentTriangles() { + let triangle = this._adjacentTriangles[0]; + if(triangle != null) { + let pairIndex = this._adjacentPairIndex[0]; + triangle._adjacentTriangles[pairIndex] = null; + triangle._adjacentPairIndex[pairIndex] = -1; + this._adjacentTriangles[0] = null; + this._adjacentPairIndex[0] = -1; + } + let triangle1 = this._adjacentTriangles[1]; + if(triangle1 != null) { + let pairIndex = this._adjacentPairIndex[1]; + triangle1._adjacentTriangles[pairIndex] = null; + triangle1._adjacentPairIndex[pairIndex] = -1; + this._adjacentTriangles[1] = null; + this._adjacentPairIndex[1] = -1; + } + let triangle2 = this._adjacentTriangles[2]; + if(triangle2 != null) { + let pairIndex = this._adjacentPairIndex[2]; + triangle2._adjacentTriangles[pairIndex] = null; + triangle2._adjacentPairIndex[pairIndex] = -1; + this._adjacentTriangles[2] = null; + this._adjacentPairIndex[2] = -1; + } + } + removeReferences() { + this._next = null; + this._prev = null; + this._tmpDfsId = 0; + this._tmpDfsVisible = false; + this._distanceSq = 0; + this._vertices[0] = null; + this._vertices[1] = null; + this._vertices[2] = null; + this._adjacentTriangles[0] = null; + this._adjacentTriangles[1] = null; + this._adjacentTriangles[2] = null; + this._adjacentPairIndex[0] = 0; + this._adjacentPairIndex[1] = 0; + this._adjacentPairIndex[2] = 0; + } + dump() { + } +} +oimo.collision.narrowphase.detector.gjkepa.EpaVertex = class oimo_collision_narrowphase_detector_gjkepa_EpaVertex { + constructor() { + this.randId = Math.random() * 100000 | 0; + this.v = new oimo.common.Vec3(); + this.w1 = new oimo.common.Vec3(); + this.w2 = new oimo.common.Vec3(); + } + init(v,w1,w2) { + let _this = this.v; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.w1; + _this1.x = w1.x; + _this1.y = w1.y; + _this1.z = w1.z; + let _this2 = this.w2; + _this2.x = w2.x; + _this2.y = w2.y; + _this2.z = w2.z; + this._next = null; + this._tmpEdgeLoopNext = null; + this._tmpEdgeLoopOuterTriangle = null; + return this; + } + removeReferences() { + this._next = null; + this._tmpEdgeLoopNext = null; + this._tmpEdgeLoopOuterTriangle = null; + } +} +oimo.collision.narrowphase.detector.gjkepa.GjkCache = class oimo_collision_narrowphase_detector_gjkepa_GjkCache { + constructor() { + this.prevClosestDir = new oimo.common.Vec3(); + } + clear() { + this.prevClosestDir.zero(); + } +} +if(!oimo.common) oimo.common = {}; +oimo.common.Vec3 = class oimo_common_Vec3 { + constructor(x,y,z) { + if(z == null) { + z = 0; + } + if(y == null) { + y = 0; + } + if(x == null) { + x = 0; + } + this.x = x; + this.y = y; + this.z = z; + oimo.common.Vec3.numCreations++; + } + init(x,y,z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + zero() { + this.x = 0; + this.y = 0; + this.z = 0; + return this; + } + add(v) { + return new oimo.common.Vec3(this.x + v.x,this.y + v.y,this.z + v.z); + } + add3(vx,vy,vz) { + return new oimo.common.Vec3(this.x + vx,this.y + vy,this.z + vz); + } + addScaled(v,s) { + return new oimo.common.Vec3(this.x + v.x * s,this.y + v.y * s,this.z + v.z * s); + } + sub(v) { + return new oimo.common.Vec3(this.x - v.x,this.y - v.y,this.z - v.z); + } + sub3(vx,vy,vz) { + return new oimo.common.Vec3(this.x - vx,this.y - vy,this.z - vz); + } + scale(s) { + return new oimo.common.Vec3(this.x * s,this.y * s,this.z * s); + } + scale3(sx,sy,sz) { + return new oimo.common.Vec3(this.x * sx,this.y * sy,this.z * sz); + } + dot(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; + } + cross(v) { + return new oimo.common.Vec3(this.y * v.z - this.z * v.y,this.z * v.x - this.x * v.z,this.x * v.y - this.y * v.x); + } + addEq(v) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + return this; + } + add3Eq(vx,vy,vz) { + this.x += vx; + this.y += vy; + this.z += vz; + return this; + } + addScaledEq(v,s) { + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + return this; + } + subEq(v) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + return this; + } + sub3Eq(vx,vy,vz) { + this.x -= vx; + this.y -= vy; + this.z -= vz; + return this; + } + scaleEq(s) { + this.x *= s; + this.y *= s; + this.z *= s; + return this; + } + scale3Eq(sx,sy,sz) { + this.x *= sx; + this.y *= sy; + this.z *= sz; + return this; + } + crossEq(v) { + let y = this.z * v.x - this.x * v.z; + let z = this.x * v.y - this.y * v.x; + this.x = this.y * v.z - this.z * v.y; + this.y = y; + this.z = z; + return this; + } + mulMat3(m) { + return new oimo.common.Vec3(this.x * m.e00 + this.y * m.e01 + this.z * m.e02,this.x * m.e10 + this.y * m.e11 + this.z * m.e12,this.x * m.e20 + this.y * m.e21 + this.z * m.e22); + } + mulMat4(m) { + return new oimo.common.Vec3(this.x * m.e00 + this.y * m.e01 + this.z * m.e02 + m.e03,this.x * m.e10 + this.y * m.e11 + this.z * m.e12 + m.e13,this.x * m.e20 + this.y * m.e21 + this.z * m.e22 + m.e23); + } + mulTransform(tf) { + let vX; + let vY; + let vZ; + vX = this.x; + vY = this.y; + vZ = this.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * vX + tf._rotation01 * vY + tf._rotation02 * vZ; + __tmp__Y = tf._rotation10 * vX + tf._rotation11 * vY + tf._rotation12 * vZ; + __tmp__Z = tf._rotation20 * vX + tf._rotation21 * vY + tf._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += tf._positionX; + vY += tf._positionY; + vZ += tf._positionZ; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + mulMat3Eq(m) { + let y = this.x * m.e10 + this.y * m.e11 + this.z * m.e12; + let z = this.x * m.e20 + this.y * m.e21 + this.z * m.e22; + this.x = this.x * m.e00 + this.y * m.e01 + this.z * m.e02; + this.y = y; + this.z = z; + return this; + } + mulMat4Eq(m) { + let y = this.x * m.e10 + this.y * m.e11 + this.z * m.e12 + m.e13; + let z = this.x * m.e20 + this.y * m.e21 + this.z * m.e22 + m.e23; + this.x = this.x * m.e00 + this.y * m.e01 + this.z * m.e02 + m.e03; + this.y = y; + this.z = z; + return this; + } + mulTransformEq(tf) { + let vX; + let vY; + let vZ; + vX = this.x; + vY = this.y; + vZ = this.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * vX + tf._rotation01 * vY + tf._rotation02 * vZ; + __tmp__Y = tf._rotation10 * vX + tf._rotation11 * vY + tf._rotation12 * vZ; + __tmp__Z = tf._rotation20 * vX + tf._rotation21 * vY + tf._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += tf._positionX; + vY += tf._positionY; + vZ += tf._positionZ; + this.x = vX; + this.y = vY; + this.z = vZ; + return this; + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + lengthSq() { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + normalized() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + return new oimo.common.Vec3(this.x * invLen,this.y * invLen,this.z * invLen); + } + normalize() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + this.x *= invLen; + this.y *= invLen; + this.z *= invLen; + return this; + } + negate() { + return new oimo.common.Vec3(-this.x,-this.y,-this.z); + } + negateEq() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + return this; + } + copyFrom(v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + return this; + } + clone() { + return new oimo.common.Vec3(this.x,this.y,this.z); + } + toString() { + return "Vec3[" + (this.x > 0 ? (this.x * 10000000 + 0.5 | 0) / 10000000 : (this.x * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.y > 0 ? (this.y * 10000000 + 0.5 | 0) / 10000000 : (this.y * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.z > 0 ? (this.z * 10000000 + 0.5 | 0) / 10000000 : (this.z * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +oimo.common.Transform = class oimo_common_Transform { + constructor() { + this._positionX = 0; + this._positionY = 0; + this._positionZ = 0; + this._rotation00 = 1; + this._rotation01 = 0; + this._rotation02 = 0; + this._rotation10 = 0; + this._rotation11 = 1; + this._rotation12 = 0; + this._rotation20 = 0; + this._rotation21 = 0; + this._rotation22 = 1; + } + identity() { + this._positionX = 0; + this._positionY = 0; + this._positionZ = 0; + this._rotation00 = 1; + this._rotation01 = 0; + this._rotation02 = 0; + this._rotation10 = 0; + this._rotation11 = 1; + this._rotation12 = 0; + this._rotation20 = 0; + this._rotation21 = 0; + this._rotation22 = 1; + return this; + } + getPosition() { + let position = new oimo.common.Vec3(); + position.x = this._positionX; + position.y = this._positionY; + position.z = this._positionZ; + return position; + } + getPositionTo(position) { + position.x = this._positionX; + position.y = this._positionY; + position.z = this._positionZ; + } + setPosition(position) { + this._positionX = position.x; + this._positionY = position.y; + this._positionZ = position.z; + return this; + } + translate(translation) { + let diffX; + let diffY; + let diffZ; + diffX = translation.x; + diffY = translation.y; + diffZ = translation.z; + this._positionX += diffX; + this._positionY += diffY; + this._positionZ += diffZ; + } + getRotation() { + let rotation = new oimo.common.Mat3(); + rotation.e00 = this._rotation00; + rotation.e01 = this._rotation01; + rotation.e02 = this._rotation02; + rotation.e10 = this._rotation10; + rotation.e11 = this._rotation11; + rotation.e12 = this._rotation12; + rotation.e20 = this._rotation20; + rotation.e21 = this._rotation21; + rotation.e22 = this._rotation22; + return rotation; + } + getRotationTo(out) { + out.e00 = this._rotation00; + out.e01 = this._rotation01; + out.e02 = this._rotation02; + out.e10 = this._rotation10; + out.e11 = this._rotation11; + out.e12 = this._rotation12; + out.e20 = this._rotation20; + out.e21 = this._rotation21; + out.e22 = this._rotation22; + } + setRotation(rotation) { + this._rotation00 = rotation.e00; + this._rotation01 = rotation.e01; + this._rotation02 = rotation.e02; + this._rotation10 = rotation.e10; + this._rotation11 = rotation.e11; + this._rotation12 = rotation.e12; + this._rotation20 = rotation.e20; + this._rotation21 = rotation.e21; + this._rotation22 = rotation.e22; + return this; + } + setRotationXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + this._rotation00 = cy * cz; + this._rotation01 = -cy * sz; + this._rotation02 = sy; + this._rotation10 = cx * sz + cz * sx * sy; + this._rotation11 = cx * cz - sx * sy * sz; + this._rotation12 = -cy * sx; + this._rotation20 = sx * sz - cx * cz * sy; + this._rotation21 = cz * sx + cx * sy * sz; + this._rotation22 = cx * cy; + } + rotate(rotation) { + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + rot00 = rotation.e00; + rot01 = rotation.e01; + rot02 = rotation.e02; + rot10 = rotation.e10; + rot11 = rotation.e11; + rot12 = rotation.e12; + rot20 = rotation.e20; + rot21 = rotation.e21; + rot22 = rotation.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._rotation00 + rot01 * this._rotation10 + rot02 * this._rotation20; + __tmp__01 = rot00 * this._rotation01 + rot01 * this._rotation11 + rot02 * this._rotation21; + __tmp__02 = rot00 * this._rotation02 + rot01 * this._rotation12 + rot02 * this._rotation22; + __tmp__10 = rot10 * this._rotation00 + rot11 * this._rotation10 + rot12 * this._rotation20; + __tmp__11 = rot10 * this._rotation01 + rot11 * this._rotation11 + rot12 * this._rotation21; + __tmp__12 = rot10 * this._rotation02 + rot11 * this._rotation12 + rot12 * this._rotation22; + __tmp__20 = rot20 * this._rotation00 + rot21 * this._rotation10 + rot22 * this._rotation20; + __tmp__21 = rot20 * this._rotation01 + rot21 * this._rotation11 + rot22 * this._rotation21; + __tmp__22 = rot20 * this._rotation02 + rot21 * this._rotation12 + rot22 * this._rotation22; + this._rotation00 = __tmp__00; + this._rotation01 = __tmp__01; + this._rotation02 = __tmp__02; + this._rotation10 = __tmp__10; + this._rotation11 = __tmp__11; + this._rotation12 = __tmp__12; + this._rotation20 = __tmp__20; + this._rotation21 = __tmp__21; + this._rotation22 = __tmp__22; + } + rotateXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + rot00 = cy * cz; + rot01 = -cy * sz; + rot02 = sy; + rot10 = cx * sz + cz * sx * sy; + rot11 = cx * cz - sx * sy * sz; + rot12 = -cy * sx; + rot20 = sx * sz - cx * cz * sy; + rot21 = cz * sx + cx * sy * sz; + rot22 = cx * cy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._rotation00 + rot01 * this._rotation10 + rot02 * this._rotation20; + __tmp__01 = rot00 * this._rotation01 + rot01 * this._rotation11 + rot02 * this._rotation21; + __tmp__02 = rot00 * this._rotation02 + rot01 * this._rotation12 + rot02 * this._rotation22; + __tmp__10 = rot10 * this._rotation00 + rot11 * this._rotation10 + rot12 * this._rotation20; + __tmp__11 = rot10 * this._rotation01 + rot11 * this._rotation11 + rot12 * this._rotation21; + __tmp__12 = rot10 * this._rotation02 + rot11 * this._rotation12 + rot12 * this._rotation22; + __tmp__20 = rot20 * this._rotation00 + rot21 * this._rotation10 + rot22 * this._rotation20; + __tmp__21 = rot20 * this._rotation01 + rot21 * this._rotation11 + rot22 * this._rotation21; + __tmp__22 = rot20 * this._rotation02 + rot21 * this._rotation12 + rot22 * this._rotation22; + this._rotation00 = __tmp__00; + this._rotation01 = __tmp__01; + this._rotation02 = __tmp__02; + this._rotation10 = __tmp__10; + this._rotation11 = __tmp__11; + this._rotation12 = __tmp__12; + this._rotation20 = __tmp__20; + this._rotation21 = __tmp__21; + this._rotation22 = __tmp__22; + } + getOrientation() { + let q = new oimo.common.Quat(); + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._rotation00; + let e11 = this._rotation11; + let e22 = this._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation21 - this._rotation12) * s; + iqY = (this._rotation02 - this._rotation20) * s; + iqZ = (this._rotation10 - this._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation02 + this._rotation20) * s; + iqW = (this._rotation21 - this._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation02 - this._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + q.x = iqX; + q.y = iqY; + q.z = iqZ; + q.w = iqW; + return q; + } + getOrientationTo(orientation) { + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._rotation00; + let e11 = this._rotation11; + let e22 = this._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation21 - this._rotation12) * s; + iqY = (this._rotation02 - this._rotation20) * s; + iqZ = (this._rotation10 - this._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation02 + this._rotation20) * s; + iqW = (this._rotation21 - this._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation02 - this._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + orientation.x = iqX; + orientation.y = iqY; + orientation.z = iqZ; + orientation.w = iqW; + } + setOrientation(quaternion) { + let qX; + let qY; + let qZ; + let qW; + qX = quaternion.x; + qY = quaternion.y; + qZ = quaternion.z; + qW = quaternion.w; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._rotation00 = 1 - yy - zz; + this._rotation01 = xy - wz; + this._rotation02 = xz + wy; + this._rotation10 = xy + wz; + this._rotation11 = 1 - xx - zz; + this._rotation12 = yz - wx; + this._rotation20 = xz - wy; + this._rotation21 = yz + wx; + this._rotation22 = 1 - xx - yy; + return this; + } + clone() { + let tf = new oimo.common.Transform(); + tf._positionX = this._positionX; + tf._positionY = this._positionY; + tf._positionZ = this._positionZ; + tf._rotation00 = this._rotation00; + tf._rotation01 = this._rotation01; + tf._rotation02 = this._rotation02; + tf._rotation10 = this._rotation10; + tf._rotation11 = this._rotation11; + tf._rotation12 = this._rotation12; + tf._rotation20 = this._rotation20; + tf._rotation21 = this._rotation21; + tf._rotation22 = this._rotation22; + return tf; + } + copyFrom(transform) { + this._positionX = transform._positionX; + this._positionY = transform._positionY; + this._positionZ = transform._positionZ; + this._rotation00 = transform._rotation00; + this._rotation01 = transform._rotation01; + this._rotation02 = transform._rotation02; + this._rotation10 = transform._rotation10; + this._rotation11 = transform._rotation11; + this._rotation12 = transform._rotation12; + this._rotation20 = transform._rotation20; + this._rotation21 = transform._rotation21; + this._rotation22 = transform._rotation22; + return this; + } +} +oimo.common.Setting = class oimo_common_Setting { +} +oimo.collision.narrowphase.detector.gjkepa.GjkEpa = class oimo_collision_narrowphase_detector_gjkepa_GjkEpa { + constructor() { + this.s = new Array(4); + this.w1 = new Array(4); + this.w2 = new Array(4); + this.baseDirs = new Array(3); + this.baseDirs[0] = new oimo.common.Vec3(1,0,0); + this.baseDirs[1] = new oimo.common.Vec3(0,1,0); + this.baseDirs[2] = new oimo.common.Vec3(0,0,1); + this.tl1 = new oimo.common.Vec3(); + this.tl2 = new oimo.common.Vec3(); + this.rayX = new oimo.common.Vec3(); + this.rayR = new oimo.common.Vec3(); + this.tempTransform = new oimo.common.Transform(); + this.s[0] = new oimo.common.Vec3(); + this.w1[0] = new oimo.common.Vec3(); + this.w2[0] = new oimo.common.Vec3(); + this.s[1] = new oimo.common.Vec3(); + this.w1[1] = new oimo.common.Vec3(); + this.w2[1] = new oimo.common.Vec3(); + this.s[2] = new oimo.common.Vec3(); + this.w1[2] = new oimo.common.Vec3(); + this.w2[2] = new oimo.common.Vec3(); + this.s[3] = new oimo.common.Vec3(); + this.w1[3] = new oimo.common.Vec3(); + this.w2[3] = new oimo.common.Vec3(); + this.dir = new oimo.common.Vec3(); + this.closest = new oimo.common.Vec3(); + this.closestPoint1 = new oimo.common.Vec3(); + this.closestPoint2 = new oimo.common.Vec3(); + this.polyhedron = new oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedron(); + } + computeClosestPointsImpl(c1,c2,tf1,tf2,cache,useEpa) { + this.c1 = c1; + this.c2 = c2; + this.tf1 = tf1; + this.tf2 = tf2; + let s = this.s; + let w1 = this.w1; + let w2 = this.w2; + let closest = this.closest; + let dir = this.dir; + if(cache != null) { + if(cache._gjkCache == null) { + cache._gjkCache = new oimo.collision.narrowphase.detector.gjkepa.GjkCache(); + } + this.loadCache(cache._gjkCache); + } else { + dir.zero(); + } + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z == 0) { + let firstDirX; + let firstDirY; + let firstDirZ; + firstDirX = tf2._positionX - tf1._positionX; + firstDirY = tf2._positionY - tf1._positionY; + firstDirZ = tf2._positionZ - tf1._positionZ; + dir.x = firstDirX; + dir.y = firstDirY; + dir.z = firstDirZ; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-6) { + dir.init(1,0,0); + } + } + this.simplexSize = 0; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v = this.w1[this.simplexSize]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let v1 = this.w2[this.simplexSize]; + _this.x -= v1.x; + _this.y -= v1.y; + _this.z -= v1.z; + this.simplexSize = 1; + let count = 0; + while(count < 40) { + let v = 0; + switch(this.simplexSize) { + case 1: + let v1 = s[0]; + closest.x = v1.x; + closest.y = v1.y; + closest.z = v1.z; + v = 1; + break; + case 2: + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v2 = s[0]; + v1X = v2.x; + v1Y = v2.y; + v1Z = v2.z; + let v3 = s[1]; + v2X = v3.x; + v2Y = v3.y; + v2Z = v3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + v = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + v = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + v = 3; + } + break; + case 3: + let vec1 = s[0]; + let vec2 = s[1]; + let vec3 = s[2]; + let v1X1; + let v1Y1; + let v1Z1; + let v2X1; + let v2Y1; + let v2Z1; + let v3X; + let v3Y; + let v3Z; + let v12X1; + let v12Y1; + let v12Z1; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X1 = vec1.x; + v1Y1 = vec1.y; + v1Z1 = vec1.z; + v2X1 = vec2.x; + v2Y1 = vec2.y; + v2Z1 = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X1 = v2X1 - v1X1; + v12Y1 = v2Y1 - v1Y1; + v12Z1 = v2Z1 - v1Z1; + v23X = v3X - v2X1; + v23Y = v3Y - v2Y1; + v23Z = v3Z - v2Z1; + v31X = v1X1 - v3X; + v31Y = v1Y1 - v3Y; + v31Z = v1Z1 - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y1 * v23Z - v12Z1 * v23Y; + nY = v12Z1 * v23X - v12X1 * v23Z; + nZ = v12X1 * v23Y - v12Y1 * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y1 * nZ - v12Z1 * nY; + n12Y = v12Z1 * nX - v12X1 * nZ; + n12Z = v12X1 * nY - v12Y1 * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X1 * n12X + v1Y1 * n12Y + v1Z1 * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X1 * n23X + v2Y1 * n23Y + v2Z1 * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X1 * nX + v1Y1 * nY + v1Z1 * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = 7; + } + break; + case 4: + let vec11 = s[0]; + let vec21 = s[1]; + let vec31 = s[2]; + let vec4 = s[3]; + let v1X2; + let v1Y2; + let v1Z2; + let v2X2; + let v2Y2; + let v2Z2; + let v3X1; + let v3Y1; + let v3Z1; + let v4X; + let v4Y; + let v4Z; + let v12X2; + let v12Y2; + let v12Z2; + let v13X; + let v13Y; + let v13Z; + let v14X; + let v14Y; + let v14Z; + let v23X1; + let v23Y1; + let v23Z1; + let v24X; + let v24Y; + let v24Z; + v1X2 = vec11.x; + v1Y2 = vec11.y; + v1Z2 = vec11.z; + v2X2 = vec21.x; + v2Y2 = vec21.y; + v2Z2 = vec21.z; + v3X1 = vec31.x; + v3Y1 = vec31.y; + v3Z1 = vec31.z; + v4X = vec4.x; + v4Y = vec4.y; + v4Z = vec4.z; + v12X2 = v2X2 - v1X2; + v12Y2 = v2Y2 - v1Y2; + v12Z2 = v2Z2 - v1Z2; + v13X = v3X1 - v1X2; + v13Y = v3Y1 - v1Y2; + v13Z = v3Z1 - v1Z2; + v14X = v4X - v1X2; + v14Y = v4Y - v1Y2; + v14Z = v4Z - v1Z2; + v23X1 = v3X1 - v2X2; + v23Y1 = v3Y1 - v2Y2; + v23Z1 = v3Z1 - v2Z2; + v24X = v4X - v2X2; + v24Y = v4Y - v2Y2; + v24Z = v4Z - v2Z2; + let n123X; + let n123Y; + let n123Z; + let n134X; + let n134Y; + let n134Z; + let n142X; + let n142Y; + let n142Z; + let n243X; + let n243Y; + let n243Z; + n123X = v12Y2 * v13Z - v12Z2 * v13Y; + n123Y = v12Z2 * v13X - v12X2 * v13Z; + n123Z = v12X2 * v13Y - v12Y2 * v13X; + n134X = v13Y * v14Z - v13Z * v14Y; + n134Y = v13Z * v14X - v13X * v14Z; + n134Z = v13X * v14Y - v13Y * v14X; + n142X = v14Y * v12Z2 - v14Z * v12Y2; + n142Y = v14Z * v12X2 - v14X * v12Z2; + n142Z = v14X * v12Y2 - v14Y * v12X2; + n243X = v24Y * v23Z1 - v24Z * v23Y1; + n243Y = v24Z * v23X1 - v24X * v23Z1; + n243Z = v24X * v23Y1 - v24Y * v23X1; + let sign = v12X2 * n243X + v12Y2 * n243Y + v12Z2 * n243Z > 0 ? 1 : -1; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if((v1X2 * n123X + v1Y2 * n123Y + v1Z2 * n123Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec31.x; + v3Y = vec31.y; + v3Z = vec31.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + mini1 = b; + mind1 = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + if((v1X2 * n134X + v1Y2 * n134Y + v1Z2 * n134Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 6) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v1X2 * n142X + v1Y2 * n142Y + v1Z2 * n142Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 3 | (b & 4) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v2X2 * n243X + v2Y2 * n243Y + v2Z2 * n243Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if(mind1 > 0) { + closest.x = minvX1; + closest.y = minvY1; + closest.z = minvZ1; + v = mini1; + } else { + closest.zero(); + v = 15; + } + break; + } + if(closest.x * closest.x + closest.y * closest.y + closest.z * closest.z < 1e-008) { + if(!useEpa) { + this.distance = 0; + return 0; + } + switch(this.simplexSize) { + case 1: + this.pointToTetrahedron(); + break; + case 2: + this.lineToTetrahedron(); + break; + case 3: + this.triangleToTetrahedron(); + break; + } + if(this.simplexSize == 4) { + let epaState = this.computeDepth(c1,c2,tf1,tf2,s,w1,w2); + if(epaState != 0) { + this.distance = 0; + return epaState; + } + this.distance = -this.depth; + return 0; + } + this.distance = 0; + return 1; + } + this.shrinkSimplex(v); + dir.x = closest.x; + dir.y = closest.y; + dir.z = closest.z; + dir.x = -dir.x; + dir.y = -dir.y; + dir.z = -dir.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this.x = v4.x; + _this.y = v4.y; + _this.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this.x -= v5.x; + _this.y -= v5.y; + _this.z -= v5.z; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-008) { + throw new Error("!?"); + } + let _this1 = s[this.simplexSize]; + if(_this1.x * dir.x + _this1.y * dir.y + _this1.z * dir.z - (closest.x * dir.x + closest.y * dir.y + closest.z * dir.z) < 1e-008) { + this.interpolateClosestPoints(); + this.distance = Math.sqrt(closest.x * closest.x + closest.y * closest.y + closest.z * closest.z); + if(cache != null && cache._gjkCache != null) { + this.saveCache(cache._gjkCache); + } + return 0; + } + this.simplexSize++; + ++count; + } + return 2; + } + convexCastImpl(c1,c2,tf1,tf2,tl1,tl2,hit) { + this.c1 = c1; + this.c2 = c2; + this.tf1 = tf1; + this.tf2 = tf2; + let s = this.s; + let closest = this.closest; + let dir = this.dir; + let firstDirX; + let firstDirY; + let firstDirZ; + firstDirX = tf2._positionX - tf1._positionX; + firstDirY = tf2._positionY - tf1._positionY; + firstDirZ = tf2._positionZ - tf1._positionZ; + dir.x = firstDirX; + dir.y = firstDirY; + dir.z = firstDirZ; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-6) { + dir.init(1,0,0); + } + this.simplexSize = 0; + if(this.c1 != null) { + this.computeWitnessPoint1(true); + } else { + let v = this.w1[this.simplexSize]; + v.x = this.tf1._positionX; + v.y = this.tf1._positionY; + v.z = this.tf1._positionZ; + } + this.computeWitnessPoint2(true); + let _this = this.s[this.simplexSize]; + let v = this.w1[this.simplexSize]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let v1 = this.w2[this.simplexSize]; + _this.x -= v1.x; + _this.y -= v1.y; + _this.z -= v1.z; + this.simplexSize = 1; + let count = 0; + let lambda = 0.0; + let rayX = this.rayX; + let rayR = this.rayR; + rayX.zero(); + rayR.x = tl2.x; + rayR.y = tl2.y; + rayR.z = tl2.z; + rayR.x -= tl1.x; + rayR.y -= tl1.y; + rayR.z -= tl1.z; + while(count < 40) { + let v = 0; + switch(this.simplexSize) { + case 1: + let v1 = s[0]; + closest.x = v1.x; + closest.y = v1.y; + closest.z = v1.z; + v = 1; + break; + case 2: + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v2 = s[0]; + v1X = v2.x; + v1Y = v2.y; + v1Z = v2.z; + let v3 = s[1]; + v2X = v3.x; + v2Y = v3.y; + v2Z = v3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + v = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + v = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + v = 3; + } + break; + case 3: + let vec1 = s[0]; + let vec2 = s[1]; + let vec3 = s[2]; + let v1X1; + let v1Y1; + let v1Z1; + let v2X1; + let v2Y1; + let v2Z1; + let v3X; + let v3Y; + let v3Z; + let v12X1; + let v12Y1; + let v12Z1; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X1 = vec1.x; + v1Y1 = vec1.y; + v1Z1 = vec1.z; + v2X1 = vec2.x; + v2Y1 = vec2.y; + v2Z1 = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X1 = v2X1 - v1X1; + v12Y1 = v2Y1 - v1Y1; + v12Z1 = v2Z1 - v1Z1; + v23X = v3X - v2X1; + v23Y = v3Y - v2Y1; + v23Z = v3Z - v2Z1; + v31X = v1X1 - v3X; + v31Y = v1Y1 - v3Y; + v31Z = v1Z1 - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y1 * v23Z - v12Z1 * v23Y; + nY = v12Z1 * v23X - v12X1 * v23Z; + nZ = v12X1 * v23Y - v12Y1 * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y1 * nZ - v12Z1 * nY; + n12Y = v12Z1 * nX - v12X1 * nZ; + n12Z = v12X1 * nY - v12Y1 * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X1 * n12X + v1Y1 * n12Y + v1Z1 * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X1 * n23X + v2Y1 * n23Y + v2Z1 * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X1 * nX + v1Y1 * nY + v1Z1 * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = 7; + } + break; + case 4: + let vec11 = s[0]; + let vec21 = s[1]; + let vec31 = s[2]; + let vec4 = s[3]; + let v1X2; + let v1Y2; + let v1Z2; + let v2X2; + let v2Y2; + let v2Z2; + let v3X1; + let v3Y1; + let v3Z1; + let v4X; + let v4Y; + let v4Z; + let v12X2; + let v12Y2; + let v12Z2; + let v13X; + let v13Y; + let v13Z; + let v14X; + let v14Y; + let v14Z; + let v23X1; + let v23Y1; + let v23Z1; + let v24X; + let v24Y; + let v24Z; + v1X2 = vec11.x; + v1Y2 = vec11.y; + v1Z2 = vec11.z; + v2X2 = vec21.x; + v2Y2 = vec21.y; + v2Z2 = vec21.z; + v3X1 = vec31.x; + v3Y1 = vec31.y; + v3Z1 = vec31.z; + v4X = vec4.x; + v4Y = vec4.y; + v4Z = vec4.z; + v12X2 = v2X2 - v1X2; + v12Y2 = v2Y2 - v1Y2; + v12Z2 = v2Z2 - v1Z2; + v13X = v3X1 - v1X2; + v13Y = v3Y1 - v1Y2; + v13Z = v3Z1 - v1Z2; + v14X = v4X - v1X2; + v14Y = v4Y - v1Y2; + v14Z = v4Z - v1Z2; + v23X1 = v3X1 - v2X2; + v23Y1 = v3Y1 - v2Y2; + v23Z1 = v3Z1 - v2Z2; + v24X = v4X - v2X2; + v24Y = v4Y - v2Y2; + v24Z = v4Z - v2Z2; + let n123X; + let n123Y; + let n123Z; + let n134X; + let n134Y; + let n134Z; + let n142X; + let n142Y; + let n142Z; + let n243X; + let n243Y; + let n243Z; + n123X = v12Y2 * v13Z - v12Z2 * v13Y; + n123Y = v12Z2 * v13X - v12X2 * v13Z; + n123Z = v12X2 * v13Y - v12Y2 * v13X; + n134X = v13Y * v14Z - v13Z * v14Y; + n134Y = v13Z * v14X - v13X * v14Z; + n134Z = v13X * v14Y - v13Y * v14X; + n142X = v14Y * v12Z2 - v14Z * v12Y2; + n142Y = v14Z * v12X2 - v14X * v12Z2; + n142Z = v14X * v12Y2 - v14Y * v12X2; + n243X = v24Y * v23Z1 - v24Z * v23Y1; + n243Y = v24Z * v23X1 - v24X * v23Z1; + n243Z = v24X * v23Y1 - v24Y * v23X1; + let sign = v12X2 * n243X + v12Y2 * n243Y + v12Z2 * n243Z > 0 ? 1 : -1; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if((v1X2 * n123X + v1Y2 * n123Y + v1Z2 * n123Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec31.x; + v3Y = vec31.y; + v3Z = vec31.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + mini1 = b; + mind1 = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + if((v1X2 * n134X + v1Y2 * n134Y + v1Z2 * n134Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 6) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v1X2 * n142X + v1Y2 * n142Y + v1Z2 * n142Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 3 | (b & 4) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v2X2 * n243X + v2Y2 * n243Y + v2Z2 * n243Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if(mind1 > 0) { + closest.x = minvX1; + closest.y = minvY1; + closest.z = minvZ1; + v = mini1; + } else { + closest.zero(); + v = 15; + } + break; + } + this.shrinkSimplex(v); + if(closest.x * closest.x + closest.y * closest.y + closest.z * closest.z < 1e-008) { + if(lambda == 0 || this.simplexSize == 4) { + hit.fraction = lambda; + return false; + } + this.interpolateClosestPoints(); + hit.fraction = lambda; + let _this = hit.normal; + _this.x = dir.x; + _this.y = dir.y; + _this.z = dir.z; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let _this1 = hit.position; + let v = this.closestPoint1; + _this1.x = v.x; + _this1.y = v.y; + _this1.z = v.z; + _this1.x += tl1.x * lambda; + _this1.y += tl1.y * lambda; + _this1.z += tl1.z * lambda; + return true; + } + dir.x = closest.x; + dir.y = closest.y; + dir.z = closest.z; + dir.x = -dir.x; + dir.y = -dir.y; + dir.z = -dir.z; + if(this.c1 != null) { + this.computeWitnessPoint1(true); + } else { + let v = this.w1[this.simplexSize]; + v.x = this.tf1._positionX; + v.y = this.tf1._positionY; + v.z = this.tf1._positionZ; + } + this.computeWitnessPoint2(true); + let _this = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this.x = v4.x; + _this.y = v4.y; + _this.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this.x -= v5.x; + _this.y -= v5.y; + _this.z -= v5.z; + let _this1 = s[this.simplexSize]; + _this1.x -= rayX.x; + _this1.y -= rayX.y; + _this1.z -= rayX.z; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-008) { + throw new Error("!?"); + } + let p = s[this.simplexSize]; + let pn = p.x * dir.x + p.y * dir.y + p.z * dir.z; + if(pn < 0) { + if(rayR.x * dir.x + rayR.y * dir.y + rayR.z * dir.z >= 0) { + return false; + } + let dLambda = pn / (rayR.x * dir.x + rayR.y * dir.y + rayR.z * dir.z); + lambda += dLambda; + if(lambda >= 1) { + return false; + } + rayX.x += rayR.x * dLambda; + rayX.y += rayR.y * dLambda; + rayX.z += rayR.z * dLambda; + let _g = 0; + let _g1 = this.simplexSize + 1; + while(_g < _g1) { + let _this = s[_g++]; + let s1 = -dLambda; + _this.x += rayR.x * s1; + _this.y += rayR.y * s1; + _this.z += rayR.z * s1; + } + } + let duplicate = false; + let _g = 0; + let _g1 = this.simplexSize; + while(_g < _g1) { + let i = _g++; + let dx = s[i].x - s[this.simplexSize].x; + let dy = s[i].y - s[this.simplexSize].y; + let dz = s[i].z - s[this.simplexSize].z; + if(dx * dx + dy * dy + dz * dz < 1e-008) { + duplicate = true; + break; + } + } + if(!duplicate) { + this.simplexSize++; + } + ++count; + } + return false; + } + interpolateClosestPoints() { + switch(this.simplexSize) { + case 1: + let _this = this.closestPoint1; + let v = this.w1[0]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.closestPoint2; + let v1 = this.w2[0]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + break; + case 2: + let cX; + let cY; + let cZ; + let v2 = this.closest; + cX = v2.x; + cY = v2.y; + cZ = v2.z; + let s0X; + let s0Y; + let s0Z; + let w10X; + let w10Y; + let w10Z; + let w20X; + let w20Y; + let w20Z; + let s1X; + let s1Y; + let s1Z; + let w11X; + let w11Y; + let w11Z; + let w21X; + let w21Y; + let w21Z; + let v3 = this.s[0]; + s0X = v3.x; + s0Y = v3.y; + s0Z = v3.z; + let v4 = this.w1[0]; + w10X = v4.x; + w10Y = v4.y; + w10Z = v4.z; + let v5 = this.w2[0]; + w20X = v5.x; + w20Y = v5.y; + w20Z = v5.z; + let v6 = this.s[1]; + s1X = v6.x; + s1Y = v6.y; + s1Z = v6.z; + let v7 = this.w1[1]; + w11X = v7.x; + w11Y = v7.y; + w11Z = v7.z; + let v8 = this.w2[1]; + w21X = v8.x; + w21Y = v8.y; + w21Z = v8.z; + let s01X; + let s01Y; + let s01Z; + s01X = s1X - s0X; + s01Y = s1Y - s0Y; + s01Z = s1Z - s0Z; + let invDet = s01X * s01X + s01Y * s01Y + s01Z * s01Z; + if(invDet != 0) { + invDet = 1 / invDet; + } + let s0cX; + let s0cY; + let s0cZ; + s0cX = cX - s0X; + s0cY = cY - s0Y; + s0cZ = cZ - s0Z; + let t = (s0cX * s01X + s0cY * s01Y + s0cZ * s01Z) * invDet; + let diffX; + let diffY; + let diffZ; + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + diffX = w11X - w10X; + diffY = w11Y - w10Y; + diffZ = w11Z - w10Z; + cp1X = w10X + diffX * t; + cp1Y = w10Y + diffY * t; + cp1Z = w10Z + diffZ * t; + diffX = w21X - w20X; + diffY = w21Y - w20Y; + diffZ = w21Z - w20Z; + cp2X = w20X + diffX * t; + cp2Y = w20Y + diffY * t; + cp2Z = w20Z + diffZ * t; + let v9 = this.closestPoint1; + v9.x = cp1X; + v9.y = cp1Y; + v9.z = cp1Z; + let v10 = this.closestPoint2; + v10.x = cp2X; + v10.y = cp2Y; + v10.z = cp2Z; + break; + case 3: + let cX1; + let cY1; + let cZ1; + let v11 = this.closest; + cX1 = v11.x; + cY1 = v11.y; + cZ1 = v11.z; + let s0X1; + let s0Y1; + let s0Z1; + let w10X1; + let w10Y1; + let w10Z1; + let w20X1; + let w20Y1; + let w20Z1; + let s1X1; + let s1Y1; + let s1Z1; + let w11X1; + let w11Y1; + let w11Z1; + let w21X1; + let w21Y1; + let w21Z1; + let s2X; + let s2Y; + let s2Z; + let w12X; + let w12Y; + let w12Z; + let w22X; + let w22Y; + let w22Z; + let v12 = this.s[0]; + s0X1 = v12.x; + s0Y1 = v12.y; + s0Z1 = v12.z; + let v13 = this.w1[0]; + w10X1 = v13.x; + w10Y1 = v13.y; + w10Z1 = v13.z; + let v14 = this.w2[0]; + w20X1 = v14.x; + w20Y1 = v14.y; + w20Z1 = v14.z; + let v15 = this.s[1]; + s1X1 = v15.x; + s1Y1 = v15.y; + s1Z1 = v15.z; + let v16 = this.w1[1]; + w11X1 = v16.x; + w11Y1 = v16.y; + w11Z1 = v16.z; + let v17 = this.w2[1]; + w21X1 = v17.x; + w21Y1 = v17.y; + w21Z1 = v17.z; + let v18 = this.s[2]; + s2X = v18.x; + s2Y = v18.y; + s2Z = v18.z; + let v19 = this.w1[2]; + w12X = v19.x; + w12Y = v19.y; + w12Z = v19.z; + let v20 = this.w2[2]; + w22X = v20.x; + w22Y = v20.y; + w22Z = v20.z; + let s01X1; + let s01Y1; + let s01Z1; + let s02X; + let s02Y; + let s02Z; + let s0cX1; + let s0cY1; + let s0cZ1; + s01X1 = s1X1 - s0X1; + s01Y1 = s1Y1 - s0Y1; + s01Z1 = s1Z1 - s0Z1; + s02X = s2X - s0X1; + s02Y = s2Y - s0Y1; + s02Z = s2Z - s0Z1; + s0cX1 = cX1 - s0X1; + s0cY1 = cY1 - s0Y1; + s0cZ1 = cZ1 - s0Z1; + let d11 = s01X1 * s01X1 + s01Y1 * s01Y1 + s01Z1 * s01Z1; + let d12 = s01X1 * s02X + s01Y1 * s02Y + s01Z1 * s02Z; + let d22 = s02X * s02X + s02Y * s02Y + s02Z * s02Z; + let d1c = s01X1 * s0cX1 + s01Y1 * s0cY1 + s01Z1 * s0cZ1; + let d2c = s02X * s0cX1 + s02Y * s0cY1 + s02Z * s0cZ1; + let invDet1 = d11 * d22 - d12 * d12; + if(invDet1 != 0) { + invDet1 = 1 / invDet1; + } + let s = (d1c * d22 - d2c * d12) * invDet1; + let t1 = (-d1c * d12 + d2c * d11) * invDet1; + let diffX1; + let diffY1; + let diffZ1; + let cp1X1; + let cp1Y1; + let cp1Z1; + let cp2X1; + let cp2Y1; + let cp2Z1; + diffX1 = w11X1 - w10X1; + diffY1 = w11Y1 - w10Y1; + diffZ1 = w11Z1 - w10Z1; + cp1X1 = w10X1 + diffX1 * s; + cp1Y1 = w10Y1 + diffY1 * s; + cp1Z1 = w10Z1 + diffZ1 * s; + diffX1 = w12X - w10X1; + diffY1 = w12Y - w10Y1; + diffZ1 = w12Z - w10Z1; + cp1X1 += diffX1 * t1; + cp1Y1 += diffY1 * t1; + cp1Z1 += diffZ1 * t1; + diffX1 = w21X1 - w20X1; + diffY1 = w21Y1 - w20Y1; + diffZ1 = w21Z1 - w20Z1; + cp2X1 = w20X1 + diffX1 * s; + cp2Y1 = w20Y1 + diffY1 * s; + cp2Z1 = w20Z1 + diffZ1 * s; + diffX1 = w22X - w20X1; + diffY1 = w22Y - w20Y1; + diffZ1 = w22Z - w20Z1; + cp2X1 += diffX1 * t1; + cp2Y1 += diffY1 * t1; + cp2Z1 += diffZ1 * t1; + let v21 = this.closestPoint1; + v21.x = cp1X1; + v21.y = cp1Y1; + v21.z = cp1Z1; + let v22 = this.closestPoint2; + v22.x = cp2X1; + v22.y = cp2Y1; + v22.z = cp2Z1; + break; + default: + throw new Error("!?"); + } + } + loadCache(gjkCache) { + let _this = this.dir; + let v = gjkCache.prevClosestDir; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + } + saveCache(gjkCache) { + let _this = gjkCache.prevClosestDir; + let v = this.closest; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x = -_this.x; + _this.y = -_this.y; + _this.z = -_this.z; + } + shrinkSimplex(vertexBits) { + this.simplexSize = vertexBits; + this.simplexSize = (this.simplexSize & 5) + (this.simplexSize >> 1 & 5); + this.simplexSize = (this.simplexSize & 3) + (this.simplexSize >> 2 & 3); + switch(vertexBits) { + case 2: + let _this = this.s[0]; + let v = this.s[1]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.w1[0]; + let v1 = this.w1[1]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + let _this2 = this.w2[0]; + let v2 = this.w2[1]; + _this2.x = v2.x; + _this2.y = v2.y; + _this2.z = v2.z; + break; + case 4: + let _this3 = this.s[0]; + let v3 = this.s[2]; + _this3.x = v3.x; + _this3.y = v3.y; + _this3.z = v3.z; + let _this4 = this.w1[0]; + let v4 = this.w1[2]; + _this4.x = v4.x; + _this4.y = v4.y; + _this4.z = v4.z; + let _this5 = this.w2[0]; + let v5 = this.w2[2]; + _this5.x = v5.x; + _this5.y = v5.y; + _this5.z = v5.z; + break; + case 5: + let _this6 = this.s[1]; + let v6 = this.s[2]; + _this6.x = v6.x; + _this6.y = v6.y; + _this6.z = v6.z; + let _this7 = this.w1[1]; + let v7 = this.w1[2]; + _this7.x = v7.x; + _this7.y = v7.y; + _this7.z = v7.z; + let _this8 = this.w2[1]; + let v8 = this.w2[2]; + _this8.x = v8.x; + _this8.y = v8.y; + _this8.z = v8.z; + break; + case 6: + let _this9 = this.s[0]; + let v9 = this.s[2]; + _this9.x = v9.x; + _this9.y = v9.y; + _this9.z = v9.z; + let _this10 = this.w1[0]; + let v10 = this.w1[2]; + _this10.x = v10.x; + _this10.y = v10.y; + _this10.z = v10.z; + let _this11 = this.w2[0]; + let v11 = this.w2[2]; + _this11.x = v11.x; + _this11.y = v11.y; + _this11.z = v11.z; + break; + case 8: + let _this12 = this.s[0]; + let v12 = this.s[3]; + _this12.x = v12.x; + _this12.y = v12.y; + _this12.z = v12.z; + let _this13 = this.w1[0]; + let v13 = this.w1[3]; + _this13.x = v13.x; + _this13.y = v13.y; + _this13.z = v13.z; + let _this14 = this.w2[0]; + let v14 = this.w2[3]; + _this14.x = v14.x; + _this14.y = v14.y; + _this14.z = v14.z; + break; + case 9: + let _this15 = this.s[1]; + let v15 = this.s[3]; + _this15.x = v15.x; + _this15.y = v15.y; + _this15.z = v15.z; + let _this16 = this.w1[1]; + let v16 = this.w1[3]; + _this16.x = v16.x; + _this16.y = v16.y; + _this16.z = v16.z; + let _this17 = this.w2[1]; + let v17 = this.w2[3]; + _this17.x = v17.x; + _this17.y = v17.y; + _this17.z = v17.z; + break; + case 10: + let _this18 = this.s[0]; + let v18 = this.s[3]; + _this18.x = v18.x; + _this18.y = v18.y; + _this18.z = v18.z; + let _this19 = this.w1[0]; + let v19 = this.w1[3]; + _this19.x = v19.x; + _this19.y = v19.y; + _this19.z = v19.z; + let _this20 = this.w2[0]; + let v20 = this.w2[3]; + _this20.x = v20.x; + _this20.y = v20.y; + _this20.z = v20.z; + break; + case 11: + let _this21 = this.s[2]; + let v21 = this.s[3]; + _this21.x = v21.x; + _this21.y = v21.y; + _this21.z = v21.z; + let _this22 = this.w1[2]; + let v22 = this.w1[3]; + _this22.x = v22.x; + _this22.y = v22.y; + _this22.z = v22.z; + let _this23 = this.w2[2]; + let v23 = this.w2[3]; + _this23.x = v23.x; + _this23.y = v23.y; + _this23.z = v23.z; + break; + case 12: + let _this24 = this.s[0]; + let v24 = this.s[2]; + _this24.x = v24.x; + _this24.y = v24.y; + _this24.z = v24.z; + let _this25 = this.w1[0]; + let v25 = this.w1[2]; + _this25.x = v25.x; + _this25.y = v25.y; + _this25.z = v25.z; + let _this26 = this.w2[0]; + let v26 = this.w2[2]; + _this26.x = v26.x; + _this26.y = v26.y; + _this26.z = v26.z; + let _this27 = this.s[1]; + let v27 = this.s[3]; + _this27.x = v27.x; + _this27.y = v27.y; + _this27.z = v27.z; + let _this28 = this.w1[1]; + let v28 = this.w1[3]; + _this28.x = v28.x; + _this28.y = v28.y; + _this28.z = v28.z; + let _this29 = this.w2[1]; + let v29 = this.w2[3]; + _this29.x = v29.x; + _this29.y = v29.y; + _this29.z = v29.z; + break; + case 13: + let _this30 = this.s[1]; + let v30 = this.s[3]; + _this30.x = v30.x; + _this30.y = v30.y; + _this30.z = v30.z; + let _this31 = this.w1[1]; + let v31 = this.w1[3]; + _this31.x = v31.x; + _this31.y = v31.y; + _this31.z = v31.z; + let _this32 = this.w2[1]; + let v32 = this.w2[3]; + _this32.x = v32.x; + _this32.y = v32.y; + _this32.z = v32.z; + break; + case 14: + let _this33 = this.s[0]; + let v33 = this.s[3]; + _this33.x = v33.x; + _this33.y = v33.y; + _this33.z = v33.z; + let _this34 = this.w1[0]; + let v34 = this.w1[3]; + _this34.x = v34.x; + _this34.y = v34.y; + _this34.z = v34.z; + let _this35 = this.w2[0]; + let v35 = this.w2[3]; + _this35.x = v35.x; + _this35.y = v35.y; + _this35.z = v35.z; + break; + } + } + computeWitnessPoint1(addMargin) { + let tmpX; + let tmpY; + let tmpZ; + let idirX; + let idirY; + let idirZ; + let v = this.dir; + idirX = v.x; + idirY = v.y; + idirZ = v.z; + let ldir1X; + let ldir1Y; + let ldir1Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this.tf1._rotation00 * idirX + this.tf1._rotation10 * idirY + this.tf1._rotation20 * idirZ; + __tmp__Y = this.tf1._rotation01 * idirX + this.tf1._rotation11 * idirY + this.tf1._rotation21 * idirZ; + __tmp__Z = this.tf1._rotation02 * idirX + this.tf1._rotation12 * idirY + this.tf1._rotation22 * idirZ; + ldir1X = __tmp__X; + ldir1Y = __tmp__Y; + ldir1Z = __tmp__Z; + let iw1X; + let iw1Y; + let iw1Z; + let v1 = this.dir; + v1.x = ldir1X; + v1.y = ldir1Y; + v1.z = ldir1Z; + this.c1.computeLocalSupportingVertex(this.dir,this.w1[this.simplexSize]); + if(addMargin) { + let _this = this.dir; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let _this1 = this.w1[this.simplexSize]; + let v = this.dir; + let s = this.c1._gjkMargin; + _this1.x += v.x * s; + _this1.y += v.y * s; + _this1.z += v.z * s; + } + let v2 = this.w1[this.simplexSize]; + tmpX = v2.x; + tmpY = v2.y; + tmpZ = v2.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this.tf1._rotation00 * tmpX + this.tf1._rotation01 * tmpY + this.tf1._rotation02 * tmpZ; + __tmp__Y1 = this.tf1._rotation10 * tmpX + this.tf1._rotation11 * tmpY + this.tf1._rotation12 * tmpZ; + __tmp__Z1 = this.tf1._rotation20 * tmpX + this.tf1._rotation21 * tmpY + this.tf1._rotation22 * tmpZ; + iw1X = __tmp__X1; + iw1Y = __tmp__Y1; + iw1Z = __tmp__Z1; + iw1X += this.tf1._positionX; + iw1Y += this.tf1._positionY; + iw1Z += this.tf1._positionZ; + let v3 = this.w1[this.simplexSize]; + v3.x = iw1X; + v3.y = iw1Y; + v3.z = iw1Z; + let v4 = this.dir; + v4.x = idirX; + v4.y = idirY; + v4.z = idirZ; + } + computeWitnessPoint2(addMargin) { + let tmpX; + let tmpY; + let tmpZ; + let idirX; + let idirY; + let idirZ; + let v = this.dir; + idirX = v.x; + idirY = v.y; + idirZ = v.z; + let ldir2X; + let ldir2Y; + let ldir2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this.tf2._rotation00 * idirX + this.tf2._rotation10 * idirY + this.tf2._rotation20 * idirZ; + __tmp__Y = this.tf2._rotation01 * idirX + this.tf2._rotation11 * idirY + this.tf2._rotation21 * idirZ; + __tmp__Z = this.tf2._rotation02 * idirX + this.tf2._rotation12 * idirY + this.tf2._rotation22 * idirZ; + ldir2X = __tmp__X; + ldir2Y = __tmp__Y; + ldir2Z = __tmp__Z; + ldir2X = -ldir2X; + ldir2Y = -ldir2Y; + ldir2Z = -ldir2Z; + let iw2X; + let iw2Y; + let iw2Z; + let v1 = this.dir; + v1.x = ldir2X; + v1.y = ldir2Y; + v1.z = ldir2Z; + this.c2.computeLocalSupportingVertex(this.dir,this.w2[this.simplexSize]); + if(addMargin) { + let _this = this.dir; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let _this1 = this.w2[this.simplexSize]; + let v = this.dir; + let s = this.c2._gjkMargin; + _this1.x += v.x * s; + _this1.y += v.y * s; + _this1.z += v.z * s; + } + let v2 = this.w2[this.simplexSize]; + tmpX = v2.x; + tmpY = v2.y; + tmpZ = v2.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this.tf2._rotation00 * tmpX + this.tf2._rotation01 * tmpY + this.tf2._rotation02 * tmpZ; + __tmp__Y1 = this.tf2._rotation10 * tmpX + this.tf2._rotation11 * tmpY + this.tf2._rotation12 * tmpZ; + __tmp__Z1 = this.tf2._rotation20 * tmpX + this.tf2._rotation21 * tmpY + this.tf2._rotation22 * tmpZ; + iw2X = __tmp__X1; + iw2Y = __tmp__Y1; + iw2Z = __tmp__Z1; + iw2X += this.tf2._positionX; + iw2Y += this.tf2._positionY; + iw2Z += this.tf2._positionZ; + let v3 = this.w2[this.simplexSize]; + v3.x = iw2X; + v3.y = iw2Y; + v3.z = iw2Z; + let v4 = this.dir; + v4.x = idirX; + v4.y = idirY; + v4.z = idirZ; + } + pointToTetrahedron() { + let _g = 0; + while(_g < 3) { + let _this = this.dir; + let v = this.baseDirs[_g++]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this1 = this.s[this.simplexSize]; + let v1 = this.w1[this.simplexSize]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + let v2 = this.w2[this.simplexSize]; + _this1.x -= v2.x; + _this1.y -= v2.y; + _this1.z -= v2.z; + this.simplexSize++; + this.lineToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + let _this2 = this.dir; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this3 = this.s[this.simplexSize]; + let v3 = this.w1[this.simplexSize]; + _this3.x = v3.x; + _this3.y = v3.y; + _this3.z = v3.z; + let v4 = this.w2[this.simplexSize]; + _this3.x -= v4.x; + _this3.y -= v4.y; + _this3.z -= v4.z; + this.simplexSize++; + this.lineToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + } + } + lineToTetrahedron() { + let oldDirX; + let oldDirY; + let oldDirZ; + let v = this.dir; + oldDirX = v.x; + oldDirY = v.y; + oldDirZ = v.z; + let s0X; + let s0Y; + let s0Z; + let s1X; + let s1Y; + let s1Z; + let lineDirX; + let lineDirY; + let lineDirZ; + let v1 = this.s[0]; + s0X = v1.x; + s0Y = v1.y; + s0Z = v1.z; + let v2 = this.s[1]; + s1X = v2.x; + s1Y = v2.y; + s1Z = v2.z; + lineDirX = s0X - s1X; + lineDirY = s0Y - s1Y; + lineDirZ = s0Z - s1Z; + let _g = 0; + while(_g < 3) { + let baseDirX; + let baseDirY; + let baseDirZ; + let v = this.baseDirs[_g++]; + baseDirX = v.x; + baseDirY = v.y; + baseDirZ = v.z; + let newDirX; + let newDirY; + let newDirZ; + newDirX = lineDirY * baseDirZ - lineDirZ * baseDirY; + newDirY = lineDirZ * baseDirX - lineDirX * baseDirZ; + newDirZ = lineDirX * baseDirY - lineDirY * baseDirX; + let v1 = this.dir; + v1.x = newDirX; + v1.y = newDirY; + v1.z = newDirZ; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v2 = this.w1[this.simplexSize]; + _this.x = v2.x; + _this.y = v2.y; + _this.z = v2.z; + let v3 = this.w2[this.simplexSize]; + _this.x -= v3.x; + _this.y -= v3.y; + _this.z -= v3.z; + this.simplexSize++; + this.triangleToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + let _this1 = this.dir; + _this1.x = -_this1.x; + _this1.y = -_this1.y; + _this1.z = -_this1.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this2 = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this2.x = v4.x; + _this2.y = v4.y; + _this2.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this2.x -= v5.x; + _this2.y -= v5.y; + _this2.z -= v5.z; + this.simplexSize++; + this.triangleToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + } + let v3 = this.dir; + v3.x = oldDirX; + v3.y = oldDirY; + v3.z = oldDirZ; + } + triangleToTetrahedron() { + let oldDirX; + let oldDirY; + let oldDirZ; + let v = this.dir; + oldDirX = v.x; + oldDirY = v.y; + oldDirZ = v.z; + while(true) { + let s0X; + let s0Y; + let s0Z; + let s1X; + let s1Y; + let s1Z; + let s2X; + let s2Y; + let s2Z; + let s01X; + let s01Y; + let s01Z; + let s02X; + let s02Y; + let s02Z; + let v = this.s[0]; + s0X = v.x; + s0Y = v.y; + s0Z = v.z; + let v1 = this.s[1]; + s1X = v1.x; + s1Y = v1.y; + s1Z = v1.z; + let v2 = this.s[2]; + s2X = v2.x; + s2Y = v2.y; + s2Z = v2.z; + s01X = s1X - s0X; + s01Y = s1Y - s0Y; + s01Z = s1Z - s0Z; + s02X = s2X - s0X; + s02Y = s2Y - s0Y; + s02Z = s2Z - s0Z; + let nX; + let nY; + let nZ; + nX = s01Y * s02Z - s01Z * s02Y; + nY = s01Z * s02X - s01X * s02Z; + nZ = s01X * s02Y - s01Y * s02X; + let v3 = this.dir; + v3.x = nX; + v3.y = nY; + v3.z = nZ; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this.x = v4.x; + _this.y = v4.y; + _this.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this.x -= v5.x; + _this.y -= v5.y; + _this.z -= v5.z; + this.simplexSize++; + if(this.isValidTetrahedron()) { + break; + } + this.simplexSize--; + let _this1 = this.dir; + _this1.x = -_this1.x; + _this1.y = -_this1.y; + _this1.z = -_this1.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this2 = this.s[this.simplexSize]; + let v6 = this.w1[this.simplexSize]; + _this2.x = v6.x; + _this2.y = v6.y; + _this2.z = v6.z; + let v7 = this.w2[this.simplexSize]; + _this2.x -= v7.x; + _this2.y -= v7.y; + _this2.z -= v7.z; + this.simplexSize++; + if(this.isValidTetrahedron()) { + break; + } + this.simplexSize--; + break; + } + let v1 = this.dir; + v1.x = oldDirX; + v1.y = oldDirY; + v1.z = oldDirZ; + } + isValidTetrahedron() { + let e10 = this.s[2].x - this.s[0].x; + let e11 = this.s[2].y - this.s[0].y; + let e12 = this.s[2].z - this.s[0].z; + let e20 = this.s[3].x - this.s[0].x; + let e21 = this.s[3].y - this.s[0].y; + let e22 = this.s[3].z - this.s[0].z; + let det = (this.s[1].x - this.s[0].x) * (e11 * e22 - e12 * e21) - (this.s[1].y - this.s[0].y) * (e10 * e22 - e12 * e20) + (this.s[1].z - this.s[0].z) * (e10 * e21 - e11 * e20); + if(!(det > 1e-12)) { + return det < -1e-12; + } else { + return true; + } + } + computeDepth(convex1,convex2,tf1,tf2,initialPolyhedron,initialPolyhedron1,initialPolyhedron2) { + let _this = this.polyhedron; + while(_this._numTriangles > 0) { + let t = _this._triangleList; + _this._numTriangles--; + let prev = t._prev; + let next = t._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(t == _this._triangleList) { + _this._triangleList = _this._triangleList._next; + } + if(t == _this._triangleListLast) { + _this._triangleListLast = _this._triangleListLast._prev; + } + t._next = null; + t._prev = null; + t.removeReferences(); + t._next = _this._trianglePool; + _this._trianglePool = t; + } + while(_this._numVertices > 0) { + let v = _this._vertices[--_this._numVertices]; + v.removeReferences(); + v._next = _this._vertexPool; + _this._vertexPool = v; + } + let tmp = this.polyhedron; + let _this1 = this.polyhedron; + let first = _this1._vertexPool; + if(first != null) { + _this1._vertexPool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let tmp1 = first.init(initialPolyhedron[0],initialPolyhedron1[0],initialPolyhedron2[0]); + let _this2 = this.polyhedron; + let first1 = _this2._vertexPool; + if(first1 != null) { + _this2._vertexPool = first1._next; + first1._next = null; + } else { + first1 = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let tmp2 = first1.init(initialPolyhedron[1],initialPolyhedron1[1],initialPolyhedron2[1]); + let _this3 = this.polyhedron; + let first2 = _this3._vertexPool; + if(first2 != null) { + _this3._vertexPool = first2._next; + first2._next = null; + } else { + first2 = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let tmp3 = first2.init(initialPolyhedron[2],initialPolyhedron1[2],initialPolyhedron2[2]); + let _this4 = this.polyhedron; + let first3 = _this4._vertexPool; + if(first3 != null) { + _this4._vertexPool = first3._next; + first3._next = null; + } else { + first3 = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + if(!tmp._init(tmp1,tmp2,tmp3,first3.init(initialPolyhedron[3],initialPolyhedron1[3],initialPolyhedron2[3]))) { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_INIT; + } + this.simplexSize = 0; + let supportingVertex = this.s[0]; + let witness1 = this.w1[0]; + let witness2 = this.w2[0]; + let count = 0; + while(count < 40) { + let f = this.polyhedron._triangleList; + let mind = 1e65536; + let minf = null; + while(f != null) { + if(f._distanceSq < mind) { + mind = f._distanceSq; + minf = f; + } + f = f._next; + } + let face = minf; + let _this = this.dir; + let v = face._normal; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this1 = this.s[this.simplexSize]; + let v1 = this.w1[this.simplexSize]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + let v2 = this.w2[this.simplexSize]; + _this1.x -= v2.x; + _this1.y -= v2.y; + _this1.z -= v2.z; + let v0 = face._vertices[0]; + let v11 = face._vertices[1]; + let v21 = face._vertices[2]; + let _this2 = v0.v; + let v3 = this.dir; + let v4 = this.dir; + if(supportingVertex.x * v4.x + supportingVertex.y * v4.y + supportingVertex.z * v4.z - (_this2.x * v3.x + _this2.y * v3.y + _this2.z * v3.z) < 1e-6 || count == 39) { + let _this = this.closest; + let v = this.dir; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.dir; + let v1 = v0.v; + let _this2 = this.dir; + let s = (_this1.x * v1.x + _this1.y * v1.y + _this1.z * v1.z) / (_this2.x * _this2.x + _this2.y * _this2.y + _this2.z * _this2.z); + _this.x *= s; + _this.y *= s; + _this.z *= s; + let cX; + let cY; + let cZ; + let v2 = this.closest; + cX = v2.x; + cY = v2.y; + cZ = v2.z; + let s0X; + let s0Y; + let s0Z; + let w10X; + let w10Y; + let w10Z; + let w20X; + let w20Y; + let w20Z; + let s1X; + let s1Y; + let s1Z; + let w11X; + let w11Y; + let w11Z; + let w21X; + let w21Y; + let w21Z; + let s2X; + let s2Y; + let s2Z; + let w12X; + let w12Y; + let w12Z; + let w22X; + let w22Y; + let w22Z; + let v3 = v0.v; + s0X = v3.x; + s0Y = v3.y; + s0Z = v3.z; + let v4 = v0.w1; + w10X = v4.x; + w10Y = v4.y; + w10Z = v4.z; + let v5 = v0.w2; + w20X = v5.x; + w20Y = v5.y; + w20Z = v5.z; + let v6 = v11.v; + s1X = v6.x; + s1Y = v6.y; + s1Z = v6.z; + let v7 = v11.w1; + w11X = v7.x; + w11Y = v7.y; + w11Z = v7.z; + let v8 = v11.w2; + w21X = v8.x; + w21Y = v8.y; + w21Z = v8.z; + let v9 = v21.v; + s2X = v9.x; + s2Y = v9.y; + s2Z = v9.z; + let v10 = v21.w1; + w12X = v10.x; + w12Y = v10.y; + w12Z = v10.z; + let v12 = v21.w2; + w22X = v12.x; + w22Y = v12.y; + w22Z = v12.z; + let s01X; + let s01Y; + let s01Z; + let s02X; + let s02Y; + let s02Z; + let s0cX; + let s0cY; + let s0cZ; + s01X = s1X - s0X; + s01Y = s1Y - s0Y; + s01Z = s1Z - s0Z; + s02X = s2X - s0X; + s02Y = s2Y - s0Y; + s02Z = s2Z - s0Z; + s0cX = cX - s0X; + s0cY = cY - s0Y; + s0cZ = cZ - s0Z; + let d11 = s01X * s01X + s01Y * s01Y + s01Z * s01Z; + let d12 = s01X * s02X + s01Y * s02Y + s01Z * s02Z; + let d22 = s02X * s02X + s02Y * s02Y + s02Z * s02Z; + let d1c = s01X * s0cX + s01Y * s0cY + s01Z * s0cZ; + let d2c = s02X * s0cX + s02Y * s0cY + s02Z * s0cZ; + let invDet = d11 * d22 - d12 * d12; + if(invDet != 0) { + invDet = 1 / invDet; + } + let s1 = (d1c * d22 - d2c * d12) * invDet; + let t = (-d1c * d12 + d2c * d11) * invDet; + let diffX; + let diffY; + let diffZ; + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + diffX = w11X - w10X; + diffY = w11Y - w10Y; + diffZ = w11Z - w10Z; + cp1X = w10X + diffX * s1; + cp1Y = w10Y + diffY * s1; + cp1Z = w10Z + diffZ * s1; + diffX = w12X - w10X; + diffY = w12Y - w10Y; + diffZ = w12Z - w10Z; + cp1X += diffX * t; + cp1Y += diffY * t; + cp1Z += diffZ * t; + diffX = w21X - w20X; + diffY = w21Y - w20Y; + diffZ = w21Z - w20Z; + cp2X = w20X + diffX * s1; + cp2Y = w20Y + diffY * s1; + cp2Z = w20Z + diffZ * s1; + diffX = w22X - w20X; + diffY = w22Y - w20Y; + diffZ = w22Z - w20Z; + cp2X += diffX * t; + cp2Y += diffY * t; + cp2Z += diffZ * t; + let v13 = this.closestPoint1; + v13.x = cp1X; + v13.y = cp1Y; + v13.z = cp1Z; + let v14 = this.closestPoint2; + v14.x = cp2X; + v14.y = cp2Y; + v14.z = cp2Z; + let _this3 = this.closest; + this.depth = Math.sqrt(_this3.x * _this3.x + _this3.y * _this3.y + _this3.z * _this3.z); + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.SUCCEEDED; + } + let _this3 = this.polyhedron; + let first = _this3._vertexPool; + if(first != null) { + _this3._vertexPool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let epaVertex = first.init(supportingVertex,witness1,witness2); + if(!this.polyhedron._addVertex(epaVertex,face)) { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_ADD_VERTEX; + } + ++count; + } + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_DID_NOT_CONVERGE; + } + computeClosestPoints(c1,c2,tf1,tf2,cache) { + return this.computeClosestPointsImpl(c1,c2,tf1,tf2,cache,true); + } + computeDistance(c1,c2,tf1,tf2,cache) { + return this.computeClosestPointsImpl(c1,c2,tf1,tf2,cache,false); + } + convexCast(c1,c2,tf1,tf2,tl1,tl2,hit) { + return this.convexCastImpl(c1,c2,tf1,tf2,tl1,tl2,hit); + } + rayCast(c,tf,begin,end,hit) { + let tf1 = this.tempTransform; + tf1._positionX = begin.x; + tf1._positionY = begin.y; + tf1._positionZ = begin.z; + let tl1 = this.tl1; + let tl2 = this.tl2; + tl1.x = end.x; + tl1.y = end.y; + tl1.z = end.z; + tl1.x -= begin.x; + tl1.y -= begin.y; + tl1.z -= begin.z; + tl2.zero(); + return this.convexCastImpl(null,c,tf1,tf,tl1,tl2,hit); + } + static getInstance() { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + } +} +oimo.collision.narrowphase.detector.gjkepa.GjkEpaLog = class oimo_collision_narrowphase_detector_gjkepa_GjkEpaLog { +} +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState = class oimo_collision_narrowphase_detector_gjkepa_GjkEpaResultState { +} +oimo.collision.narrowphase.detector.gjkepa.SimplexUtil = class oimo_collision_narrowphase_detector_gjkepa_SimplexUtil { + static projectOrigin2(vec1,vec2,out) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + return 1; + } + if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + return 2; + } + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + return 3; + } + static projectOrigin3(vec1,vec2,vec3,out) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini = b; + mind = out.x * out.x + out.y * out.y + out.z * out.z; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(mind > 0) { + out.x = minvX; + out.y = minvY; + out.z = minvZ; + return mini; + } + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + out.x = minvX; + out.y = minvY; + out.z = minvZ; + return 7; + } + static projectOrigin4(vec1,vec2,vec3,vec4,out) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v4X; + let v4Y; + let v4Z; + let v12X; + let v12Y; + let v12Z; + let v13X; + let v13Y; + let v13Z; + let v14X; + let v14Y; + let v14Z; + let v23X; + let v23Y; + let v23Z; + let v24X; + let v24Y; + let v24Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v4X = vec4.x; + v4Y = vec4.y; + v4Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v13X = v3X - v1X; + v13Y = v3Y - v1Y; + v13Z = v3Z - v1Z; + v14X = v4X - v1X; + v14Y = v4Y - v1Y; + v14Z = v4Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v24X = v4X - v2X; + v24Y = v4Y - v2Y; + v24Z = v4Z - v2Z; + let n123X; + let n123Y; + let n123Z; + let n134X; + let n134Y; + let n134Z; + let n142X; + let n142Y; + let n142Z; + let n243X; + let n243Y; + let n243Z; + n123X = v12Y * v13Z - v12Z * v13Y; + n123Y = v12Z * v13X - v12X * v13Z; + n123Z = v12X * v13Y - v12Y * v13X; + n134X = v13Y * v14Z - v13Z * v14Y; + n134Y = v13Z * v14X - v13X * v14Z; + n134Z = v13X * v14Y - v13Y * v14X; + n142X = v14Y * v12Z - v14Z * v12Y; + n142Y = v14Z * v12X - v14X * v12Z; + n142Z = v14X * v12Y - v14Y * v12X; + n243X = v24Y * v23Z - v24Z * v23Y; + n243Y = v24Z * v23X - v24X * v23Z; + n243Z = v24X * v23Y - v24Y * v23X; + let sign = v12X * n243X + v12Y * n243Y + v12Z * n243Z > 0 ? 1 : -1; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if((v1X * n123X + v1Y * n123Y + v1Z * n123Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + mini = b; + mind = out.x * out.x + out.y * out.y + out.z * out.z; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + if((v1X * n134X + v1Y * n134Y + v1Z * n134Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec3.x; + v1Y = vec3.y; + v1Z = vec3.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 6) << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if((v1X * n142X + v1Y * n142Y + v1Z * n142Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b & 3 | (b & 4) << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if((v2X * n243X + v2Y * n243Y + v2Z * n243Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec3.x; + v1Y = vec3.y; + v1Z = vec3.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(mind > 0) { + out.x = minvX; + out.y = minvY; + out.z = minvZ; + return mini; + } + out.zero(); + return 15; + } +} +oimo.common.Mat3 = class oimo_common_Mat3 { + constructor(e00,e01,e02,e10,e11,e12,e20,e21,e22) { + if(e22 == null) { + e22 = 1; + } + if(e21 == null) { + e21 = 0; + } + if(e20 == null) { + e20 = 0; + } + if(e12 == null) { + e12 = 0; + } + if(e11 == null) { + e11 = 1; + } + if(e10 == null) { + e10 = 0; + } + if(e02 == null) { + e02 = 0; + } + if(e01 == null) { + e01 = 0; + } + if(e00 == null) { + e00 = 1; + } + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + oimo.common.Mat3.numCreations++; + } + init(e00,e01,e02,e10,e11,e12,e20,e21,e22) { + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + return this; + } + identity() { + this.e00 = 1; + this.e01 = 0; + this.e02 = 0; + this.e10 = 0; + this.e11 = 1; + this.e12 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = 1; + return this; + } + add(m) { + return new oimo.common.Mat3(this.e00 + m.e00,this.e01 + m.e01,this.e02 + m.e02,this.e10 + m.e10,this.e11 + m.e11,this.e12 + m.e12,this.e20 + m.e20,this.e21 + m.e21,this.e22 + m.e22); + } + sub(m) { + return new oimo.common.Mat3(this.e00 - m.e00,this.e01 - m.e01,this.e02 - m.e02,this.e10 - m.e10,this.e11 - m.e11,this.e12 - m.e12,this.e20 - m.e20,this.e21 - m.e21,this.e22 - m.e22); + } + scale(s) { + return new oimo.common.Mat3(this.e00 * s,this.e01 * s,this.e02 * s,this.e10 * s,this.e11 * s,this.e12 * s,this.e20 * s,this.e21 * s,this.e22 * s); + } + mul(m) { + return new oimo.common.Mat3(this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20,this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21,this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22,this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20,this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21,this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22,this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20,this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21,this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22); + } + addEq(m) { + this.e00 += m.e00; + this.e01 += m.e01; + this.e02 += m.e02; + this.e10 += m.e10; + this.e11 += m.e11; + this.e12 += m.e12; + this.e20 += m.e20; + this.e21 += m.e21; + this.e22 += m.e22; + return this; + } + subEq(m) { + this.e00 -= m.e00; + this.e01 -= m.e01; + this.e02 -= m.e02; + this.e10 -= m.e10; + this.e11 -= m.e11; + this.e12 -= m.e12; + this.e20 -= m.e20; + this.e21 -= m.e21; + this.e22 -= m.e22; + return this; + } + scaleEq(s) { + this.e00 *= s; + this.e01 *= s; + this.e02 *= s; + this.e10 *= s; + this.e11 *= s; + this.e12 *= s; + this.e20 *= s; + this.e21 *= s; + this.e22 *= s; + return this; + } + mulEq(m) { + let e01 = this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21; + let e02 = this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22; + let e10 = this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20; + let e11 = this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21; + let e12 = this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22; + let e20 = this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20; + let e21 = this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21; + let e22 = this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22; + this.e00 = this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20; + this.e01 = e01; + this.e02 = e02; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + return this; + } + prependScale(sx,sy,sz) { + return new oimo.common.Mat3(this.e00 * sx,this.e01 * sx,this.e02 * sx,this.e10 * sy,this.e11 * sy,this.e12 * sy,this.e20 * sz,this.e21 * sz,this.e22 * sz); + } + appendScale(sx,sy,sz) { + return new oimo.common.Mat3(this.e00 * sx,this.e01 * sy,this.e02 * sz,this.e10 * sx,this.e11 * sy,this.e12 * sz,this.e20 * sx,this.e21 * sy,this.e22 * sz); + } + prependRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat3(r00 * this.e00 + r01 * this.e10 + r02 * this.e20,r00 * this.e01 + r01 * this.e11 + r02 * this.e21,r00 * this.e02 + r01 * this.e12 + r02 * this.e22,r10 * this.e00 + r11 * this.e10 + r12 * this.e20,r10 * this.e01 + r11 * this.e11 + r12 * this.e21,r10 * this.e02 + r11 * this.e12 + r12 * this.e22,r20 * this.e00 + r21 * this.e10 + r22 * this.e20,r20 * this.e01 + r21 * this.e11 + r22 * this.e21,r20 * this.e02 + r21 * this.e12 + r22 * this.e22); + } + appendRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat3(this.e00 * r00 + this.e01 * r10 + this.e02 * r20,this.e00 * r01 + this.e01 * r11 + this.e02 * r21,this.e00 * r02 + this.e01 * r12 + this.e02 * r22,this.e10 * r00 + this.e11 * r10 + this.e12 * r20,this.e10 * r01 + this.e11 * r11 + this.e12 * r21,this.e10 * r02 + this.e11 * r12 + this.e12 * r22,this.e20 * r00 + this.e21 * r10 + this.e22 * r20,this.e20 * r01 + this.e21 * r11 + this.e22 * r21,this.e20 * r02 + this.e21 * r12 + this.e22 * r22); + } + prependScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sx; + this.e02 *= sx; + this.e10 *= sy; + this.e11 *= sy; + this.e12 *= sy; + this.e20 *= sz; + this.e21 *= sz; + this.e22 *= sz; + return this; + } + appendScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sy; + this.e02 *= sz; + this.e10 *= sx; + this.e11 *= sy; + this.e12 *= sz; + this.e20 *= sx; + this.e21 *= sy; + this.e22 *= sz; + return this; + } + prependRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e10 = r10 * this.e00 + r11 * this.e10 + r12 * this.e20; + let e11 = r10 * this.e01 + r11 * this.e11 + r12 * this.e21; + let e12 = r10 * this.e02 + r11 * this.e12 + r12 * this.e22; + let e20 = r20 * this.e00 + r21 * this.e10 + r22 * this.e20; + let e21 = r20 * this.e01 + r21 * this.e11 + r22 * this.e21; + let e22 = r20 * this.e02 + r21 * this.e12 + r22 * this.e22; + this.e00 = r00 * this.e00 + r01 * this.e10 + r02 * this.e20; + this.e01 = r00 * this.e01 + r01 * this.e11 + r02 * this.e21; + this.e02 = r00 * this.e02 + r01 * this.e12 + r02 * this.e22; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + return this; + } + appendRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e01 = this.e00 * r01 + this.e01 * r11 + this.e02 * r21; + let e02 = this.e00 * r02 + this.e01 * r12 + this.e02 * r22; + let e11 = this.e10 * r01 + this.e11 * r11 + this.e12 * r21; + let e12 = this.e10 * r02 + this.e11 * r12 + this.e12 * r22; + let e21 = this.e20 * r01 + this.e21 * r11 + this.e22 * r21; + let e22 = this.e20 * r02 + this.e21 * r12 + this.e22 * r22; + this.e00 = this.e00 * r00 + this.e01 * r10 + this.e02 * r20; + this.e01 = e01; + this.e02 = e02; + this.e10 = this.e10 * r00 + this.e11 * r10 + this.e12 * r20; + this.e11 = e11; + this.e12 = e12; + this.e20 = this.e20 * r00 + this.e21 * r10 + this.e22 * r20; + this.e21 = e21; + this.e22 = e22; + return this; + } + transpose() { + return new oimo.common.Mat3(this.e00,this.e10,this.e20,this.e01,this.e11,this.e21,this.e02,this.e12,this.e22); + } + transposeEq() { + let e10 = this.e01; + let e20 = this.e02; + let e21 = this.e12; + + this.e01 = this.e10; + this.e02 = this.e20; + this.e10 = e10; + + this.e12 = this.e21; + this.e20 = e20; + this.e21 = e21; + + return this; + } + determinant() { + return this.e00 * (this.e11 * this.e22 - this.e12 * this.e21) - this.e01 * (this.e10 * this.e22 - this.e12 * this.e20) + this.e02 * (this.e10 * this.e21 - this.e11 * this.e20); + } + trace() { + return this.e00 + this.e11 + this.e22; + } + inverse() { + let d00 = this.e11 * this.e22 - this.e12 * this.e21; + let d01 = this.e10 * this.e22 - this.e12 * this.e20; + let d02 = this.e10 * this.e21 - this.e11 * this.e20; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02; + if(invDet != 0) { + invDet = 1 / invDet; + } + return new oimo.common.Mat3(d00 * invDet,-(this.e01 * this.e22 - this.e02 * this.e21) * invDet,(this.e01 * this.e12 - this.e02 * this.e11) * invDet,-d01 * invDet,(this.e00 * this.e22 - this.e02 * this.e20) * invDet,-(this.e00 * this.e12 - this.e02 * this.e10) * invDet,d02 * invDet,-(this.e00 * this.e21 - this.e01 * this.e20) * invDet,(this.e00 * this.e11 - this.e01 * this.e10) * invDet); + } + inverseEq() { + let d00 = this.e11 * this.e22 - this.e12 * this.e21; + let d01 = this.e10 * this.e22 - this.e12 * this.e20; + let d02 = this.e10 * this.e21 - this.e11 * this.e20; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02; + if(invDet != 0) { + invDet = 1 / invDet; + } + let t02 = (this.e01 * this.e12 - this.e02 * this.e11) * invDet; + let t11 = (this.e00 * this.e22 - this.e02 * this.e20) * invDet; + let t12 = -(this.e00 * this.e12 - this.e02 * this.e10) * invDet; + let t21 = -(this.e00 * this.e21 - this.e01 * this.e20) * invDet; + let t22 = (this.e00 * this.e11 - this.e01 * this.e10) * invDet; + this.e00 = d00 * invDet; + this.e01 = -(this.e01 * this.e22 - this.e02 * this.e21) * invDet; + this.e02 = t02; + this.e10 = -d01 * invDet; + this.e11 = t11; + this.e12 = t12; + this.e20 = d02 * invDet; + this.e21 = t21; + this.e22 = t22; + return this; + } + toArray(columnMajor) { + if(columnMajor == null) { + columnMajor = false; + } + if(columnMajor) { + return [this.e00,this.e10,this.e20,this.e01,this.e11,this.e21,this.e02,this.e12,this.e22]; + } else { + return [this.e00,this.e01,this.e02,this.e10,this.e11,this.e12,this.e20,this.e21,this.e22]; + } + } + copyFrom(m) { + this.e00 = m.e00; + this.e01 = m.e01; + this.e02 = m.e02; + this.e10 = m.e10; + this.e11 = m.e11; + this.e12 = m.e12; + this.e20 = m.e20; + this.e21 = m.e21; + this.e22 = m.e22; + return this; + } + clone() { + return new oimo.common.Mat3(this.e00,this.e01,this.e02,this.e10,this.e11,this.e12,this.e20,this.e21,this.e22); + } + fromQuat(q) { + let x = q.x; + let y = q.y; + let z = q.z; + let w = q.w; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this.e00 = 1 - yy - zz; + this.e01 = xy - wz; + this.e02 = xz + wy; + this.e10 = xy + wz; + this.e11 = 1 - xx - zz; + this.e12 = yz - wx; + this.e20 = xz - wy; + this.e21 = yz + wx; + this.e22 = 1 - xx - yy; + return this; + } + toQuat() { + let _this = new oimo.common.Quat(); + let e00 = this.e00; + let e11 = this.e11; + let e22 = this.e22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + _this.w = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e21 - this.e12) * s; + _this.y = (this.e02 - this.e20) * s; + _this.z = (this.e10 - this.e01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + _this.x = 0.5 * s; + s = 0.5 / s; + _this.y = (this.e01 + this.e10) * s; + _this.z = (this.e02 + this.e20) * s; + _this.w = (this.e21 - this.e12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + _this.z = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e02 + this.e20) * s; + _this.y = (this.e12 + this.e21) * s; + _this.w = (this.e10 - this.e01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + _this.y = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e01 + this.e10) * s; + _this.z = (this.e12 + this.e21) * s; + _this.w = (this.e02 - this.e20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + _this.z = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e02 + this.e20) * s; + _this.y = (this.e12 + this.e21) * s; + _this.w = (this.e10 - this.e01) * s; + } + return _this; + } + fromEulerXyz(eulerAngles) { + let sx = Math.sin(eulerAngles.x); + let sy = Math.sin(eulerAngles.y); + let sz = Math.sin(eulerAngles.z); + let cx = Math.cos(eulerAngles.x); + let cy = Math.cos(eulerAngles.y); + let cz = Math.cos(eulerAngles.z); + this.e00 = cy * cz; + this.e01 = -cy * sz; + this.e02 = sy; + this.e10 = cx * sz + cz * sx * sy; + this.e11 = cx * cz - sx * sy * sz; + this.e12 = -cy * sx; + this.e20 = sx * sz - cx * cz * sy; + this.e21 = cz * sx + cx * sy * sz; + this.e22 = cx * cy; + return this; + } + toEulerXyz() { + let sy = this.e02; + if(sy <= -1) { + let xSubZ = Math.atan2(this.e21,this.e11); + return new oimo.common.Vec3(xSubZ * 0.5,-1.570796326794895,-xSubZ * 0.5); + } + if(sy >= 1) { + let xAddZ = Math.atan2(this.e21,this.e11); + return new oimo.common.Vec3(xAddZ * 0.5,1.570796326794895,xAddZ * 0.5); + } + return new oimo.common.Vec3(Math.atan2(-this.e12,this.e22),Math.asin(sy),Math.atan2(-this.e01,this.e00)); + } + getRow(index) { + if(index == 0) { + return new oimo.common.Vec3(this.e00,this.e01,this.e02); + } else if(index == 1) { + return new oimo.common.Vec3(this.e10,this.e11,this.e12); + } else if(index == 2) { + return new oimo.common.Vec3(this.e20,this.e21,this.e22); + } else { + return null; + } + } + getCol(index) { + if(index == 0) { + return new oimo.common.Vec3(this.e00,this.e10,this.e20); + } else if(index == 1) { + return new oimo.common.Vec3(this.e01,this.e11,this.e21); + } else if(index == 2) { + return new oimo.common.Vec3(this.e02,this.e12,this.e22); + } else { + return null; + } + } + getRowTo(index,dst) { + if(index == 0) { + dst.init(this.e00,this.e01,this.e02); + } else if(index == 1) { + dst.init(this.e10,this.e11,this.e12); + } else if(index == 2) { + dst.init(this.e20,this.e21,this.e22); + } else { + dst.zero(); + } + } + getColTo(index,dst) { + if(index == 0) { + dst.init(this.e00,this.e10,this.e20); + } else if(index == 1) { + dst.init(this.e01,this.e11,this.e21); + } else if(index == 2) { + dst.init(this.e02,this.e12,this.e22); + } else { + dst.zero(); + } + } + fromRows(row0,row1,row2) { + this.e00 = row0.x; + this.e01 = row0.y; + this.e02 = row0.z; + this.e10 = row1.x; + this.e11 = row1.y; + this.e12 = row1.z; + this.e20 = row2.x; + this.e21 = row2.y; + this.e22 = row2.z; + return this; + } + fromCols(col0,col1,col2) { + this.e00 = col0.x; + this.e01 = col1.x; + this.e02 = col2.x; + this.e10 = col0.y; + this.e11 = col1.y; + this.e12 = col2.y; + this.e20 = col0.z; + this.e21 = col1.z; + this.e22 = col2.z; + return this; + } + toString() { + return "Mat3[" + (this.e00 > 0 ? (this.e00 * 10000000 + 0.5 | 0) / 10000000 : (this.e00 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e01 > 0 ? (this.e01 * 10000000 + 0.5 | 0) / 10000000 : (this.e01 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e02 > 0 ? (this.e02 * 10000000 + 0.5 | 0) / 10000000 : (this.e02 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e10 > 0 ? (this.e10 * 10000000 + 0.5 | 0) / 10000000 : (this.e10 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e11 > 0 ? (this.e11 * 10000000 + 0.5 | 0) / 10000000 : (this.e11 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e12 > 0 ? (this.e12 * 10000000 + 0.5 | 0) / 10000000 : (this.e12 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e20 > 0 ? (this.e20 * 10000000 + 0.5 | 0) / 10000000 : (this.e20 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e21 > 0 ? (this.e21 * 10000000 + 0.5 | 0) / 10000000 : (this.e21 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e22 > 0 ? (this.e22 * 10000000 + 0.5 | 0) / 10000000 : (this.e22 * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +oimo.common.Mat4 = class oimo_common_Mat4 { + constructor(e00,e01,e02,e03,e10,e11,e12,e13,e20,e21,e22,e23,e30,e31,e32,e33) { + if(e33 == null) { + e33 = 1; + } + if(e32 == null) { + e32 = 0; + } + if(e31 == null) { + e31 = 0; + } + if(e30 == null) { + e30 = 0; + } + if(e23 == null) { + e23 = 0; + } + if(e22 == null) { + e22 = 1; + } + if(e21 == null) { + e21 = 0; + } + if(e20 == null) { + e20 = 0; + } + if(e13 == null) { + e13 = 0; + } + if(e12 == null) { + e12 = 0; + } + if(e11 == null) { + e11 = 1; + } + if(e10 == null) { + e10 = 0; + } + if(e03 == null) { + e03 = 0; + } + if(e02 == null) { + e02 = 0; + } + if(e01 == null) { + e01 = 0; + } + if(e00 == null) { + e00 = 1; + } + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e03 = e03; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + oimo.common.Mat4.numCreations++; + } + init(e00,e01,e02,e03,e10,e11,e12,e13,e20,e21,e22,e23,e30,e31,e32,e33) { + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e03 = e03; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + return this; + } + identity() { + this.e00 = 1; + this.e01 = 0; + this.e02 = 0; + this.e03 = 0; + this.e10 = 0; + this.e11 = 1; + this.e12 = 0; + this.e13 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = 1; + this.e23 = 0; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + add(m) { + return new oimo.common.Mat4(this.e00 + m.e00,this.e01 + m.e01,this.e02 + m.e02,this.e03 + m.e03,this.e10 + m.e10,this.e11 + m.e11,this.e12 + m.e12,this.e13 + m.e13,this.e20 + m.e20,this.e21 + m.e21,this.e22 + m.e22,this.e23 + m.e23,this.e30 + m.e30,this.e31 + m.e31,this.e32 + m.e32,this.e33 + m.e33); + } + sub(m) { + return new oimo.common.Mat4(this.e00 - m.e00,this.e01 - m.e01,this.e02 - m.e02,this.e03 - m.e03,this.e10 - m.e10,this.e11 - m.e11,this.e12 - m.e12,this.e13 - m.e13,this.e20 - m.e20,this.e21 - m.e21,this.e22 - m.e22,this.e23 - m.e23,this.e30 - m.e30,this.e31 - m.e31,this.e32 - m.e32,this.e33 - m.e33); + } + scale(s) { + return new oimo.common.Mat4(this.e00 * s,this.e01 * s,this.e02 * s,this.e03 * s,this.e10 * s,this.e11 * s,this.e12 * s,this.e13 * s,this.e20 * s,this.e21 * s,this.e22 * s,this.e23 * s,this.e30 * s,this.e31 * s,this.e32 * s,this.e33 * s); + } + mul(m) { + return new oimo.common.Mat4(this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20 + this.e03 * m.e30,this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21 + this.e03 * m.e31,this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22 + this.e03 * m.e32,this.e00 * m.e03 + this.e01 * m.e13 + this.e02 * m.e23 + this.e03 * m.e33,this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20 + this.e13 * m.e30,this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21 + this.e13 * m.e31,this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22 + this.e13 * m.e32,this.e10 * m.e03 + this.e11 * m.e13 + this.e12 * m.e23 + this.e13 * m.e33,this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20 + this.e23 * m.e30,this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21 + this.e23 * m.e31,this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22 + this.e23 * m.e32,this.e20 * m.e03 + this.e21 * m.e13 + this.e22 * m.e23 + this.e23 * m.e33,this.e30 * m.e00 + this.e31 * m.e10 + this.e32 * m.e20 + this.e33 * m.e30,this.e30 * m.e01 + this.e31 * m.e11 + this.e32 * m.e21 + this.e33 * m.e31,this.e30 * m.e02 + this.e31 * m.e12 + this.e32 * m.e22 + this.e33 * m.e32,this.e30 * m.e03 + this.e31 * m.e13 + this.e32 * m.e23 + this.e33 * m.e33); + } + addEq(m) { + this.e00 += m.e00; + this.e01 += m.e01; + this.e02 += m.e02; + this.e03 += m.e03; + this.e10 += m.e10; + this.e11 += m.e11; + this.e12 += m.e12; + this.e13 += m.e13; + this.e20 += m.e20; + this.e21 += m.e21; + this.e22 += m.e22; + this.e23 += m.e23; + this.e30 += m.e30; + this.e31 += m.e31; + this.e32 += m.e32; + this.e33 += m.e33; + return this; + } + subEq(m) { + this.e00 -= m.e00; + this.e01 -= m.e01; + this.e02 -= m.e02; + this.e03 -= m.e03; + this.e10 -= m.e10; + this.e11 -= m.e11; + this.e12 -= m.e12; + this.e13 -= m.e13; + this.e20 -= m.e20; + this.e21 -= m.e21; + this.e22 -= m.e22; + this.e23 -= m.e23; + this.e30 -= m.e30; + this.e31 -= m.e31; + this.e32 -= m.e32; + this.e33 -= m.e33; + return this; + } + scaleEq(s) { + this.e00 *= s; + this.e01 *= s; + this.e02 *= s; + this.e03 *= s; + this.e10 *= s; + this.e11 *= s; + this.e12 *= s; + this.e13 *= s; + this.e20 *= s; + this.e21 *= s; + this.e22 *= s; + this.e23 *= s; + this.e30 *= s; + this.e31 *= s; + this.e32 *= s; + this.e33 *= s; + return this; + } + mulEq(m) { + let e01 = this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21 + this.e03 * m.e31; + let e02 = this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22 + this.e03 * m.e32; + let e03 = this.e00 * m.e03 + this.e01 * m.e13 + this.e02 * m.e23 + this.e03 * m.e33; + let e10 = this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20 + this.e13 * m.e30; + let e11 = this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21 + this.e13 * m.e31; + let e12 = this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22 + this.e13 * m.e32; + let e13 = this.e10 * m.e03 + this.e11 * m.e13 + this.e12 * m.e23 + this.e13 * m.e33; + let e20 = this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20 + this.e23 * m.e30; + let e21 = this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21 + this.e23 * m.e31; + let e22 = this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22 + this.e23 * m.e32; + let e23 = this.e20 * m.e03 + this.e21 * m.e13 + this.e22 * m.e23 + this.e23 * m.e33; + let e30 = this.e30 * m.e00 + this.e31 * m.e10 + this.e32 * m.e20 + this.e33 * m.e30; + let e31 = this.e30 * m.e01 + this.e31 * m.e11 + this.e32 * m.e21 + this.e33 * m.e31; + let e32 = this.e30 * m.e02 + this.e31 * m.e12 + this.e32 * m.e22 + this.e33 * m.e32; + let e33 = this.e30 * m.e03 + this.e31 * m.e13 + this.e32 * m.e23 + this.e33 * m.e33; + this.e00 = this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20 + this.e03 * m.e30; + this.e01 = e01; + this.e02 = e02; + this.e03 = e03; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + return this; + } + prependScale(sx,sy,sz) { + return new oimo.common.Mat4(this.e00 * sx,this.e01 * sx,this.e02 * sx,this.e03 * sx,this.e10 * sy,this.e11 * sy,this.e12 * sy,this.e13 * sy,this.e20 * sz,this.e21 * sz,this.e22 * sz,this.e23 * sz,this.e30,this.e31,this.e32,this.e33); + } + appendScale(sx,sy,sz) { + return new oimo.common.Mat4(this.e00 * sx,this.e01 * sy,this.e02 * sz,this.e03,this.e10 * sx,this.e11 * sy,this.e12 * sz,this.e13,this.e20 * sx,this.e21 * sy,this.e22 * sz,this.e23,this.e30 * sx,this.e31 * sy,this.e32 * sz,this.e33); + } + prependRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat4(r00 * this.e00 + r01 * this.e10 + r02 * this.e20,r00 * this.e01 + r01 * this.e11 + r02 * this.e21,r00 * this.e02 + r01 * this.e12 + r02 * this.e22,r00 * this.e03 + r01 * this.e13 + r02 * this.e23,r10 * this.e00 + r11 * this.e10 + r12 * this.e20,r10 * this.e01 + r11 * this.e11 + r12 * this.e21,r10 * this.e02 + r11 * this.e12 + r12 * this.e22,r10 * this.e03 + r11 * this.e13 + r12 * this.e23,r20 * this.e00 + r21 * this.e10 + r22 * this.e20,r20 * this.e01 + r21 * this.e11 + r22 * this.e21,r20 * this.e02 + r21 * this.e12 + r22 * this.e22,r20 * this.e03 + r21 * this.e13 + r22 * this.e23,this.e30,this.e31,this.e32,this.e33); + } + appendRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat4(this.e00 * r00 + this.e01 * r10 + this.e02 * r20,this.e00 * r01 + this.e01 * r11 + this.e02 * r21,this.e00 * r02 + this.e01 * r12 + this.e02 * r22,this.e03,this.e10 * r00 + this.e11 * r10 + this.e12 * r20,this.e10 * r01 + this.e11 * r11 + this.e12 * r21,this.e10 * r02 + this.e11 * r12 + this.e12 * r22,this.e13,this.e20 * r00 + this.e21 * r10 + this.e22 * r20,this.e20 * r01 + this.e21 * r11 + this.e22 * r21,this.e20 * r02 + this.e21 * r12 + this.e22 * r22,this.e23,this.e30 * r00 + this.e31 * r10 + this.e32 * r20,this.e30 * r01 + this.e31 * r11 + this.e32 * r21,this.e30 * r02 + this.e31 * r12 + this.e32 * r22,this.e33); + } + prependTranslation(tx,ty,tz) { + return new oimo.common.Mat4(this.e00 + tx * this.e30,this.e01 + tx * this.e31,this.e02 + tx * this.e32,this.e03 + tx * this.e33,this.e10 + ty * this.e30,this.e11 + ty * this.e31,this.e12 + ty * this.e32,this.e13 + ty * this.e33,this.e20 + tz * this.e30,this.e21 + tz * this.e31,this.e22 + tz * this.e32,this.e23 + tz * this.e33,this.e30,this.e31,this.e32,this.e33); + } + appendTranslation(tx,ty,tz) { + return new oimo.common.Mat4(this.e00,this.e01,this.e02,this.e00 * tx + this.e01 * ty + this.e02 * tz + this.e03,this.e10,this.e11,this.e12,this.e10 * tx + this.e11 * ty + this.e12 * tz + this.e13,this.e20,this.e21,this.e22,this.e20 * tx + this.e21 * ty + this.e22 * tz + this.e23,this.e30,this.e31,this.e32,this.e30 * tx + this.e31 * ty + this.e32 * tz + this.e33); + } + prependScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sx; + this.e02 *= sx; + this.e03 *= sx; + this.e10 *= sy; + this.e11 *= sy; + this.e12 *= sy; + this.e13 *= sy; + this.e20 *= sz; + this.e21 *= sz; + this.e22 *= sz; + this.e23 *= sz; + + + + + return this; + } + appendScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sy; + this.e02 *= sz; + + this.e10 *= sx; + this.e11 *= sy; + this.e12 *= sz; + + this.e20 *= sx; + this.e21 *= sy; + this.e22 *= sz; + + this.e30 *= sx; + this.e31 *= sy; + this.e32 *= sz; + + return this; + } + prependRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e10 = r10 * this.e00 + r11 * this.e10 + r12 * this.e20; + let e11 = r10 * this.e01 + r11 * this.e11 + r12 * this.e21; + let e12 = r10 * this.e02 + r11 * this.e12 + r12 * this.e22; + let e13 = r10 * this.e03 + r11 * this.e13 + r12 * this.e23; + let e20 = r20 * this.e00 + r21 * this.e10 + r22 * this.e20; + let e21 = r20 * this.e01 + r21 * this.e11 + r22 * this.e21; + let e22 = r20 * this.e02 + r21 * this.e12 + r22 * this.e22; + let e23 = r20 * this.e03 + r21 * this.e13 + r22 * this.e23; + this.e00 = r00 * this.e00 + r01 * this.e10 + r02 * this.e20; + this.e01 = r00 * this.e01 + r01 * this.e11 + r02 * this.e21; + this.e02 = r00 * this.e02 + r01 * this.e12 + r02 * this.e22; + this.e03 = r00 * this.e03 + r01 * this.e13 + r02 * this.e23; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + + + + + return this; + } + appendRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e01 = this.e00 * r01 + this.e01 * r11 + this.e02 * r21; + let e02 = this.e00 * r02 + this.e01 * r12 + this.e02 * r22; + let e11 = this.e10 * r01 + this.e11 * r11 + this.e12 * r21; + let e12 = this.e10 * r02 + this.e11 * r12 + this.e12 * r22; + let e21 = this.e20 * r01 + this.e21 * r11 + this.e22 * r21; + let e22 = this.e20 * r02 + this.e21 * r12 + this.e22 * r22; + let e31 = this.e30 * r01 + this.e31 * r11 + this.e32 * r21; + let e32 = this.e30 * r02 + this.e31 * r12 + this.e32 * r22; + this.e00 = this.e00 * r00 + this.e01 * r10 + this.e02 * r20; + this.e01 = e01; + this.e02 = e02; + + this.e10 = this.e10 * r00 + this.e11 * r10 + this.e12 * r20; + this.e11 = e11; + this.e12 = e12; + + this.e20 = this.e20 * r00 + this.e21 * r10 + this.e22 * r20; + this.e21 = e21; + this.e22 = e22; + + this.e30 = this.e30 * r00 + this.e31 * r10 + this.e32 * r20; + this.e31 = e31; + this.e32 = e32; + + return this; + } + prependTranslationEq(tx,ty,tz) { + this.e00 += tx * this.e30; + this.e01 += tx * this.e31; + this.e02 += tx * this.e32; + this.e03 += tx * this.e33; + this.e10 += ty * this.e30; + this.e11 += ty * this.e31; + this.e12 += ty * this.e32; + this.e13 += ty * this.e33; + this.e20 += tz * this.e30; + this.e21 += tz * this.e31; + this.e22 += tz * this.e32; + this.e23 += tz * this.e33; + + + + + return this; + } + appendTranslationEq(tx,ty,tz) { + let e03 = this.e00 * tx + this.e01 * ty + this.e02 * tz + this.e03; + let e13 = this.e10 * tx + this.e11 * ty + this.e12 * tz + this.e13; + let e23 = this.e20 * tx + this.e21 * ty + this.e22 * tz + this.e23; + let e33 = this.e30 * tx + this.e31 * ty + this.e32 * tz + this.e33; + + + + + this.e03 = e03; + + + + + this.e13 = e13; + + + + + this.e23 = e23; + + + + + this.e33 = e33; + return this; + } + transpose() { + return new oimo.common.Mat4(this.e00,this.e10,this.e20,this.e30,this.e01,this.e11,this.e21,this.e31,this.e02,this.e12,this.e22,this.e32,this.e03,this.e13,this.e23,this.e33); + } + transposeEq() { + let e10 = this.e01; + let e20 = this.e02; + let e21 = this.e12; + let e30 = this.e03; + let e31 = this.e13; + let e32 = this.e23; + + this.e01 = this.e10; + this.e02 = this.e20; + this.e03 = this.e30; + this.e10 = e10; + + this.e12 = this.e21; + this.e13 = this.e31; + this.e20 = e20; + this.e21 = e21; + + this.e23 = this.e32; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + + return this; + } + determinant() { + let d23_01 = this.e20 * this.e31 - this.e21 * this.e30; + let d23_02 = this.e20 * this.e32 - this.e22 * this.e30; + let d23_03 = this.e20 * this.e33 - this.e23 * this.e30; + let d23_12 = this.e21 * this.e32 - this.e22 * this.e31; + let d23_13 = this.e21 * this.e33 - this.e23 * this.e31; + let d23_23 = this.e22 * this.e33 - this.e23 * this.e32; + return this.e00 * (this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12) - this.e01 * (this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02) + this.e02 * (this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01) - this.e03 * (this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01); + } + trace() { + return this.e00 + this.e11 + this.e22 + this.e33; + } + inverse() { + let d01_01 = this.e00 * this.e11 - this.e01 * this.e10; + let d01_02 = this.e00 * this.e12 - this.e02 * this.e10; + let d01_03 = this.e00 * this.e13 - this.e03 * this.e10; + let d01_12 = this.e01 * this.e12 - this.e02 * this.e11; + let d01_13 = this.e01 * this.e13 - this.e03 * this.e11; + let d01_23 = this.e02 * this.e13 - this.e03 * this.e12; + let d23_01 = this.e20 * this.e31 - this.e21 * this.e30; + let d23_02 = this.e20 * this.e32 - this.e22 * this.e30; + let d23_03 = this.e20 * this.e33 - this.e23 * this.e30; + let d23_12 = this.e21 * this.e32 - this.e22 * this.e31; + let d23_13 = this.e21 * this.e33 - this.e23 * this.e31; + let d23_23 = this.e22 * this.e33 - this.e23 * this.e32; + let d00 = this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12; + let d01 = this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02; + let d02 = this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01; + let d03 = this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02 - this.e03 * d03; + if(invDet != 0) { + invDet = 1 / invDet; + } + return new oimo.common.Mat4(d00 * invDet,-(this.e01 * d23_23 - this.e02 * d23_13 + this.e03 * d23_12) * invDet,(this.e31 * d01_23 - this.e32 * d01_13 + this.e33 * d01_12) * invDet,-(this.e21 * d01_23 - this.e22 * d01_13 + this.e23 * d01_12) * invDet,-d01 * invDet,(this.e00 * d23_23 - this.e02 * d23_03 + this.e03 * d23_02) * invDet,-(this.e30 * d01_23 - this.e32 * d01_03 + this.e33 * d01_02) * invDet,(this.e20 * d01_23 - this.e22 * d01_03 + this.e23 * d01_02) * invDet,d02 * invDet,-(this.e00 * d23_13 - this.e01 * d23_03 + this.e03 * d23_01) * invDet,(this.e30 * d01_13 - this.e31 * d01_03 + this.e33 * d01_01) * invDet,-(this.e20 * d01_13 - this.e21 * d01_03 + this.e23 * d01_01) * invDet,-d03 * invDet,(this.e00 * d23_12 - this.e01 * d23_02 + this.e02 * d23_01) * invDet,-(this.e30 * d01_12 - this.e31 * d01_02 + this.e32 * d01_01) * invDet,(this.e20 * d01_12 - this.e21 * d01_02 + this.e22 * d01_01) * invDet); + } + inverseEq() { + let d01_01 = this.e00 * this.e11 - this.e01 * this.e10; + let d01_02 = this.e00 * this.e12 - this.e02 * this.e10; + let d01_03 = this.e00 * this.e13 - this.e03 * this.e10; + let d01_12 = this.e01 * this.e12 - this.e02 * this.e11; + let d01_13 = this.e01 * this.e13 - this.e03 * this.e11; + let d01_23 = this.e02 * this.e13 - this.e03 * this.e12; + let d23_01 = this.e20 * this.e31 - this.e21 * this.e30; + let d23_02 = this.e20 * this.e32 - this.e22 * this.e30; + let d23_03 = this.e20 * this.e33 - this.e23 * this.e30; + let d23_12 = this.e21 * this.e32 - this.e22 * this.e31; + let d23_13 = this.e21 * this.e33 - this.e23 * this.e31; + let d23_23 = this.e22 * this.e33 - this.e23 * this.e32; + let d00 = this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12; + let d01 = this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02; + let d02 = this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01; + let d03 = this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02 - this.e03 * d03; + if(invDet != 0) { + invDet = 1 / invDet; + } + let t11 = (this.e00 * d23_23 - this.e02 * d23_03 + this.e03 * d23_02) * invDet; + let t21 = -(this.e00 * d23_13 - this.e01 * d23_03 + this.e03 * d23_01) * invDet; + let t23 = -(this.e20 * d01_13 - this.e21 * d01_03 + this.e23 * d01_01) * invDet; + let t31 = (this.e00 * d23_12 - this.e01 * d23_02 + this.e02 * d23_01) * invDet; + let t32 = -(this.e30 * d01_12 - this.e31 * d01_02 + this.e32 * d01_01) * invDet; + let t33 = (this.e20 * d01_12 - this.e21 * d01_02 + this.e22 * d01_01) * invDet; + this.e00 = d00 * invDet; + this.e01 = -(this.e01 * d23_23 - this.e02 * d23_13 + this.e03 * d23_12) * invDet; + this.e02 = (this.e31 * d01_23 - this.e32 * d01_13 + this.e33 * d01_12) * invDet; + this.e03 = -(this.e21 * d01_23 - this.e22 * d01_13 + this.e23 * d01_12) * invDet; + this.e10 = -d01 * invDet; + this.e11 = t11; + this.e12 = -(this.e30 * d01_23 - this.e32 * d01_03 + this.e33 * d01_02) * invDet; + this.e13 = (this.e20 * d01_23 - this.e22 * d01_03 + this.e23 * d01_02) * invDet; + this.e20 = d02 * invDet; + this.e21 = t21; + this.e22 = (this.e30 * d01_13 - this.e31 * d01_03 + this.e33 * d01_01) * invDet; + this.e23 = t23; + this.e30 = -d03 * invDet; + this.e31 = t31; + this.e32 = t32; + this.e33 = t33; + return this; + } + lookAt(eyeX,eyeY,eyeZ,atX,atY,atZ,upX,upY,upZ) { + let zx = eyeX - atX; + let zy = eyeY - atY; + let zz = eyeZ - atZ; + let tmp = 1 / Math.sqrt(zx * zx + zy * zy + zz * zz); + zx *= tmp; + zy *= tmp; + zz *= tmp; + let xx = upY * zz - upZ * zy; + let xy = upZ * zx - upX * zz; + let xz = upX * zy - upY * zx; + tmp = 1 / Math.sqrt(xx * xx + xy * xy + xz * xz); + xx *= tmp; + xy *= tmp; + xz *= tmp; + let yx = zy * xz - zz * xy; + let yy = zz * xx - zx * xz; + let yz = zx * xy - zy * xx; + this.e00 = xx; + this.e01 = xy; + this.e02 = xz; + this.e03 = -(xx * eyeX + xy * eyeY + xz * eyeZ); + this.e10 = yx; + this.e11 = yy; + this.e12 = yz; + this.e13 = -(yx * eyeX + yy * eyeY + yz * eyeZ); + this.e20 = zx; + this.e21 = zy; + this.e22 = zz; + this.e23 = -(zx * eyeX + zy * eyeY + zz * eyeZ); + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + perspective(fovY,aspect,near,far) { + let h = 1 / Math.tan(fovY * 0.5); + let fnf = far / (near - far); + this.e00 = h / aspect; + this.e01 = 0; + this.e02 = 0; + this.e03 = 0; + this.e10 = 0; + this.e11 = h; + this.e12 = 0; + this.e13 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = fnf; + this.e23 = near * fnf; + this.e30 = 0; + this.e31 = 0; + this.e32 = -1; + this.e33 = 0; + return this; + } + ortho(width,height,near,far) { + let nf = 1 / (near - far); + this.e00 = 2 / width; + this.e01 = 0; + this.e02 = 0; + this.e03 = 0; + this.e10 = 0; + this.e11 = 2 / height; + this.e12 = 0; + this.e13 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = nf; + this.e23 = near * nf; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + toArray(columnMajor) { + if(columnMajor == null) { + columnMajor = false; + } + if(columnMajor) { + return [this.e00,this.e10,this.e20,this.e30,this.e01,this.e11,this.e21,this.e31,this.e02,this.e12,this.e22,this.e32,this.e03,this.e13,this.e23,this.e33]; + } else { + return [this.e00,this.e01,this.e02,this.e03,this.e10,this.e11,this.e12,this.e13,this.e20,this.e21,this.e22,this.e23,this.e30,this.e31,this.e32,this.e33]; + } + } + copyFrom(m) { + this.e00 = m.e00; + this.e01 = m.e01; + this.e02 = m.e02; + this.e03 = m.e03; + this.e10 = m.e10; + this.e11 = m.e11; + this.e12 = m.e12; + this.e13 = m.e13; + this.e20 = m.e20; + this.e21 = m.e21; + this.e22 = m.e22; + this.e23 = m.e23; + this.e30 = m.e30; + this.e31 = m.e31; + this.e32 = m.e32; + this.e33 = m.e33; + return this; + } + fromMat3(m) { + this.e00 = m.e00; + this.e01 = m.e01; + this.e02 = m.e02; + this.e03 = 0; + this.e10 = m.e10; + this.e11 = m.e11; + this.e12 = m.e12; + this.e13 = 0; + this.e20 = m.e20; + this.e21 = m.e21; + this.e22 = m.e22; + this.e23 = 0; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + fromTransform(transform) { + this.e00 = transform._rotation00; + this.e01 = transform._rotation01; + this.e02 = transform._rotation02; + this.e10 = transform._rotation10; + this.e11 = transform._rotation11; + this.e12 = transform._rotation12; + this.e20 = transform._rotation20; + this.e21 = transform._rotation21; + this.e22 = transform._rotation22; + this.e03 = transform._positionX; + this.e13 = transform._positionY; + this.e23 = transform._positionZ; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + clone() { + return new oimo.common.Mat4(this.e00,this.e01,this.e02,this.e03,this.e10,this.e11,this.e12,this.e13,this.e20,this.e21,this.e22,this.e23,this.e30,this.e31,this.e32,this.e33); + } + toString() { + return "Mat4[" + (this.e00 > 0 ? (this.e00 * 10000000 + 0.5 | 0) / 10000000 : (this.e00 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e01 > 0 ? (this.e01 * 10000000 + 0.5 | 0) / 10000000 : (this.e01 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e02 > 0 ? (this.e02 * 10000000 + 0.5 | 0) / 10000000 : (this.e02 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e03 > 0 ? (this.e03 * 10000000 + 0.5 | 0) / 10000000 : (this.e03 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e10 > 0 ? (this.e10 * 10000000 + 0.5 | 0) / 10000000 : (this.e10 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e11 > 0 ? (this.e11 * 10000000 + 0.5 | 0) / 10000000 : (this.e11 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e12 > 0 ? (this.e12 * 10000000 + 0.5 | 0) / 10000000 : (this.e12 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e13 > 0 ? (this.e13 * 10000000 + 0.5 | 0) / 10000000 : (this.e13 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e20 > 0 ? (this.e20 * 10000000 + 0.5 | 0) / 10000000 : (this.e20 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e21 > 0 ? (this.e21 * 10000000 + 0.5 | 0) / 10000000 : (this.e21 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e22 > 0 ? (this.e22 * 10000000 + 0.5 | 0) / 10000000 : (this.e22 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e23 > 0 ? (this.e23 * 10000000 + 0.5 | 0) / 10000000 : (this.e23 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e30 > 0 ? (this.e30 * 10000000 + 0.5 | 0) / 10000000 : (this.e30 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e31 > 0 ? (this.e31 * 10000000 + 0.5 | 0) / 10000000 : (this.e31 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e32 > 0 ? (this.e32 * 10000000 + 0.5 | 0) / 10000000 : (this.e32 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e33 > 0 ? (this.e33 * 10000000 + 0.5 | 0) / 10000000 : (this.e33 * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +oimo.common.MathUtil = class oimo_common_MathUtil { + static abs(x) { + if(x > 0) { + return x; + } else { + return -x; + } + } + static sin(x) { + return Math.sin(x); + } + static cos(x) { + return Math.cos(x); + } + static tan(x) { + return Math.tan(x); + } + static asin(x) { + return Math.asin(x); + } + static acos(x) { + return Math.acos(x); + } + static atan(x) { + return Math.atan(x); + } + static safeAsin(x) { + if(x <= -1) { + return -1.570796326794895; + } + if(x >= 1) { + return 1.570796326794895; + } + return Math.asin(x); + } + static safeAcos(x) { + if(x <= -1) { + return 3.14159265358979; + } + if(x >= 1) { + return 0; + } + return Math.acos(x); + } + static atan2(y,x) { + return Math.atan2(y,x); + } + static sqrt(x) { + return Math.sqrt(x); + } + static clamp(x,min,max) { + if(x < min) { + return min; + } else if(x > max) { + return max; + } else { + return x; + } + } + static rand() { + return Math.random(); + } + static randIn(min,max) { + return min + Math.random() * (max - min); + } + static randVec3In(min,max) { + return new oimo.common.Vec3(min + Math.random() * (max - min),min + Math.random() * (max - min),min + Math.random() * (max - min)); + } + static randVec3() { + return new oimo.common.Vec3(-1 + Math.random() * 2,-1 + Math.random() * 2,-1 + Math.random() * 2); + } +} +oimo.common.Pool = class oimo_common_Pool { + constructor() { + this.stackVec3 = new Array(256); + this.sizeVec3 = 0; + this.stackMat3 = new Array(256); + this.sizeMat3 = 0; + this.stackMat4 = new Array(256); + this.sizeMat4 = 0; + this.stackQuat = new Array(256); + this.sizeQuat = 0; + } + vec3() { + if(this.sizeVec3 == 0) { + return new oimo.common.Vec3(); + } else { + return this.stackVec3[--this.sizeVec3]; + } + } + mat3() { + if(this.sizeMat3 == 0) { + return new oimo.common.Mat3(); + } else { + return this.stackMat3[--this.sizeMat3]; + } + } + mat4() { + if(this.sizeMat4 == 0) { + return new oimo.common.Mat4(); + } else { + return this.stackMat4[--this.sizeMat4]; + } + } + quat() { + if(this.sizeQuat == 0) { + return new oimo.common.Quat(); + } else { + return this.stackQuat[--this.sizeQuat]; + } + } + dispose(vec3,mat3,mat4,quat) { + if(vec3 != null) { + vec3.zero(); + if(this.sizeVec3 == this.stackVec3.length) { + let newArray = new Array(this.sizeVec3 << 1); + let _g = 0; + let _g1 = this.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackVec3[i]; + this.stackVec3[i] = null; + } + this.stackVec3 = newArray; + } + this.stackVec3[this.sizeVec3++] = vec3; + } + if(mat3 != null) { + mat3.e00 = 1; + mat3.e01 = 0; + mat3.e02 = 0; + mat3.e10 = 0; + mat3.e11 = 1; + mat3.e12 = 0; + mat3.e20 = 0; + mat3.e21 = 0; + mat3.e22 = 1; + if(this.sizeMat3 == this.stackMat3.length) { + let newArray = new Array(this.sizeMat3 << 1); + let _g = 0; + let _g1 = this.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat3[i]; + this.stackMat3[i] = null; + } + this.stackMat3 = newArray; + } + this.stackMat3[this.sizeMat3++] = mat3; + } + if(mat4 != null) { + mat4.e00 = 1; + mat4.e01 = 0; + mat4.e02 = 0; + mat4.e03 = 0; + mat4.e10 = 0; + mat4.e11 = 1; + mat4.e12 = 0; + mat4.e13 = 0; + mat4.e20 = 0; + mat4.e21 = 0; + mat4.e22 = 1; + mat4.e23 = 0; + mat4.e30 = 0; + mat4.e31 = 0; + mat4.e32 = 0; + mat4.e33 = 1; + if(this.sizeMat4 == this.stackMat4.length) { + let newArray = new Array(this.sizeMat4 << 1); + let _g = 0; + let _g1 = this.sizeMat4; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat4[i]; + this.stackMat4[i] = null; + } + this.stackMat4 = newArray; + } + this.stackMat4[this.sizeMat4++] = mat4; + } + if(quat != null) { + quat.x = 0; + quat.y = 0; + quat.z = 0; + quat.w = 1; + if(this.sizeQuat == this.stackQuat.length) { + let newArray = new Array(this.sizeQuat << 1); + let _g = 0; + let _g1 = this.sizeQuat; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackQuat[i]; + this.stackQuat[i] = null; + } + this.stackQuat = newArray; + } + this.stackQuat[this.sizeQuat++] = quat; + } + } + disposeVec3(v) { + v.zero(); + if(this.sizeVec3 == this.stackVec3.length) { + let newArray = new Array(this.sizeVec3 << 1); + let _g = 0; + let _g1 = this.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackVec3[i]; + this.stackVec3[i] = null; + } + this.stackVec3 = newArray; + } + this.stackVec3[this.sizeVec3++] = v; + } + disposeMat3(m) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(this.sizeMat3 == this.stackMat3.length) { + let newArray = new Array(this.sizeMat3 << 1); + let _g = 0; + let _g1 = this.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat3[i]; + this.stackMat3[i] = null; + } + this.stackMat3 = newArray; + } + this.stackMat3[this.sizeMat3++] = m; + } + disposeMat4(m) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e03 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e13 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + m.e23 = 0; + m.e30 = 0; + m.e31 = 0; + m.e32 = 0; + m.e33 = 1; + if(this.sizeMat4 == this.stackMat4.length) { + let newArray = new Array(this.sizeMat4 << 1); + let _g = 0; + let _g1 = this.sizeMat4; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat4[i]; + this.stackMat4[i] = null; + } + this.stackMat4 = newArray; + } + this.stackMat4[this.sizeMat4++] = m; + } + disposeQuat(q) { + q.x = 0; + q.y = 0; + q.z = 0; + q.w = 1; + if(this.sizeQuat == this.stackQuat.length) { + let newArray = new Array(this.sizeQuat << 1); + let _g = 0; + let _g1 = this.sizeQuat; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackQuat[i]; + this.stackQuat[i] = null; + } + this.stackQuat = newArray; + } + this.stackQuat[this.sizeQuat++] = q; + } +} +oimo.common.Quat = class oimo_common_Quat { + constructor(x,y,z,w) { + if(w == null) { + w = 1; + } + if(z == null) { + z = 0; + } + if(y == null) { + y = 0; + } + if(x == null) { + x = 0; + } + this.x = x; + this.y = y; + this.z = z; + this.w = w; + oimo.common.Quat.numCreations++; + } + identity() { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + return this; + } + init(x,y,z,w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + add(q) { + return new oimo.common.Quat(this.x + q.x,this.y + q.y,this.z + q.z,this.w + q.w); + } + sub(q) { + return new oimo.common.Quat(this.x - q.x,this.y - q.y,this.z - q.z,this.w - q.w); + } + scale(s) { + return new oimo.common.Quat(this.x * s,this.y * s,this.z * s,this.w * s); + } + addEq(q) { + this.x += q.x; + this.y += q.y; + this.z += q.z; + this.w += q.w; + return this; + } + subEq(q) { + this.x -= q.x; + this.y -= q.y; + this.z -= q.z; + this.w -= q.w; + return this; + } + scaleEq(s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.w *= s; + return this; + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + } + lengthSq() { + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + } + dot(q) { + return this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w; + } + normalized() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + if(invLen > 0) { + invLen = 1 / invLen; + } + return new oimo.common.Quat(this.x * invLen,this.y * invLen,this.z * invLen,this.w * invLen); + } + normalize() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + if(invLen > 0) { + invLen = 1 / invLen; + } + this.x *= invLen; + this.y *= invLen; + this.z *= invLen; + this.w *= invLen; + return this; + } + setArc(v1,v2) { + let x1 = v1.x; + let y1 = v1.y; + let z1 = v1.z; + let x2 = v2.x; + let y2 = v2.y; + let z2 = v2.z; + let d = x1 * x2 + y1 * y2 + z1 * z2; + this.w = Math.sqrt((1 + d) * 0.5); + if(this.w == 0) { + x2 = x1 * x1; + y2 = y1 * y1; + z2 = z1 * z1; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this.x = 0; + this.y = z1 * d; + this.z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this.z = 0; + this.x = y1 * d; + this.y = -x1 * d; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this.y = 0; + this.z = x1 * d; + this.x = -z1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this.z = 0; + this.x = y1 * d; + this.y = -x1 * d; + } + return this; + } + d = 0.5 / this.w; + this.x = (y1 * z2 - z1 * y2) * d; + this.y = (z1 * x2 - x1 * z2) * d; + this.z = (x1 * y2 - y1 * x2) * d; + return this; + } + slerp(q,t) { + let qx; + let qy; + let qz; + let qw; + let d = this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w; + if(d < 0) { + d = -d; + qx = -q.x; + qy = -q.y; + qz = -q.z; + qw = -q.w; + } else { + qx = q.x; + qy = q.y; + qz = q.z; + qw = q.w; + } + if(d > 0.999999) { + let _this = new oimo.common.Quat(this.x + (qx - this.x) * t,this.y + (qy - this.y) * t,this.z + (qz - this.z) * t,this.w + (qw - this.w) * t); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z + _this.w * _this.w); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + _this.w *= invLen; + return _this; + } + let theta = t * Math.acos(d); + qx -= this.x * d; + qy -= this.y * d; + qz -= this.z * d; + qw -= this.w * d; + let invLen = 1 / Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw); + qx *= invLen; + qy *= invLen; + qz *= invLen; + qw *= invLen; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + return new oimo.common.Quat(this.x * cos + qx * sin,this.y * cos + qy * sin,this.z * cos + qz * sin,this.w * cos + qw * sin); + } + copyFrom(q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + return this; + } + clone() { + return new oimo.common.Quat(this.x,this.y,this.z,this.w); + } + fromMat3(m) { + let e00 = m.e00; + let e11 = m.e11; + let e22 = m.e22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + this.w = 0.5 * s; + s = 0.5 / s; + this.x = (m.e21 - m.e12) * s; + this.y = (m.e02 - m.e20) * s; + this.z = (m.e10 - m.e01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + this.x = 0.5 * s; + s = 0.5 / s; + this.y = (m.e01 + m.e10) * s; + this.z = (m.e02 + m.e20) * s; + this.w = (m.e21 - m.e12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + this.z = 0.5 * s; + s = 0.5 / s; + this.x = (m.e02 + m.e20) * s; + this.y = (m.e12 + m.e21) * s; + this.w = (m.e10 - m.e01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + this.y = 0.5 * s; + s = 0.5 / s; + this.x = (m.e01 + m.e10) * s; + this.z = (m.e12 + m.e21) * s; + this.w = (m.e02 - m.e20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + this.z = 0.5 * s; + s = 0.5 / s; + this.x = (m.e02 + m.e20) * s; + this.y = (m.e12 + m.e21) * s; + this.w = (m.e10 - m.e01) * s; + } + return this; + } + toMat3() { + let _this = new oimo.common.Mat3(); + let x = this.x; + let y = this.y; + let z = this.z; + let w = this.w; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this.e00 = 1 - yy - zz; + _this.e01 = xy - wz; + _this.e02 = xz + wy; + _this.e10 = xy + wz; + _this.e11 = 1 - xx - zz; + _this.e12 = yz - wx; + _this.e20 = xz - wy; + _this.e21 = yz + wx; + _this.e22 = 1 - xx - yy; + return _this; + } + toString() { + return "Quat[" + (this.x > 0 ? (this.x * 10000000 + 0.5 | 0) / 10000000 : (this.x * 10000000 - 0.5 | 0) / 10000000) + " i,\n" + " " + (this.y > 0 ? (this.y * 10000000 + 0.5 | 0) / 10000000 : (this.y * 10000000 - 0.5 | 0) / 10000000) + " j,\n" + " " + (this.z > 0 ? (this.z * 10000000 + 0.5 | 0) / 10000000 : (this.z * 10000000 - 0.5 | 0) / 10000000) + " k,\n" + " " + (this.w > 0 ? (this.w * 10000000 + 0.5 | 0) / 10000000 : (this.w * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +if(!oimo.dynamics) oimo.dynamics = {}; +oimo.dynamics.Contact = class oimo_dynamics_Contact { + constructor() { + this._next = null; + this._prev = null; + this._link1 = new oimo.dynamics.ContactLink(); + this._link2 = new oimo.dynamics.ContactLink(); + this._s1 = null; + this._s2 = null; + this._b1 = null; + this._b2 = null; + this._detector = null; + this._cachedDetectorData = new oimo.collision.narrowphase.detector.CachedDetectorData(); + this._detectorResult = new oimo.collision.narrowphase.DetectorResult(); + this._latest = false; + this._shouldBeSkipped = false; + this._manifold = new oimo.dynamics.constraint.contact.Manifold(); + this._updater = new oimo.dynamics.constraint.contact.ManifoldUpdater(this._manifold); + this._contactConstraint = new oimo.dynamics.constraint.contact.ContactConstraint(this._manifold); + this._touching = false; + } + _updateManifold() { + if(this._detector == null) { + return; + } + let ptouching = this._touching; + let result = this._detectorResult; + this._detector.detect(result,this._s1._geom,this._s2._geom,this._s1._transform,this._s2._transform,this._cachedDetectorData); + this._touching = result.numPoints > 0; + if(this._touching) { + this._manifold._buildBasis(result.normal); + if(result.getMaxDepth() > oimo.common.Setting.contactUseAlternativePositionCorrectionAlgorithmDepthThreshold) { + this._contactConstraint._positionCorrectionAlgorithm = oimo.common.Setting.alternativeContactPositionCorrectionAlgorithm; + } else { + this._contactConstraint._positionCorrectionAlgorithm = oimo.common.Setting.defaultContactPositionCorrectionAlgorithm; + } + if(result.incremental) { + this._updater.incrementalUpdate(result,this._b1._transform,this._b2._transform); + } else { + this._updater.totalUpdate(result,this._b1._transform,this._b2._transform); + } + } else { + this._manifold._clear(); + } + if(this._touching && !ptouching) { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.beginContact(this); + } + if(cc2 != null) { + cc2.beginContact(this); + } + } + if(!this._touching && ptouching) { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(this); + } + if(cc2 != null) { + cc2.endContact(this); + } + } + if(this._touching) { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.preSolve(this); + } + if(cc2 != null) { + cc2.preSolve(this); + } + } + } + _postSolve() { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.postSolve(this); + } + if(cc2 != null) { + cc2.postSolve(this); + } + } + getShape1() { + return this._s1; + } + getShape2() { + return this._s2; + } + isTouching() { + return this._touching; + } + getManifold() { + return this._manifold; + } + getContactConstraint() { + return this._contactConstraint; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.ContactLink = class oimo_dynamics_ContactLink { + constructor() { + this._prev = null; + this._next = null; + this._contact = null; + this._other = null; + } + getContact() { + return this._contact; + } + getOther() { + return this._other; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.ContactManager = class oimo_dynamics_ContactManager { + constructor(broadPhase) { + this._broadPhase = broadPhase; + this._collisionMatrix = new oimo.collision.narrowphase.CollisionMatrix(); + this._numContacts = 0; + } + createContacts() { + let pp = this._broadPhase._proxyPairList; + while(pp != null) { + let n = pp._next; + while(true) { + let s1; + let s2; + if(pp._p1._id < pp._p2._id) { + s1 = pp._p1.userData; + s2 = pp._p2.userData; + } else { + s1 = pp._p2.userData; + s2 = pp._p1.userData; + } + if(!this.shouldCollide(s1,s2)) { + break; + } + let b1 = s1._rigidBody; + let b2 = s2._rigidBody; + let l; + if(b1._numContactLinks < b2._numContactLinks) { + l = b1._contactLinkList; + } else { + l = b2._contactLinkList; + } + let id1 = s1._id; + let id2 = s2._id; + let found = false; + while(l != null) { + let c = l._contact; + if(c._s1._id == id1 && c._s2._id == id2) { + c._latest = true; + found = true; + break; + } + l = l._next; + } + if(!found) { + let first = this._contactPool; + if(first != null) { + this._contactPool = first._next; + first._next = null; + } else { + first = new oimo.dynamics.Contact(); + } + let c = first; + if(this._contactList == null) { + this._contactList = c; + this._contactListLast = c; + } else { + this._contactListLast._next = c; + c._prev = this._contactListLast; + this._contactListLast = c; + } + c._latest = true; + let detector = this._collisionMatrix.detectors[s1._geom._type][s2._geom._type]; + c._s1 = s1; + c._s2 = s2; + c._b1 = s1._rigidBody; + c._b2 = s2._rigidBody; + c._touching = false; + if(c._b1._contactLinkList == null) { + c._b1._contactLinkList = c._link1; + c._b1._contactLinkListLast = c._link1; + } else { + c._b1._contactLinkListLast._next = c._link1; + c._link1._prev = c._b1._contactLinkListLast; + c._b1._contactLinkListLast = c._link1; + } + if(c._b2._contactLinkList == null) { + c._b2._contactLinkList = c._link2; + c._b2._contactLinkListLast = c._link2; + } else { + c._b2._contactLinkListLast._next = c._link2; + c._link2._prev = c._b2._contactLinkListLast; + c._b2._contactLinkListLast = c._link2; + } + c._b1._numContactLinks++; + c._b2._numContactLinks++; + c._link1._other = c._b2; + c._link2._other = c._b1; + c._link1._contact = c; + c._link2._contact = c; + c._detector = detector; + let _this = c._contactConstraint; + _this._s1 = s1; + _this._s2 = s2; + _this._b1 = _this._s1._rigidBody; + _this._b2 = _this._s2._rigidBody; + _this._tf1 = _this._b1._transform; + _this._tf2 = _this._b2._transform; + this._numContacts++; + } + break; + } + pp = n; + } + } + destroyOutdatedContacts() { + let incremental = this._broadPhase._incremental; + let c = this._contactList; + while(c != null) { + let n = c._next; + while(true) { + if(c._latest) { + c._latest = false; + c._shouldBeSkipped = false; + break; + } + if(!incremental) { + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == this._contactList) { + this._contactList = this._contactList._next; + } + if(c == this._contactListLast) { + this._contactListLast = this._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this = c._contactConstraint; + _this._s1 = null; + _this._s2 = null; + _this._b1 = null; + _this._b2 = null; + _this._tf1 = null; + _this._tf2 = null; + c._next = this._contactPool; + this._contactPool = c; + this._numContacts--; + break; + } + let s1 = c._s1; + let s2 = c._s2; + let r1 = s1._rigidBody; + let r2 = s2._rigidBody; + if(!(!r1._sleeping && r1._type != 1) && !(!r2._sleeping && r2._type != 1)) { + c._shouldBeSkipped = true; + break; + } + let aabb1 = s1._aabb; + let aabb2 = s2._aabb; + let proxy1 = s1._proxy; + let proxy2 = s2._proxy; + if(!(proxy1._aabbMinX < proxy2._aabbMaxX && proxy1._aabbMaxX > proxy2._aabbMinX && proxy1._aabbMinY < proxy2._aabbMaxY && proxy1._aabbMaxY > proxy2._aabbMinY && proxy1._aabbMinZ < proxy2._aabbMaxZ && proxy1._aabbMaxZ > proxy2._aabbMinZ) || !this.shouldCollide(s1,s2)) { + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == this._contactList) { + this._contactList = this._contactList._next; + } + if(c == this._contactListLast) { + this._contactListLast = this._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this = c._contactConstraint; + _this._s1 = null; + _this._s2 = null; + _this._b1 = null; + _this._b2 = null; + _this._tf1 = null; + _this._tf2 = null; + c._next = this._contactPool; + this._contactPool = c; + this._numContacts--; + break; + } + c._shouldBeSkipped = !(aabb1._minX < aabb2._maxX && aabb1._maxX > aabb2._minX && aabb1._minY < aabb2._maxY && aabb1._maxY > aabb2._minY && aabb1._minZ < aabb2._maxZ && aabb1._maxZ > aabb2._minZ); + break; + } + c = n; + } + } + shouldCollide(s1,s2) { + let r1 = s1._rigidBody; + let r2 = s2._rigidBody; + if(r1 == r2) { + return false; + } + if(r1._type != 0 && r2._type != 0) { + return false; + } + if((s1._collisionGroup & s2._collisionMask) == 0 || (s2._collisionGroup & s1._collisionMask) == 0) { + return false; + } + let jl; + let other; + if(r1._numJointLinks < r2._numJointLinks) { + jl = r1._jointLinkList; + other = r2; + } else { + jl = r2._jointLinkList; + other = r1; + } + while(jl != null) { + if(jl._other == other && !jl._joint._allowCollision) { + return false; + } + jl = jl._next; + } + return true; + } + _updateContacts() { + this._broadPhase.collectPairs(); + this.createContacts(); + this.destroyOutdatedContacts(); + } + _postSolve() { + let c = this._contactList; + while(c != null) { + let n = c._next; + if(c._touching) { + c._postSolve(); + } + c = n; + } + } + getNumContacts() { + return this._numContacts; + } + getContactList() { + return this._contactList; + } +} +oimo.dynamics.Island = class oimo_dynamics_Island { + constructor() { + this.rigidBodies = new Array(oimo.common.Setting.islandInitialRigidBodyArraySize); + this.solvers = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this.solversSi = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this.solversNgs = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this.numRigidBodies = 0; + this.numSolvers = 0; + this.numSolversSi = 0; + this.numSolversNgs = 0; + } + _clear() { + while(this.numRigidBodies > 0) this.rigidBodies[--this.numRigidBodies] = null; + while(this.numSolvers > 0) this.solvers[--this.numSolvers] = null; + while(this.numSolversSi > 0) this.solversSi[--this.numSolversSi] = null; + while(this.numSolversNgs > 0) this.solversNgs[--this.numSolversNgs] = null; + } + _addRigidBody(rigidBody) { + if(this.numRigidBodies == this.rigidBodies.length) { + let newArray = new Array(this.numRigidBodies << 1); + let _g = 0; + let _g1 = this.numRigidBodies; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.rigidBodies[i]; + this.rigidBodies[i] = null; + } + this.rigidBodies = newArray; + } + rigidBody._addedToIsland = true; + this.rigidBodies[this.numRigidBodies++] = rigidBody; + } + _addConstraintSolver(solver,positionCorrection) { + if(this.numSolvers == this.solvers.length) { + let newArray = new Array(this.numSolvers << 1); + let _g = 0; + let _g1 = this.numSolvers; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.solvers[i]; + this.solvers[i] = null; + } + this.solvers = newArray; + } + solver._addedToIsland = true; + this.solvers[this.numSolvers++] = solver; + if(positionCorrection == oimo.dynamics.constraint.PositionCorrectionAlgorithm.SPLIT_IMPULSE) { + if(this.numSolversSi == this.solversSi.length) { + let newArray = new Array(this.numSolversSi << 1); + let _g = 0; + let _g1 = this.numSolversSi; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.solversSi[i]; + this.solversSi[i] = null; + } + this.solversSi = newArray; + } + this.solversSi[this.numSolversSi++] = solver; + } + if(positionCorrection == oimo.dynamics.constraint.PositionCorrectionAlgorithm.NGS) { + if(this.numSolversNgs == this.solversNgs.length) { + let newArray = new Array(this.numSolversNgs << 1); + let _g = 0; + let _g1 = this.numSolversNgs; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.solversNgs[i]; + this.solversNgs[i] = null; + } + this.solversNgs = newArray; + } + this.solversNgs[this.numSolversNgs++] = solver; + } + } + _stepSingleRigidBody(timeStep,rb) { + let dt = timeStep.dt; + let dst = rb._ptransform; + let src = rb._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + rb._linearContactImpulseX = 0; + rb._linearContactImpulseY = 0; + rb._linearContactImpulseZ = 0; + rb._angularContactImpulseX = 0; + rb._angularContactImpulseY = 0; + rb._angularContactImpulseZ = 0; + if(rb._autoSleep && rb._velX * rb._velX + rb._velY * rb._velY + rb._velZ * rb._velZ < oimo.common.Setting.sleepingVelocityThreshold * oimo.common.Setting.sleepingVelocityThreshold && rb._angVelX * rb._angVelX + rb._angVelY * rb._angVelY + rb._angVelZ * rb._angVelZ < oimo.common.Setting.sleepingAngularVelocityThreshold * oimo.common.Setting.sleepingAngularVelocityThreshold) { + rb._sleepTime += dt; + if(rb._sleepTime > oimo.common.Setting.sleepingTimeThreshold) { + rb._sleeping = true; + rb._sleepTime = 0; + } + } else { + rb._sleepTime = 0; + } + if(!rb._sleeping) { + if(rb._type == 0) { + let x = dt * rb._linearDamping; + let x2 = x * x; + let linScale = 1 / (1 + x + x2 * (0.5 + x * 0.16666666666666666 + x2 * 0.041666666666666664)); + let x1 = dt * rb._angularDamping; + let x21 = x1 * x1; + let angScale = 1 / (1 + x1 + x21 * (0.5 + x1 * 0.16666666666666666 + x21 * 0.041666666666666664)); + let linAccX; + let linAccY; + let linAccZ; + let angAccX; + let angAccY; + let angAccZ; + linAccX = this.gravityX * rb._gravityScale; + linAccY = this.gravityY * rb._gravityScale; + linAccZ = this.gravityZ * rb._gravityScale; + linAccX += rb._forceX * rb._invMass; + linAccY += rb._forceY * rb._invMass; + linAccZ += rb._forceZ * rb._invMass; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rb._invInertia00 * rb._torqueX + rb._invInertia01 * rb._torqueY + rb._invInertia02 * rb._torqueZ; + __tmp__Y = rb._invInertia10 * rb._torqueX + rb._invInertia11 * rb._torqueY + rb._invInertia12 * rb._torqueZ; + __tmp__Z = rb._invInertia20 * rb._torqueX + rb._invInertia21 * rb._torqueY + rb._invInertia22 * rb._torqueZ; + angAccX = __tmp__X; + angAccY = __tmp__Y; + angAccZ = __tmp__Z; + rb._velX += linAccX * dt; + rb._velY += linAccY * dt; + rb._velZ += linAccZ * dt; + rb._velX *= linScale; + rb._velY *= linScale; + rb._velZ *= linScale; + rb._angVelX += angAccX * dt; + rb._angVelY += angAccY * dt; + rb._angVelZ += angAccZ * dt; + rb._angVelX *= angScale; + rb._angVelY *= angScale; + rb._angVelZ *= angScale; + } + rb._integrate(dt); + let s = rb._shapeList; + while(s != null) { + let n = s._next; + let tf1 = rb._ptransform; + let tf2 = rb._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } + _step(timeStep,numVelocityIterations,numPositionIterations) { + let dt = timeStep.dt; + let sleepIsland = true; + let _g = 0; + let _g1 = this.numRigidBodies; + while(_g < _g1) { + let rb = this.rigidBodies[_g++]; + let dst = rb._ptransform; + let src = rb._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + rb._linearContactImpulseX = 0; + rb._linearContactImpulseY = 0; + rb._linearContactImpulseZ = 0; + rb._angularContactImpulseX = 0; + rb._angularContactImpulseY = 0; + rb._angularContactImpulseZ = 0; + rb._sleeping = false; + if(rb._autoSleep && rb._velX * rb._velX + rb._velY * rb._velY + rb._velZ * rb._velZ < oimo.common.Setting.sleepingVelocityThreshold * oimo.common.Setting.sleepingVelocityThreshold && rb._angVelX * rb._angVelX + rb._angVelY * rb._angVelY + rb._angVelZ * rb._angVelZ < oimo.common.Setting.sleepingAngularVelocityThreshold * oimo.common.Setting.sleepingAngularVelocityThreshold) { + rb._sleepTime += dt; + } else { + rb._sleepTime = 0; + } + if(rb._sleepTime < oimo.common.Setting.sleepingTimeThreshold) { + sleepIsland = false; + } + if(rb._type == 0) { + let x = dt * rb._linearDamping; + let x2 = x * x; + let linScale = 1 / (1 + x + x2 * (0.5 + x * 0.16666666666666666 + x2 * 0.041666666666666664)); + let x1 = dt * rb._angularDamping; + let x21 = x1 * x1; + let angScale = 1 / (1 + x1 + x21 * (0.5 + x1 * 0.16666666666666666 + x21 * 0.041666666666666664)); + let linAccX; + let linAccY; + let linAccZ; + let angAccX; + let angAccY; + let angAccZ; + linAccX = this.gravityX * rb._gravityScale; + linAccY = this.gravityY * rb._gravityScale; + linAccZ = this.gravityZ * rb._gravityScale; + linAccX += rb._forceX * rb._invMass; + linAccY += rb._forceY * rb._invMass; + linAccZ += rb._forceZ * rb._invMass; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rb._invInertia00 * rb._torqueX + rb._invInertia01 * rb._torqueY + rb._invInertia02 * rb._torqueZ; + __tmp__Y = rb._invInertia10 * rb._torqueX + rb._invInertia11 * rb._torqueY + rb._invInertia12 * rb._torqueZ; + __tmp__Z = rb._invInertia20 * rb._torqueX + rb._invInertia21 * rb._torqueY + rb._invInertia22 * rb._torqueZ; + angAccX = __tmp__X; + angAccY = __tmp__Y; + angAccZ = __tmp__Z; + rb._velX += linAccX * dt; + rb._velY += linAccY * dt; + rb._velZ += linAccZ * dt; + rb._velX *= linScale; + rb._velY *= linScale; + rb._velZ *= linScale; + rb._angVelX += angAccX * dt; + rb._angVelY += angAccY * dt; + rb._angVelZ += angAccZ * dt; + rb._angVelX *= angScale; + rb._angVelY *= angScale; + rb._angVelZ *= angScale; + } + } + if(sleepIsland) { + let _g = 0; + let _g1 = this.numRigidBodies; + while(_g < _g1) { + let rb = this.rigidBodies[_g++]; + rb._sleeping = true; + rb._sleepTime = 0; + } + return; + } + let _g2 = 0; + let _g3 = this.numSolvers; + while(_g2 < _g3) this.solvers[_g2++].preSolveVelocity(timeStep); + let _g4 = 0; + let _g5 = this.numSolvers; + while(_g4 < _g5) this.solvers[_g4++].warmStart(timeStep); + let _g6 = 0; + while(_g6 < numVelocityIterations) { + ++_g6; + let _g = 0; + let _g1 = this.numSolvers; + while(_g < _g1) this.solvers[_g++].solveVelocity(); + } + let _g7 = 0; + let _g8 = this.numSolvers; + while(_g7 < _g8) this.solvers[_g7++].postSolveVelocity(timeStep); + let _g9 = 0; + let _g10 = this.numRigidBodies; + while(_g9 < _g10) this.rigidBodies[_g9++]._integrate(dt); + let _g11 = 0; + let _g12 = this.numSolversSi; + while(_g11 < _g12) this.solversSi[_g11++].preSolvePosition(timeStep); + let _g13 = 0; + while(_g13 < numPositionIterations) { + ++_g13; + let _g = 0; + let _g1 = this.numSolversSi; + while(_g < _g1) this.solversSi[_g++].solvePositionSplitImpulse(); + } + let _g14 = 0; + let _g15 = this.numRigidBodies; + while(_g14 < _g15) this.rigidBodies[_g14++]._integratePseudoVelocity(); + let _g16 = 0; + let _g17 = this.numSolversNgs; + while(_g16 < _g17) this.solversNgs[_g16++].preSolvePosition(timeStep); + let _g18 = 0; + while(_g18 < numPositionIterations) { + ++_g18; + let _g = 0; + let _g1 = this.numSolversNgs; + while(_g < _g1) this.solversNgs[_g++].solvePositionNgs(timeStep); + } + let _g19 = 0; + let _g20 = this.numSolvers; + while(_g19 < _g20) this.solvers[_g19++].postSolve(); + let _g21 = 0; + let _g22 = this.numRigidBodies; + while(_g21 < _g22) { + let rb = this.rigidBodies[_g21++]; + let s = rb._shapeList; + while(s != null) { + let n = s._next; + let tf1 = rb._ptransform; + let tf2 = rb._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } +} +oimo.dynamics.TimeStep = class oimo_dynamics_TimeStep { + constructor() { + this.dt = 0; + this.invDt = 0; + this.dtRatio = 1; + } +} +oimo.dynamics.World = class oimo_dynamics_World { + constructor(broadPhaseType,gravity) { + if(broadPhaseType == null) { + broadPhaseType = 2; + } + switch(broadPhaseType) { + case 1: + this._broadPhase = new oimo.collision.broadphase.bruteforce.BruteForceBroadPhase(); + break; + case 2: + this._broadPhase = new oimo.collision.broadphase.bvh.BvhBroadPhase(); + break; + } + this._contactManager = new oimo.dynamics.ContactManager(this._broadPhase); + if(gravity == null) { + gravity = new oimo.common.Vec3(0,-9.80665,0); + } + this._gravity = new oimo.common.Vec3(gravity.x,gravity.y,gravity.z); + this._rigidBodyList = null; + this._rigidBodyListLast = null; + this._jointList = null; + this._jointListLast = null; + this._numRigidBodies = 0; + this._numShapes = 0; + this._numJoints = 0; + this._numIslands = 0; + this._numVelocityIterations = 10; + this._numPositionIterations = 5; + this._rayCastWrapper = new oimo.dynamics._World.RayCastWrapper(); + this._convexCastWrapper = new oimo.dynamics._World.ConvexCastWrapper(); + this._aabbTestWrapper = new oimo.dynamics._World.AabbTestWrapper(); + this._island = new oimo.dynamics.Island(); + this._solversInIslands = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this._rigidBodyStack = new Array(oimo.common.Setting.islandInitialRigidBodyArraySize); + this._timeStep = new oimo.dynamics.TimeStep(); + this._pool = new oimo.common.Pool(); + this._shapeIdCount = 0; + } + _updateContacts() { + let st = Date.now() / 1000; + this._contactManager._updateContacts(); + oimo.dynamics.common.Performance.broadPhaseCollisionTime = (Date.now() / 1000 - st) * 1000; + let st1 = Date.now() / 1000; + let c = this._contactManager._contactList; + while(c != null) { + let n = c._next; + if(!c._shouldBeSkipped) { + c._updateManifold(); + } + c = n; + } + oimo.dynamics.common.Performance.narrowPhaseCollisionTime = (Date.now() / 1000 - st1) * 1000; + } + _solveIslands() { + let st = Date.now() / 1000; + if(oimo.common.Setting.disableSleeping) { + let b = this._rigidBodyList; + while(b != null) { + b._sleeping = false; + b._sleepTime = 0; + b = b._next; + } + } + if(this._rigidBodyStack.length < this._numRigidBodies) { + let newStackSize = this._rigidBodyStack.length << 1; + while(newStackSize < this._numRigidBodies) newStackSize <<= 1; + this._rigidBodyStack = new Array(newStackSize); + } + this._numIslands = 0; + let _this = this._island; + let gravity = this._gravity; + _this.gravityX = gravity.x; + _this.gravityY = gravity.y; + _this.gravityZ = gravity.z; + let b = this._rigidBodyList; + this._numSolversInIslands = 0; + while(b != null) { + let n = b._next; + while(!(b._addedToIsland || b._sleeping || b._type == 1)) { + if(b._numContactLinks == 0 && b._numJointLinks == 0) { + this._island._stepSingleRigidBody(this._timeStep,b); + this._numIslands++; + break; + } + this.buildIsland(b); + this._island._step(this._timeStep,this._numVelocityIterations,this._numPositionIterations); + this._island._clear(); + this._numIslands++; + break; + } + b = n; + } + this._contactManager._postSolve(); + b = this._rigidBodyList; + while(b != null) { + b._addedToIsland = false; + b = b._next; + } + b = this._rigidBodyList; + while(b != null) { + b._forceX = 0; + b._forceY = 0; + b._forceZ = 0; + b._torqueX = 0; + b._torqueY = 0; + b._torqueZ = 0; + b = b._next; + } + while(this._numSolversInIslands > 0) { + this._solversInIslands[--this._numSolversInIslands]._addedToIsland = false; + this._solversInIslands[this._numSolversInIslands] = null; + } + oimo.dynamics.common.Performance.dynamicsTime = (Date.now() / 1000 - st) * 1000; + } + buildIsland(base) { + let stackCount = 1; + this._island._addRigidBody(base); + this._rigidBodyStack[0] = base; + while(stackCount > 0) { + let rb = this._rigidBodyStack[--stackCount]; + this._rigidBodyStack[stackCount] = null; + if(rb._type == 1) { + continue; + } + let cl = rb._contactLinkList; + while(cl != null) { + let n = cl._next; + let cc = cl._contact._contactConstraint; + let ccs = cl._contact._contactConstraint._solver; + if(cc.isTouching() && !ccs._addedToIsland) { + if(this._solversInIslands.length == this._numSolversInIslands) { + let newArray = new Array(this._numSolversInIslands << 1); + let _g = 0; + let _g1 = this._numSolversInIslands; + while(_g < _g1) { + let i = _g++; + newArray[i] = this._solversInIslands[i]; + this._solversInIslands[i] = null; + } + this._solversInIslands = newArray; + } + this._solversInIslands[this._numSolversInIslands++] = ccs; + this._island._addConstraintSolver(ccs,cc._positionCorrectionAlgorithm); + let other = cl._other; + if(!other._addedToIsland) { + this._island._addRigidBody(other); + this._rigidBodyStack[stackCount++] = other; + } + } + cl = n; + } + let jl = rb._jointLinkList; + while(jl != null) { + let n = jl._next; + let j = jl._joint; + let js1 = j._solver; + if(!js1._addedToIsland) { + if(this._solversInIslands.length == this._numSolversInIslands) { + let newArray = new Array(this._numSolversInIslands << 1); + let _g = 0; + let _g1 = this._numSolversInIslands; + while(_g < _g1) { + let i = _g++; + newArray[i] = this._solversInIslands[i]; + this._solversInIslands[i] = null; + } + this._solversInIslands = newArray; + } + this._solversInIslands[this._numSolversInIslands++] = js1; + this._island._addConstraintSolver(js1,j._positionCorrectionAlgorithm); + let other = jl._other; + if(!other._addedToIsland) { + this._island._addRigidBody(other); + this._rigidBodyStack[stackCount++] = other; + } + } + jl = n; + } + } + } + _drawBvh(d,tree) { + if(d.drawBvh) { + this._drawBvhNode(d,tree._root,0,d.style.bvhNodeColor); + } + } + _drawBvhNode(d,node,level,color) { + if(node == null) { + return; + } + if(level >= d.drawBvhMinLevel && level <= d.drawBvhMaxLevel) { + let _this = this._pool; + let min = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let max = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = min; + v.x = node._aabbMinX; + v.y = node._aabbMinY; + v.z = node._aabbMinZ; + let v1 = max; + v1.x = node._aabbMaxX; + v1.y = node._aabbMaxY; + v1.z = node._aabbMaxZ; + d.aabb(min,max,color); + let _this2 = this._pool; + if(min != null) { + min.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = min; + } + let _this3 = this._pool; + if(max != null) { + max.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = max; + } + } + this._drawBvhNode(d,node._children[0],level + 1,color); + this._drawBvhNode(d,node._children[1],level + 1,color); + } + _drawRigidBodies(d) { + let style = d.style; + let r = this._rigidBodyList; + while(r != null) { + let n = r._next; + if(d.drawBases) { + let style = d.style; + d.basis(r._transform,style.basisLength,style.basisColorX,style.basisColorY,style.basisColorZ); + } + let shapeColor = null; + let isDynamic = r._type == 0; + if(!isDynamic) { + shapeColor = r._type == 2 ? style.kinematicShapeColor : style.staticShapeColor; + } + let s = r._shapeList; + while(s != null) { + let n = s._next; + if(isDynamic) { + if((s._id & 1) == 0) { + shapeColor = r._sleeping ? style.sleepingShapeColor1 : r._sleepTime > oimo.common.Setting.sleepingTimeThreshold ? style.sleepyShapeColor1 : style.shapeColor1; + } else { + shapeColor = r._sleeping ? style.sleepingShapeColor2 : r._sleepTime > oimo.common.Setting.sleepingTimeThreshold ? style.sleepyShapeColor2 : style.shapeColor2; + } + } + if(d.drawShapes) { + let geom = s._geom; + let tf = s._transform; + switch(geom._type) { + case 0: + d.sphere(tf,geom._radius,shapeColor); + break; + case 1: + let g = geom; + let _this = this._pool; + let hx = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let v = hx; + v.x = g._halfExtentsX; + v.y = g._halfExtentsY; + v.z = g._halfExtentsZ; + d.box(tf,hx,shapeColor); + let _this1 = this._pool; + if(hx != null) { + hx.zero(); + if(_this1.sizeVec3 == _this1.stackVec3.length) { + let newArray = new Array(_this1.sizeVec3 << 1); + let _g = 0; + let _g1 = _this1.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this1.stackVec3[i]; + _this1.stackVec3[i] = null; + } + _this1.stackVec3 = newArray; + } + _this1.stackVec3[_this1.sizeVec3++] = hx; + } + break; + case 2: + let g1 = geom; + d.cylinder(tf,g1._radius,g1._halfHeight,shapeColor); + break; + case 3: + let g2 = geom; + d.cone(tf,g2._radius,g2._halfHeight,shapeColor); + break; + case 4: + let g3 = geom; + d.capsule(tf,g3._radius,g3._halfHeight,shapeColor); + break; + case 5: + let g4 = geom; + let n = g4._numVertices; + let _this2 = this._pool; + let v1 = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this._pool; + let v2 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this._pool; + let v3 = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let _this5 = this._pool; + let v12 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + let _this6 = this._pool; + let v13 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + let _this7 = this._pool; + let normal = _this7.sizeVec3 == 0 ? new oimo.common.Vec3() : _this7.stackVec3[--_this7.sizeVec3]; + let _this8 = this._pool; + let m = _this8.sizeMat3 == 0 ? new oimo.common.Mat3() : _this8.stackMat3[--_this8.sizeMat3]; + let _this9 = this._pool; + let o = _this9.sizeVec3 == 0 ? new oimo.common.Vec3() : _this9.stackVec3[--_this9.sizeVec3]; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + let v4 = o; + v4.x = tf._positionX; + v4.y = tf._positionY; + v4.z = tf._positionZ; + let _g = 0; + while(_g < n) { + let i = _g++; + let _this = g4._tmpVertices[i]; + let v = g4._vertices[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + _this.x += o.x; + _this.y += o.y; + _this.z += o.z; + } + if(n > 30) { + let _g = 0; + while(_g < n) { + let i = _g++; + let v = g4._tmpVertices[i]; + v1.x = v.x; + v1.y = v.y; + v1.z = v.z; + let v3 = g4._tmpVertices[(i + 1) % n]; + v2.x = v3.x; + v2.y = v3.y; + v2.z = v3.z; + d.line(v1,v2,shapeColor); + } + } else if(this._debugDraw.wireframe || n > 10) { + let _g = 0; + while(_g < n) { + let i = _g++; + let v = g4._tmpVertices[i]; + v1.x = v.x; + v1.y = v.y; + v1.z = v.z; + let _g1 = 0; + while(_g1 < i) { + let v = g4._tmpVertices[_g1++]; + v2.x = v.x; + v2.y = v.y; + v2.z = v.z; + d.line(v1,v2,shapeColor); + } + } + } else { + let _g = 0; + while(_g < n) { + let i = _g++; + let v = g4._tmpVertices[i]; + v1.x = v.x; + v1.y = v.y; + v1.z = v.z; + let _g1 = 0; + while(_g1 < i) { + let j = _g1++; + let v = g4._tmpVertices[j]; + v2.x = v.x; + v2.y = v.y; + v2.z = v.z; + let _g = 0; + while(_g < j) { + let v = g4._tmpVertices[_g++]; + v3.x = v.x; + v3.y = v.y; + v3.z = v.z; + v12.x = v2.x; + v12.y = v2.y; + v12.z = v2.z; + let _this = v12; + _this.x -= v1.x; + _this.y -= v1.y; + _this.z -= v1.z; + v13.x = v3.x; + v13.y = v3.y; + v13.z = v3.z; + let _this1 = v13; + _this1.x -= v1.x; + _this1.y -= v1.y; + _this1.z -= v1.z; + normal.x = v12.x; + normal.y = v12.y; + normal.z = v12.z; + let _this2 = normal; + let y = _this2.z * v13.x - _this2.x * v13.z; + let z = _this2.x * v13.y - _this2.y * v13.x; + _this2.x = _this2.y * v13.z - _this2.z * v13.y; + _this2.y = y; + _this2.z = z; + let invLen = Math.sqrt(_this2.x * _this2.x + _this2.y * _this2.y + _this2.z * _this2.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this2.x *= invLen; + _this2.y *= invLen; + _this2.z *= invLen; + d.triangle(v1,v2,v3,normal,normal,normal,shapeColor); + normal.x = -normal.x; + normal.y = -normal.y; + normal.z = -normal.z; + d.triangle(v1,v3,v2,normal,normal,normal,shapeColor); + } + } + } + } + let _this10 = this._pool; + if(v1 != null) { + v1.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = v1; + } + let _this11 = this._pool; + if(v2 != null) { + v2.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = v2; + } + let _this12 = this._pool; + if(v3 != null) { + v3.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = v3; + } + let _this13 = this._pool; + if(v12 != null) { + v12.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = v12; + } + let _this14 = this._pool; + if(v13 != null) { + v13.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = v13; + } + let _this15 = this._pool; + if(normal != null) { + normal.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = normal; + } + let _this16 = this._pool; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this16.sizeMat3 == _this16.stackMat3.length) { + let newArray = new Array(_this16.sizeMat3 << 1); + let _g = 0; + let _g1 = _this16.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackMat3[i]; + _this16.stackMat3[i] = null; + } + _this16.stackMat3 = newArray; + } + _this16.stackMat3[_this16.sizeMat3++] = m; + } + let _this17 = this._pool; + if(o != null) { + o.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = o; + } + break; + } + } + if(d.drawAabbs) { + let aabb = s._aabb; + let color = style.aabbColor; + let _this = this._pool; + let min = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let max = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = min; + v.x = aabb._minX; + v.y = aabb._minY; + v.z = aabb._minZ; + let v1 = max; + v1.x = aabb._maxX; + v1.y = aabb._maxY; + v1.z = aabb._maxZ; + d.aabb(min,max,color); + let _this2 = this._pool; + if(min != null) { + min.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = min; + } + let _this3 = this._pool; + if(max != null) { + max.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = max; + } + } + s = n; + } + r = n; + } + } + _drawConstraints(d) { + let style = d.style; + if(d.drawPairs || d.drawContacts) { + let c = this._contactManager._contactList; + while(c != null) { + let n = c._next; + if(d.drawPairs) { + let color = style.pairColor; + let _this = this._pool; + let v1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let v2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = v1; + v.x = c._s1._transform._positionX; + v.y = c._s1._transform._positionY; + v.z = c._s1._transform._positionZ; + let v3 = v2; + v3.x = c._s2._transform._positionX; + v3.y = c._s2._transform._positionY; + v3.z = c._s2._transform._positionZ; + d.line(v1,v2,color); + let _this2 = this._pool; + if(v1 != null) { + v1.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = v1; + } + let _this3 = this._pool; + if(v2 != null) { + v2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = v2; + } + } + if(d.drawContacts) { + let cc = c._contactConstraint; + let ps = c._contactConstraint._manifold._points; + let _g = 0; + let _g1 = c._contactConstraint._manifold._numPoints; + while(_g < _g1) { + let p = ps[_g++]; + let style = d.style; + let _this = this._pool; + let pos1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let pos2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this._pool; + let normal = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this._pool; + let tangent = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this._pool; + let binormal = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let v = pos1; + v.x = p._pos1X; + v.y = p._pos1Y; + v.z = p._pos1Z; + let v1 = pos2; + v1.x = p._pos2X; + v1.y = p._pos2Y; + v1.z = p._pos2Z; + let v2 = normal; + v2.x = cc._manifold._normalX; + v2.y = cc._manifold._normalY; + v2.z = cc._manifold._normalZ; + let v3 = tangent; + v3.x = cc._manifold._tangentX; + v3.y = cc._manifold._tangentY; + v3.z = cc._manifold._tangentZ; + let v4 = binormal; + v4.x = cc._manifold._binormalX; + v4.y = cc._manifold._binormalY; + v4.z = cc._manifold._binormalZ; + if(p._disabled) { + d.point(pos1,style.disabledContactColor); + d.point(pos2,style.disabledContactColor); + d.line(pos1,pos2,style.disabledContactColor); + } else if(p._warmStarted) { + let color; + switch(p._id & 3) { + case 0: + color = style.contactColor; + break; + case 1: + color = style.contactColor2; + break; + case 2: + color = style.contactColor3; + break; + default: + color = style.contactColor4; + } + d.point(pos1,color); + d.point(pos2,color); + d.line(pos1,pos2,style.contactColor); + } else { + d.point(pos1,style.newContactColor); + d.point(pos2,style.newContactColor); + d.line(pos1,pos2,style.newContactColor); + } + pos2.x = pos1.x; + pos2.y = pos1.y; + pos2.z = pos1.z; + let _this5 = pos2; + let s = style.contactNormalLength; + _this5.x += normal.x * s; + _this5.y += normal.y * s; + _this5.z += normal.z * s; + d.line(pos1,pos2,style.contactNormalColor); + if(d.drawContactBases) { + pos2.x = pos1.x; + pos2.y = pos1.y; + pos2.z = pos1.z; + let _this = pos2; + let s = style.contactTangentLength; + _this.x += tangent.x * s; + _this.y += tangent.y * s; + _this.z += tangent.z * s; + d.line(pos1,pos2,style.contactTangentColor); + pos2.x = pos1.x; + pos2.y = pos1.y; + pos2.z = pos1.z; + let _this1 = pos2; + let s1 = style.contactBinormalLength; + _this1.x += binormal.x * s1; + _this1.y += binormal.y * s1; + _this1.z += binormal.z * s1; + d.line(pos1,pos2,style.contactBinormalColor); + } + let _this6 = this._pool; + if(pos1 != null) { + pos1.zero(); + if(_this6.sizeVec3 == _this6.stackVec3.length) { + let newArray = new Array(_this6.sizeVec3 << 1); + let _g = 0; + let _g1 = _this6.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this6.stackVec3[i]; + _this6.stackVec3[i] = null; + } + _this6.stackVec3 = newArray; + } + _this6.stackVec3[_this6.sizeVec3++] = pos1; + } + let _this7 = this._pool; + if(pos2 != null) { + pos2.zero(); + if(_this7.sizeVec3 == _this7.stackVec3.length) { + let newArray = new Array(_this7.sizeVec3 << 1); + let _g = 0; + let _g1 = _this7.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this7.stackVec3[i]; + _this7.stackVec3[i] = null; + } + _this7.stackVec3 = newArray; + } + _this7.stackVec3[_this7.sizeVec3++] = pos2; + } + let _this8 = this._pool; + if(normal != null) { + normal.zero(); + if(_this8.sizeVec3 == _this8.stackVec3.length) { + let newArray = new Array(_this8.sizeVec3 << 1); + let _g = 0; + let _g1 = _this8.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this8.stackVec3[i]; + _this8.stackVec3[i] = null; + } + _this8.stackVec3 = newArray; + } + _this8.stackVec3[_this8.sizeVec3++] = normal; + } + let _this9 = this._pool; + if(tangent != null) { + tangent.zero(); + if(_this9.sizeVec3 == _this9.stackVec3.length) { + let newArray = new Array(_this9.sizeVec3 << 1); + let _g = 0; + let _g1 = _this9.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackVec3[i]; + _this9.stackVec3[i] = null; + } + _this9.stackVec3 = newArray; + } + _this9.stackVec3[_this9.sizeVec3++] = tangent; + } + let _this10 = this._pool; + if(binormal != null) { + binormal.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = binormal; + } + } + } + c = n; + } + } + if(d.drawJoints) { + let j = this._jointList; + while(j != null) { + let n = j._next; + let _this = this._pool; + let p1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let p2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = p1; + v.x = j._b1._transform._positionX; + v.y = j._b1._transform._positionY; + v.z = j._b1._transform._positionZ; + let v1 = p2; + v1.x = j._b2._transform._positionX; + v1.y = j._b2._transform._positionY; + v1.z = j._b2._transform._positionZ; + let _this2 = this._pool; + let anchor1 = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this._pool; + let anchor2 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this._pool; + let basisX1 = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let _this5 = this._pool; + let basisY1 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + let _this6 = this._pool; + let basisZ1 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + let _this7 = this._pool; + let basisX2 = _this7.sizeVec3 == 0 ? new oimo.common.Vec3() : _this7.stackVec3[--_this7.sizeVec3]; + let _this8 = this._pool; + let basisY2 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + let _this9 = this._pool; + let basisZ2 = _this9.sizeVec3 == 0 ? new oimo.common.Vec3() : _this9.stackVec3[--_this9.sizeVec3]; + let v2 = anchor1; + v2.x = j._anchor1X; + v2.y = j._anchor1Y; + v2.z = j._anchor1Z; + let v3 = anchor2; + v3.x = j._anchor2X; + v3.y = j._anchor2Y; + v3.z = j._anchor2Z; + let v4 = basisX1; + v4.x = j._basisX1X; + v4.y = j._basisX1Y; + v4.z = j._basisX1Z; + let v5 = basisY1; + v5.x = j._basisY1X; + v5.y = j._basisY1Y; + v5.z = j._basisY1Z; + let v6 = basisZ1; + v6.x = j._basisZ1X; + v6.y = j._basisZ1Y; + v6.z = j._basisZ1Z; + let v7 = basisX2; + v7.x = j._basisX2X; + v7.y = j._basisX2Y; + v7.z = j._basisX2Z; + let v8 = basisY2; + v8.x = j._basisY2X; + v8.y = j._basisY2Y; + v8.z = j._basisY2Z; + let v9 = basisZ2; + v9.x = j._basisZ2X; + v9.y = j._basisZ2Y; + v9.z = j._basisZ2Z; + d.line(p1,anchor1,d.style.jointLineColor); + d.line(p2,anchor2,d.style.jointLineColor); + if(d.drawJointLimits) { + switch(j._type) { + case 0: + break; + case 1: + let lm = j._lm; + this._drawRotationalLimit(d,anchor1,basisY1,basisZ1,basisY2,d.style.jointRotationalConstraintRadius,lm.lowerLimit,lm.upperLimit,d.style.jointLineColor); + break; + case 2: + let j1 = j; + let color = d.style.jointLineColor; + let rlm = j1._rotLm; + let tlm = j1._translLm; + this._drawRotationalLimit(d,anchor2,basisY1,basisZ1,basisY2,d.style.jointRotationalConstraintRadius,rlm.lowerLimit,rlm.upperLimit,color); + this._drawTranslationalLimit(d,anchor1,basisX1,tlm.lowerLimit,tlm.upperLimit,color); + break; + case 3: + let lm1 = j._lm; + this._drawTranslationalLimit(d,anchor1,basisX1,lm1.lowerLimit,lm1.upperLimit,d.style.jointLineColor); + break; + case 4: + let j2 = j; + let radius = d.style.jointRotationalConstraintRadius; + let color1 = d.style.jointLineColor; + let lm11 = j2._lm1; + let lm2 = j2._lm2; + this._drawRotationalLimit(d,anchor1,basisY1,basisZ1,basisY1,radius,j2._angleX - lm11.upperLimit,j2._angleX - lm11.lowerLimit,color1); + this._drawRotationalLimit(d,anchor2,basisX2,basisY2,basisX2,radius,lm2.lowerLimit - j2._angleZ,lm2.upperLimit - j2._angleZ,color1); + break; + case 5: + let j3 = j; + let radius1 = d.style.jointRotationalConstraintRadius; + let color2 = d.style.jointLineColor; + let lm3 = j3._twistLm; + this._drawRotationalLimit(d,anchor2,basisY2,basisZ2,basisY2,radius1,lm3.lowerLimit - j3._twistAngle,lm3.upperLimit - j3._twistAngle,color2); + this._drawEllipseOnSphere(d,anchor1,basisX1,basisY1,basisZ1,j3._maxSwingAngle1,j3._maxSwingAngle2,radius1,color2); + let _this10 = this._pool; + let _this11 = _this10.sizeVec3 == 0 ? new oimo.common.Vec3() : _this10.stackVec3[--_this10.sizeVec3]; + _this11.x = anchor2.x; + _this11.y = anchor2.y; + _this11.z = anchor2.z; + let _this12 = _this11; + _this12.x += basisX2.x * radius1; + _this12.y += basisX2.y * radius1; + _this12.z += basisX2.z * radius1; + d.line(anchor2,_this12,color2); + let _this13 = this._pool; + if(_this12 != null) { + _this12.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = _this12; + } + break; + case 6: + let j4 = j; + let radius2 = d.style.jointRotationalConstraintRadius; + let color3 = d.style.jointLineColor; + let rxlm = j4._rotLms[0]; + let rylm = j4._rotLms[1]; + let rzlm = j4._rotLms[2]; + this._drawTranslationalLimit3D(d,anchor1,basisX1,basisY1,basisZ1,j4._translLms[0],j4._translLms[1],j4._translLms[2],color3); + let _this14 = this._pool; + let rotYAxis = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + let v10 = rotYAxis; + v10.x = j4._axisYX; + v10.y = j4._axisYY; + v10.z = j4._axisYZ; + let _this15 = this._pool; + let _this16 = _this15.sizeVec3 == 0 ? new oimo.common.Vec3() : _this15.stackVec3[--_this15.sizeVec3]; + _this16.x = basisX1.x; + _this16.y = basisX1.y; + _this16.z = basisX1.z; + let rotYBasisX = _this16; + let _this17 = this._pool; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = basisX1.x; + _this18.y = basisX1.y; + _this18.z = basisX1.z; + let _this19 = _this18; + let y = _this19.z * rotYAxis.x - _this19.x * rotYAxis.z; + let z = _this19.x * rotYAxis.y - _this19.y * rotYAxis.x; + _this19.x = _this19.y * rotYAxis.z - _this19.z * rotYAxis.y; + _this19.y = y; + _this19.z = z; + this._drawRotationalLimit(d,anchor2,basisY1,basisZ1,basisY1,radius2,j4._angleX - rxlm.upperLimit,j4._angleX - rxlm.lowerLimit,color3); + this._drawRotationalLimit(d,anchor2,rotYBasisX,_this19,rotYBasisX,radius2,rylm.lowerLimit - j4._angleY,rylm.upperLimit - j4._angleY,color3); + this._drawRotationalLimit(d,anchor2,basisX2,basisY2,basisX2,radius2,rzlm.lowerLimit - j4._angleZ,rzlm.upperLimit - j4._angleZ,color3); + break; + } + } + d.line(anchor1,anchor2,d.style.jointErrorColor); + let _this20 = this._pool; + if(p1 != null) { + p1.zero(); + if(_this20.sizeVec3 == _this20.stackVec3.length) { + let newArray = new Array(_this20.sizeVec3 << 1); + let _g = 0; + let _g1 = _this20.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this20.stackVec3[i]; + _this20.stackVec3[i] = null; + } + _this20.stackVec3 = newArray; + } + _this20.stackVec3[_this20.sizeVec3++] = p1; + } + let _this21 = this._pool; + if(p2 != null) { + p2.zero(); + if(_this21.sizeVec3 == _this21.stackVec3.length) { + let newArray = new Array(_this21.sizeVec3 << 1); + let _g = 0; + let _g1 = _this21.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this21.stackVec3[i]; + _this21.stackVec3[i] = null; + } + _this21.stackVec3 = newArray; + } + _this21.stackVec3[_this21.sizeVec3++] = p2; + } + let _this22 = this._pool; + if(anchor1 != null) { + anchor1.zero(); + if(_this22.sizeVec3 == _this22.stackVec3.length) { + let newArray = new Array(_this22.sizeVec3 << 1); + let _g = 0; + let _g1 = _this22.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this22.stackVec3[i]; + _this22.stackVec3[i] = null; + } + _this22.stackVec3 = newArray; + } + _this22.stackVec3[_this22.sizeVec3++] = anchor1; + } + let _this23 = this._pool; + if(anchor2 != null) { + anchor2.zero(); + if(_this23.sizeVec3 == _this23.stackVec3.length) { + let newArray = new Array(_this23.sizeVec3 << 1); + let _g = 0; + let _g1 = _this23.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this23.stackVec3[i]; + _this23.stackVec3[i] = null; + } + _this23.stackVec3 = newArray; + } + _this23.stackVec3[_this23.sizeVec3++] = anchor2; + } + let _this24 = this._pool; + if(basisX1 != null) { + basisX1.zero(); + if(_this24.sizeVec3 == _this24.stackVec3.length) { + let newArray = new Array(_this24.sizeVec3 << 1); + let _g = 0; + let _g1 = _this24.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this24.stackVec3[i]; + _this24.stackVec3[i] = null; + } + _this24.stackVec3 = newArray; + } + _this24.stackVec3[_this24.sizeVec3++] = basisX1; + } + let _this25 = this._pool; + if(basisY1 != null) { + basisY1.zero(); + if(_this25.sizeVec3 == _this25.stackVec3.length) { + let newArray = new Array(_this25.sizeVec3 << 1); + let _g = 0; + let _g1 = _this25.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this25.stackVec3[i]; + _this25.stackVec3[i] = null; + } + _this25.stackVec3 = newArray; + } + _this25.stackVec3[_this25.sizeVec3++] = basisY1; + } + let _this26 = this._pool; + if(basisZ1 != null) { + basisZ1.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = basisZ1; + } + let _this27 = this._pool; + if(basisX2 != null) { + basisX2.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = basisX2; + } + let _this28 = this._pool; + if(basisY2 != null) { + basisY2.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = basisY2; + } + let _this29 = this._pool; + if(basisZ2 != null) { + basisZ2.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = basisZ2; + } + j = n; + } + } + } + _drawRotationalLimit(d,center,ex,ey,needle,radius,min,max,color) { + if(min != max) { + let _this = this._pool; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = center.x; + _this1.y = center.y; + _this1.z = center.z; + let _this2 = _this1; + _this2.x += needle.x * radius; + _this2.y += needle.y * radius; + _this2.z += needle.z * radius; + d.line(center,_this2,color); + let _this3 = this._pool; + if(_this2 != null) { + _this2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = _this2; + } + if(min > max) { + d.ellipse(center,ex,ey,radius,radius,color); + } else { + d.arc(center,ex,ey,radius,radius,min,max,true,color); + } + } + } + _drawTranslationalLimit(d,center,ex,min,max,color) { + if(min < max) { + let _this = this._pool; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = center.x; + _this1.y = center.y; + _this1.z = center.z; + let _this2 = _this1; + _this2.x += ex.x * min; + _this2.y += ex.y * min; + _this2.z += ex.z * min; + let _this3 = this._pool; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = center.x; + _this4.y = center.y; + _this4.z = center.z; + let _this5 = _this4; + _this5.x += ex.x * max; + _this5.y += ex.y * max; + _this5.z += ex.z * max; + d.line(_this2,_this5,color); + let _this6 = this._pool; + if(_this2 != null) { + _this2.zero(); + if(_this6.sizeVec3 == _this6.stackVec3.length) { + let newArray = new Array(_this6.sizeVec3 << 1); + let _g = 0; + let _g1 = _this6.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this6.stackVec3[i]; + _this6.stackVec3[i] = null; + } + _this6.stackVec3 = newArray; + } + _this6.stackVec3[_this6.sizeVec3++] = _this2; + } + let _this7 = this._pool; + if(_this5 != null) { + _this5.zero(); + if(_this7.sizeVec3 == _this7.stackVec3.length) { + let newArray = new Array(_this7.sizeVec3 << 1); + let _g = 0; + let _g1 = _this7.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this7.stackVec3[i]; + _this7.stackVec3[i] = null; + } + _this7.stackVec3 = newArray; + } + _this7.stackVec3[_this7.sizeVec3++] = _this5; + } + } + } + _drawTranslationalLimit3D(d,center,ex,ey,ez,xlm,ylm,zlm,color) { + let minx = xlm.lowerLimit; + let maxx = xlm.upperLimit; + let miny = ylm.lowerLimit; + let maxy = ylm.upperLimit; + let minz = zlm.lowerLimit; + let maxz = zlm.upperLimit; + let _this = this._pool; + if(_this.sizeVec3 == 0) { + new oimo.common.Vec3(); + } else { + --_this.sizeVec3; + } + let _this1 = this._pool; + if(_this1.sizeVec3 == 0) { + new oimo.common.Vec3(); + } else { + --_this1.sizeVec3; + } + let _this2 = this._pool; + let _this3 = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + _this3.x = center.x; + _this3.y = center.y; + _this3.z = center.z; + let _this4 = _this3; + _this4.x += ex.x * minx; + _this4.y += ex.y * minx; + _this4.z += ex.z * minx; + _this4.x += ey.x * miny; + _this4.y += ey.y * miny; + _this4.z += ey.z * miny; + _this4.x += ez.x * minz; + _this4.y += ez.y * minz; + _this4.z += ez.z * minz; + let _this5 = this._pool; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = center.x; + _this6.y = center.y; + _this6.z = center.z; + let _this7 = _this6; + _this7.x += ex.x * minx; + _this7.y += ex.y * minx; + _this7.z += ex.z * minx; + _this7.x += ey.x * miny; + _this7.y += ey.y * miny; + _this7.z += ey.z * miny; + _this7.x += ez.x * maxz; + _this7.y += ez.y * maxz; + _this7.z += ez.z * maxz; + let _this8 = this._pool; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = center.x; + _this9.y = center.y; + _this9.z = center.z; + let _this10 = _this9; + _this10.x += ex.x * minx; + _this10.y += ex.y * minx; + _this10.z += ex.z * minx; + _this10.x += ey.x * maxy; + _this10.y += ey.y * maxy; + _this10.z += ey.z * maxy; + _this10.x += ez.x * minz; + _this10.y += ez.y * minz; + _this10.z += ez.z * minz; + let _this11 = this._pool; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = center.x; + _this12.y = center.y; + _this12.z = center.z; + let _this13 = _this12; + _this13.x += ex.x * minx; + _this13.y += ex.y * minx; + _this13.z += ex.z * minx; + _this13.x += ey.x * maxy; + _this13.y += ey.y * maxy; + _this13.z += ey.z * maxy; + _this13.x += ez.x * maxz; + _this13.y += ez.y * maxz; + _this13.z += ez.z * maxz; + let _this14 = this._pool; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = center.x; + _this15.y = center.y; + _this15.z = center.z; + let _this16 = _this15; + _this16.x += ex.x * maxx; + _this16.y += ex.y * maxx; + _this16.z += ex.z * maxx; + _this16.x += ey.x * miny; + _this16.y += ey.y * miny; + _this16.z += ey.z * miny; + _this16.x += ez.x * minz; + _this16.y += ez.y * minz; + _this16.z += ez.z * minz; + let _this17 = this._pool; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = center.x; + _this18.y = center.y; + _this18.z = center.z; + let _this19 = _this18; + _this19.x += ex.x * maxx; + _this19.y += ex.y * maxx; + _this19.z += ex.z * maxx; + _this19.x += ey.x * miny; + _this19.y += ey.y * miny; + _this19.z += ey.z * miny; + _this19.x += ez.x * maxz; + _this19.y += ez.y * maxz; + _this19.z += ez.z * maxz; + let _this20 = this._pool; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = center.x; + _this21.y = center.y; + _this21.z = center.z; + let _this22 = _this21; + _this22.x += ex.x * maxx; + _this22.y += ex.y * maxx; + _this22.z += ex.z * maxx; + _this22.x += ey.x * maxy; + _this22.y += ey.y * maxy; + _this22.z += ey.z * maxy; + _this22.x += ez.x * minz; + _this22.y += ez.y * minz; + _this22.z += ez.z * minz; + let _this23 = this._pool; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = center.x; + _this24.y = center.y; + _this24.z = center.z; + let _this25 = _this24; + _this25.x += ex.x * maxx; + _this25.y += ex.y * maxx; + _this25.z += ex.z * maxx; + _this25.x += ey.x * maxy; + _this25.y += ey.y * maxy; + _this25.z += ey.z * maxy; + _this25.x += ez.x * maxz; + _this25.y += ez.y * maxz; + _this25.z += ez.z * maxz; + d.line(_this4,_this16,color); + d.line(_this10,_this22,color); + d.line(_this7,_this19,color); + d.line(_this13,_this25,color); + d.line(_this4,_this10,color); + d.line(_this16,_this22,color); + d.line(_this7,_this13,color); + d.line(_this19,_this25,color); + d.line(_this4,_this7,color); + d.line(_this16,_this19,color); + d.line(_this10,_this13,color); + d.line(_this22,_this25,color); + let _this26 = this._pool; + if(_this4 != null) { + _this4.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = _this4; + } + let _this27 = this._pool; + if(_this7 != null) { + _this7.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = _this7; + } + let _this28 = this._pool; + if(_this10 != null) { + _this10.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = _this10; + } + let _this29 = this._pool; + if(_this13 != null) { + _this13.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this13; + } + let _this30 = this._pool; + if(_this16 != null) { + _this16.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this16; + } + let _this31 = this._pool; + if(_this19 != null) { + _this19.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this19; + } + let _this32 = this._pool; + if(_this22 != null) { + _this22.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this22; + } + let _this33 = this._pool; + if(_this25 != null) { + _this25.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this25; + } + } + _drawEllipseOnSphere(d,center,normal,x,y,radiansX,radiansY,radius,color) { + let theta = 0; + let _this = this._pool; + let rotVec = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let rotQ = _this1.sizeQuat == 0 ? new oimo.common.Quat() : _this1.stackQuat[--_this1.sizeQuat]; + let _this2 = this._pool; + let rotM = _this2.sizeMat3 == 0 ? new oimo.common.Mat3() : _this2.stackMat3[--_this2.sizeMat3]; + let _this3 = this._pool; + let prevV = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _g = 0; + while(_g < 17) { + let i = _g++; + let rx = Math.cos(theta) * radiansX; + let ry = Math.sin(theta) * radiansY; + let halfRotAng = Math.sqrt(rx * rx + ry * ry); + let rotSin = Math.sin(halfRotAng * 0.5); + let rotCos = Math.cos(halfRotAng * 0.5); + let _this = rotVec.zero(); + _this.x += x.x * rx; + _this.y += x.y * rx; + _this.z += x.z * rx; + _this.x += y.x * ry; + _this.y += y.y * ry; + _this.z += y.z * ry; + let s = 1 / halfRotAng * rotSin; + rotVec.x *= s; + rotVec.y *= s; + rotVec.z *= s; + rotQ.x = rotVec.x; + rotQ.y = rotVec.y; + rotQ.z = rotVec.z; + rotQ.w = rotCos; + let x1 = rotQ.x; + let y1 = rotQ.y; + let z = rotQ.z; + let w = rotQ.w; + let x2 = 2 * x1; + let y2 = 2 * y1; + let z2 = 2 * z; + let xx = x1 * x2; + let yy = y1 * y2; + let zz = z * z2; + let xy = x1 * y2; + let yz = y1 * z2; + let xz = x1 * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + rotM.e00 = 1 - yy - zz; + rotM.e01 = xy - wz; + rotM.e02 = xz + wy; + rotM.e10 = xy + wz; + rotM.e11 = 1 - xx - zz; + rotM.e12 = yz - wx; + rotM.e20 = xz - wy; + rotM.e21 = yz + wx; + rotM.e22 = 1 - xx - yy; + let _this1 = this._pool; + let _this2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + _this2.x += normal.x * radius; + _this2.y += normal.y * radius; + _this2.z += normal.z * radius; + let v = _this2; + let y3 = v.x * rotM.e10 + v.y * rotM.e11 + v.z * rotM.e12; + let z1 = v.x * rotM.e20 + v.y * rotM.e21 + v.z * rotM.e22; + v.x = v.x * rotM.e00 + v.y * rotM.e01 + v.z * rotM.e02; + v.y = y3; + v.z = z1; + v.x += center.x; + v.y += center.y; + v.z += center.z; + if(i >= 1) { + d.line(prevV,v,color); + } + let _this3 = this._pool; + if(prevV != null) { + prevV.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = prevV; + } + prevV = v; + theta += 0.39269908169872375; + } + let _this4 = this._pool; + if(rotVec != null) { + rotVec.zero(); + if(_this4.sizeVec3 == _this4.stackVec3.length) { + let newArray = new Array(_this4.sizeVec3 << 1); + let _g = 0; + let _g1 = _this4.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this4.stackVec3[i]; + _this4.stackVec3[i] = null; + } + _this4.stackVec3 = newArray; + } + _this4.stackVec3[_this4.sizeVec3++] = rotVec; + } + let _this5 = this._pool; + if(rotQ != null) { + rotQ.x = 0; + rotQ.y = 0; + rotQ.z = 0; + rotQ.w = 1; + if(_this5.sizeQuat == _this5.stackQuat.length) { + let newArray = new Array(_this5.sizeQuat << 1); + let _g = 0; + let _g1 = _this5.sizeQuat; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this5.stackQuat[i]; + _this5.stackQuat[i] = null; + } + _this5.stackQuat = newArray; + } + _this5.stackQuat[_this5.sizeQuat++] = rotQ; + } + let _this6 = this._pool; + if(rotM != null) { + rotM.e00 = 1; + rotM.e01 = 0; + rotM.e02 = 0; + rotM.e10 = 0; + rotM.e11 = 1; + rotM.e12 = 0; + rotM.e20 = 0; + rotM.e21 = 0; + rotM.e22 = 1; + if(_this6.sizeMat3 == _this6.stackMat3.length) { + let newArray = new Array(_this6.sizeMat3 << 1); + let _g = 0; + let _g1 = _this6.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this6.stackMat3[i]; + _this6.stackMat3[i] = null; + } + _this6.stackMat3 = newArray; + } + _this6.stackMat3[_this6.sizeMat3++] = rotM; + } + let _this7 = this._pool; + if(prevV != null) { + prevV.zero(); + if(_this7.sizeVec3 == _this7.stackVec3.length) { + let newArray = new Array(_this7.sizeVec3 << 1); + let _g = 0; + let _g1 = _this7.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this7.stackVec3[i]; + _this7.stackVec3[i] = null; + } + _this7.stackVec3 = newArray; + } + _this7.stackVec3[_this7.sizeVec3++] = prevV; + } + } + step(timeStep) { + if(this._timeStep.dt > 0) { + this._timeStep.dtRatio = timeStep / this._timeStep.dt; + } + this._timeStep.dt = timeStep; + this._timeStep.invDt = 1 / timeStep; + let st = Date.now() / 1000; + this._updateContacts(); + this._solveIslands(); + oimo.dynamics.common.Performance.totalTime = (Date.now() / 1000 - st) * 1000; + } + addRigidBody(rigidBody) { + if(rigidBody._world != null) { + throw new Error("A rigid body cannot belong to multiple worlds."); + } + if(this._rigidBodyList == null) { + this._rigidBodyList = rigidBody; + this._rigidBodyListLast = rigidBody; + } else { + this._rigidBodyListLast._next = rigidBody; + rigidBody._prev = this._rigidBodyListLast; + this._rigidBodyListLast = rigidBody; + } + rigidBody._world = this; + let s = rigidBody._shapeList; + while(s != null) { + let n = s._next; + s._proxy = this._broadPhase.createProxy(s,s._aabb); + s._id = this._shapeIdCount++; + this._numShapes++; + s = n; + } + this._numRigidBodies++; + } + removeRigidBody(rigidBody) { + if(rigidBody._world != this) { + throw new Error("The rigid body doesn't belong to the world."); + } + let prev = rigidBody._prev; + let next = rigidBody._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(rigidBody == this._rigidBodyList) { + this._rigidBodyList = this._rigidBodyList._next; + } + if(rigidBody == this._rigidBodyListLast) { + this._rigidBodyListLast = this._rigidBodyListLast._prev; + } + rigidBody._next = null; + rigidBody._prev = null; + rigidBody._world = null; + let s = rigidBody._shapeList; + while(s != null) { + let n = s._next; + this._broadPhase.destroyProxy(s._proxy); + s._proxy = null; + s._id = -1; + let cl = s._rigidBody._contactLinkList; + while(cl != null) { + let n = cl._next; + let c = cl._contact; + if(c._s1 == s || c._s2 == s) { + let _this = cl._other; + _this._sleeping = false; + _this._sleepTime = 0; + let _this1 = this._contactManager; + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == _this1._contactList) { + _this1._contactList = _this1._contactList._next; + } + if(c == _this1._contactListLast) { + _this1._contactListLast = _this1._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this2 = c._contactConstraint; + _this2._s1 = null; + _this2._s2 = null; + _this2._b1 = null; + _this2._b2 = null; + _this2._tf1 = null; + _this2._tf2 = null; + c._next = _this1._contactPool; + _this1._contactPool = c; + _this1._numContacts--; + } + cl = n; + } + this._numShapes--; + s = n; + } + this._numRigidBodies--; + } + addJoint(joint) { + if(joint._world != null) { + throw new Error("A joint cannot belong to multiple worlds."); + } + if(this._jointList == null) { + this._jointList = joint; + this._jointListLast = joint; + } else { + this._jointListLast._next = joint; + joint._prev = this._jointListLast; + this._jointListLast = joint; + } + joint._world = this; + joint._link1._other = joint._b2; + joint._link2._other = joint._b1; + if(joint._b1._jointLinkList == null) { + joint._b1._jointLinkList = joint._link1; + joint._b1._jointLinkListLast = joint._link1; + } else { + joint._b1._jointLinkListLast._next = joint._link1; + joint._link1._prev = joint._b1._jointLinkListLast; + joint._b1._jointLinkListLast = joint._link1; + } + if(joint._b2._jointLinkList == null) { + joint._b2._jointLinkList = joint._link2; + joint._b2._jointLinkListLast = joint._link2; + } else { + joint._b2._jointLinkListLast._next = joint._link2; + joint._link2._prev = joint._b2._jointLinkListLast; + joint._b2._jointLinkListLast = joint._link2; + } + joint._b1._numJointLinks++; + joint._b2._numJointLinks++; + let _this = joint._b1; + _this._sleeping = false; + _this._sleepTime = 0; + let _this1 = joint._b2; + _this1._sleeping = false; + _this1._sleepTime = 0; + joint._syncAnchors(); + this._numJoints++; + } + removeJoint(joint) { + if(joint._world != this) { + throw new Error("The joint doesn't belong to the world."); + } + let prev = joint._prev; + let next = joint._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(joint == this._jointList) { + this._jointList = this._jointList._next; + } + if(joint == this._jointListLast) { + this._jointListLast = this._jointListLast._prev; + } + joint._next = null; + joint._prev = null; + joint._world = null; + let prev1 = joint._link1._prev; + let next1 = joint._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(joint._link1 == joint._b1._jointLinkList) { + joint._b1._jointLinkList = joint._b1._jointLinkList._next; + } + if(joint._link1 == joint._b1._jointLinkListLast) { + joint._b1._jointLinkListLast = joint._b1._jointLinkListLast._prev; + } + joint._link1._next = null; + joint._link1._prev = null; + let prev2 = joint._link2._prev; + let next2 = joint._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(joint._link2 == joint._b2._jointLinkList) { + joint._b2._jointLinkList = joint._b2._jointLinkList._next; + } + if(joint._link2 == joint._b2._jointLinkListLast) { + joint._b2._jointLinkListLast = joint._b2._jointLinkListLast._prev; + } + joint._link2._next = null; + joint._link2._prev = null; + joint._link1._other = null; + joint._link2._other = null; + joint._b1._numJointLinks--; + joint._b2._numJointLinks--; + let _this = joint._b1; + _this._sleeping = false; + _this._sleepTime = 0; + let _this1 = joint._b2; + _this1._sleeping = false; + _this1._sleepTime = 0; + this._numJoints--; + } + setDebugDraw(debugDraw) { + this._debugDraw = debugDraw; + } + getDebugDraw() { + return this._debugDraw; + } + debugDraw() { + if(this._debugDraw != null) { + if(this._broadPhase._type == 2) { + this._drawBvh(this._debugDraw,this._broadPhase._tree); + } + this._drawRigidBodies(this._debugDraw); + this._drawConstraints(this._debugDraw); + } + } + rayCast(begin,end,callback) { + let _this = this._rayCastWrapper.begin; + _this.x = begin.x; + _this.y = begin.y; + _this.z = begin.z; + let _this1 = this._rayCastWrapper.end; + _this1.x = end.x; + _this1.y = end.y; + _this1.z = end.z; + this._rayCastWrapper.callback = callback; + this._broadPhase.rayCast(begin,end,this._rayCastWrapper); + } + convexCast(convex,begin,translation,callback) { + this._convexCastWrapper.convex = convex; + let _this = this._convexCastWrapper.begin; + _this._positionX = begin._positionX; + _this._positionY = begin._positionY; + _this._positionZ = begin._positionZ; + _this._rotation00 = begin._rotation00; + _this._rotation01 = begin._rotation01; + _this._rotation02 = begin._rotation02; + _this._rotation10 = begin._rotation10; + _this._rotation11 = begin._rotation11; + _this._rotation12 = begin._rotation12; + _this._rotation20 = begin._rotation20; + _this._rotation21 = begin._rotation21; + _this._rotation22 = begin._rotation22; + let _this1 = this._convexCastWrapper.translation; + _this1.x = translation.x; + _this1.y = translation.y; + _this1.z = translation.z; + this._convexCastWrapper.callback = callback; + this._broadPhase.convexCast(convex,begin,translation,this._convexCastWrapper); + } + aabbTest(aabb,callback) { + this._aabbTestWrapper._aabb.copyFrom(aabb); + this._aabbTestWrapper._callback = callback; + this._broadPhase.aabbTest(aabb,this._aabbTestWrapper); + } + getRigidBodyList() { + return this._rigidBodyList; + } + getJointList() { + return this._jointList; + } + getBroadPhase() { + return this._broadPhase; + } + getContactManager() { + return this._contactManager; + } + getNumRigidBodies() { + return this._numRigidBodies; + } + getNumJoints() { + return this._numJoints; + } + getNumShapes() { + return this._numShapes; + } + getNumIslands() { + return this._numIslands; + } + getNumVelocityIterations() { + return this._numVelocityIterations; + } + setNumVelocityIterations(numVelocityIterations) { + this._numVelocityIterations = numVelocityIterations; + } + getNumPositionIterations() { + return this._numPositionIterations; + } + setNumPositionIterations(numPositionIterations) { + this._numPositionIterations = numPositionIterations; + } + getGravity() { + return this._gravity; + } + setGravity(gravity) { + let _this = this._gravity; + _this.x = gravity.x; + _this.y = gravity.y; + _this.z = gravity.z; + } +} +if(!oimo.dynamics._World) oimo.dynamics._World = {}; +oimo.dynamics._World.RayCastWrapper = class oimo_dynamics__$World_RayCastWrapper extends oimo.collision.broadphase.BroadPhaseProxyCallback { + constructor() { + super(); + this.rayCastHit = new oimo.collision.geometry.RayCastHit(); + this.begin = new oimo.common.Vec3(); + this.end = new oimo.common.Vec3(); + this.callback = null; + } + process(proxy) { + let shape = proxy.userData; + if(shape._geom.rayCast(this.begin,this.end,shape._transform,this.rayCastHit)) { + this.callback.process(shape,this.rayCastHit); + } + } +} +oimo.dynamics._World.ConvexCastWrapper = class oimo_dynamics__$World_ConvexCastWrapper extends oimo.collision.broadphase.BroadPhaseProxyCallback { + constructor() { + super(); + this.rayCastHit = new oimo.collision.geometry.RayCastHit(); + this.begin = new oimo.common.Transform(); + this.translation = new oimo.common.Vec3(); + this.zero = new oimo.common.Vec3(); + this.callback = null; + this.convex = null; + } + process(proxy) { + let shape = proxy.userData; + let type = shape._geom._type; + if(type < 0 || type > 5) { + return; + } + if(oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance.convexCast(this.convex,shape._geom,this.begin,shape._transform,this.translation,this.zero,this.rayCastHit)) { + this.callback.process(shape,this.rayCastHit); + } + } +} +oimo.dynamics._World.AabbTestWrapper = class oimo_dynamics__$World_AabbTestWrapper extends oimo.collision.broadphase.BroadPhaseProxyCallback { + constructor() { + super(); + this._aabb = new oimo.collision.geometry.Aabb(); + this._callback = null; + } + process(proxy) { + let shape = proxy.userData; + let shapeAabb = shape._aabb; + if(shapeAabb._minX < this._aabb._maxX && shapeAabb._maxX > this._aabb._minX && shapeAabb._minY < this._aabb._maxY && shapeAabb._maxY > this._aabb._minY && shapeAabb._minZ < this._aabb._maxZ && shapeAabb._maxZ > this._aabb._minZ) { + this._callback.process(shape); + } + } +} +if(!oimo.dynamics.callback) oimo.dynamics.callback = {}; +oimo.dynamics.callback.AabbTestCallback = class oimo_dynamics_callback_AabbTestCallback { + constructor() { + } + process(shape) { + } +} +oimo.dynamics.callback.ContactCallback = class oimo_dynamics_callback_ContactCallback { + constructor() { + } + beginContact(c) { + } + preSolve(c) { + } + postSolve(c) { + } + endContact(c) { + } +} +oimo.dynamics.callback.RayCastCallback = class oimo_dynamics_callback_RayCastCallback { + constructor() { + } + process(shape,hit) { + } +} +oimo.dynamics.callback.RayCastClosest = class oimo_dynamics_callback_RayCastClosest extends oimo.dynamics.callback.RayCastCallback { + constructor() { + super(); + this.position = new oimo.common.Vec3(); + this.normal = new oimo.common.Vec3(); + this.shape = null; + this.fraction = 1; + this.position.zero(); + this.normal.zero(); + this.hit = false; + } + clear() { + this.shape = null; + this.fraction = 1; + this.position.zero(); + this.normal.zero(); + this.hit = false; + } + process(shape,hit) { + if(hit.fraction < this.fraction) { + this.shape = shape; + this.hit = true; + this.fraction = hit.fraction; + let _this = this.position; + let v = hit.position; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.normal; + let v1 = hit.normal; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + } + } +} +if(!oimo.dynamics.common) oimo.dynamics.common = {}; +oimo.dynamics.common.DebugDraw = class oimo_dynamics_common_DebugDraw { + constructor() { + this.p = new oimo.common.Pool(); + this.wireframe = false; + this.drawShapes = true; + this.drawBvh = false; + this.drawBvhMinLevel = 0; + this.drawBvhMaxLevel = 65536; + this.drawAabbs = false; + this.drawBases = false; + this.drawPairs = false; + this.drawContacts = false; + this.drawJoints = true; + this.drawJointLimits = false; + this.sphereCoords = new Array(5); + this.tmpSphereVerts = new Array(5); + this.tmpSphereNorms = new Array(5); + let _g = 0; + while(_g < 5) { + let i = _g++; + let num = i == 0 || i == 4 ? 1 : 8; + this.sphereCoords[i] = new Array(num); + this.tmpSphereVerts[i] = new Array(num); + this.tmpSphereNorms[i] = new Array(num); + let _g1 = 0; + while(_g1 < 8) { + let j = _g1++; + let theta = i * 0.7853981633974475; + let phi = j * 0.7853981633974475; + this.sphereCoords[i][j] = new oimo.common.Vec3(Math.sin(theta) * Math.cos(phi),Math.cos(theta),-Math.sin(theta) * Math.sin(phi)); + this.tmpSphereVerts[i][j] = new oimo.common.Vec3(); + this.tmpSphereNorms[i][j] = new oimo.common.Vec3(); + } + } + this.circleCoords = new Array(8); + this.circleCoordsShift = new Array(8); + this.tmpCircleVerts1 = new Array(8); + this.tmpCircleVerts2 = new Array(8); + this.tmpCircleNorms = new Array(8); + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + this.circleCoords[i] = new oimo.common.Vec3(Math.cos(i * 0.7853981633974475),0,-Math.sin(i * 0.7853981633974475)); + this.circleCoordsShift[i] = new oimo.common.Vec3(Math.cos((i + 0.5) * 0.7853981633974475),0,-Math.sin((i + 0.5) * 0.7853981633974475)); + this.tmpCircleVerts1[i] = new oimo.common.Vec3(); + this.tmpCircleVerts2[i] = new oimo.common.Vec3(); + this.tmpCircleNorms[i] = new oimo.common.Vec3(); + } + this.style = new oimo.dynamics.common.DebugDrawStyle(); + } + aabb(min,max,color) { + let _this = this.p; + let v1 = (_this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]).init(min.x,min.y,min.z); + let _this1 = this.p; + let v2 = (_this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]).init(min.x,min.y,max.z); + let _this2 = this.p; + let v3 = (_this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]).init(min.x,max.y,min.z); + let _this3 = this.p; + let v4 = (_this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]).init(min.x,max.y,max.z); + let _this4 = this.p; + let v5 = (_this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]).init(max.x,min.y,min.z); + let _this5 = this.p; + let v6 = (_this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]).init(max.x,min.y,max.z); + let _this6 = this.p; + let v7 = (_this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]).init(max.x,max.y,min.z); + let _this7 = this.p; + let v8 = (_this7.sizeVec3 == 0 ? new oimo.common.Vec3() : _this7.stackVec3[--_this7.sizeVec3]).init(max.x,max.y,max.z); + this.line(v1,v2,color); + this.line(v3,v4,color); + this.line(v5,v6,color); + this.line(v7,v8,color); + this.line(v1,v3,color); + this.line(v2,v4,color); + this.line(v5,v7,color); + this.line(v6,v8,color); + this.line(v1,v5,color); + this.line(v2,v6,color); + this.line(v3,v7,color); + this.line(v4,v8,color); + let _this8 = this.p; + if(v1 != null) { + v1.zero(); + if(_this8.sizeVec3 == _this8.stackVec3.length) { + let newArray = new Array(_this8.sizeVec3 << 1); + let _g = 0; + let _g1 = _this8.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this8.stackVec3[i]; + _this8.stackVec3[i] = null; + } + _this8.stackVec3 = newArray; + } + _this8.stackVec3[_this8.sizeVec3++] = v1; + } + let _this9 = this.p; + if(v2 != null) { + v2.zero(); + if(_this9.sizeVec3 == _this9.stackVec3.length) { + let newArray = new Array(_this9.sizeVec3 << 1); + let _g = 0; + let _g1 = _this9.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackVec3[i]; + _this9.stackVec3[i] = null; + } + _this9.stackVec3 = newArray; + } + _this9.stackVec3[_this9.sizeVec3++] = v2; + } + let _this10 = this.p; + if(v3 != null) { + v3.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = v3; + } + let _this11 = this.p; + if(v4 != null) { + v4.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = v4; + } + let _this12 = this.p; + if(v5 != null) { + v5.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = v5; + } + let _this13 = this.p; + if(v6 != null) { + v6.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = v6; + } + let _this14 = this.p; + if(v7 != null) { + v7.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = v7; + } + let _this15 = this.p; + if(v8 != null) { + v8.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = v8; + } + } + basis(transform,length,colorX,colorY,colorZ) { + let _this = this.p; + let pos = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let rot = _this1.sizeMat3 == 0 ? new oimo.common.Mat3() : _this1.stackMat3[--_this1.sizeMat3]; + let _this2 = this.p; + let ex = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let ey = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let ez = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let v = pos; + v.x = transform._positionX; + v.y = transform._positionY; + v.z = transform._positionZ; + let m = rot; + m.e00 = transform._rotation00; + m.e01 = transform._rotation01; + m.e02 = transform._rotation02; + m.e10 = transform._rotation10; + m.e11 = transform._rotation11; + m.e12 = transform._rotation12; + m.e20 = transform._rotation20; + m.e21 = transform._rotation21; + m.e22 = transform._rotation22; + ex.init(rot.e00,rot.e10,rot.e20); + ey.init(rot.e01,rot.e11,rot.e21); + ez.init(rot.e02,rot.e12,rot.e22); + ex.x *= length; + ex.y *= length; + ex.z *= length; + let _this5 = ex; + _this5.x += pos.x; + _this5.y += pos.y; + _this5.z += pos.z; + ey.x *= length; + ey.y *= length; + ey.z *= length; + let _this6 = ey; + _this6.x += pos.x; + _this6.y += pos.y; + _this6.z += pos.z; + ez.x *= length; + ez.y *= length; + ez.z *= length; + let _this7 = ez; + _this7.x += pos.x; + _this7.y += pos.y; + _this7.z += pos.z; + this.line(pos,ex,colorX); + this.line(pos,ey,colorY); + this.line(pos,ez,colorZ); + let _this8 = this.p; + if(pos != null) { + pos.zero(); + if(_this8.sizeVec3 == _this8.stackVec3.length) { + let newArray = new Array(_this8.sizeVec3 << 1); + let _g = 0; + let _g1 = _this8.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this8.stackVec3[i]; + _this8.stackVec3[i] = null; + } + _this8.stackVec3 = newArray; + } + _this8.stackVec3[_this8.sizeVec3++] = pos; + } + let _this9 = this.p; + if(rot != null) { + rot.e00 = 1; + rot.e01 = 0; + rot.e02 = 0; + rot.e10 = 0; + rot.e11 = 1; + rot.e12 = 0; + rot.e20 = 0; + rot.e21 = 0; + rot.e22 = 1; + if(_this9.sizeMat3 == _this9.stackMat3.length) { + let newArray = new Array(_this9.sizeMat3 << 1); + let _g = 0; + let _g1 = _this9.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackMat3[i]; + _this9.stackMat3[i] = null; + } + _this9.stackMat3 = newArray; + } + _this9.stackMat3[_this9.sizeMat3++] = rot; + } + let _this10 = this.p; + if(ex != null) { + ex.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = ex; + } + let _this11 = this.p; + if(ey != null) { + ey.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = ey; + } + let _this12 = this.p; + if(ez != null) { + ez.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = ez; + } + } + ellipse(center,ex,ey,radiusX,radiusY,color) { + this.arc(center,ex,ey,radiusX,radiusY,0,6.28318530717958,false,color); + } + arc(center,ex,ey,radiusX,radiusY,startAngle,endAngle,drawSector,color) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ex.x; + _this1.y = ex.y; + _this1.z = ex.z; + let _this2 = _this1; + _this2.x *= radiusX; + _this2.y *= radiusX; + _this2.z *= radiusX; + + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = ey.x; + _this4.y = ey.y; + _this4.z = ey.z; + let _this5 = _this4; + _this5.x *= radiusY; + _this5.y *= radiusY; + _this5.z *= radiusY; + + let angDiff = endAngle - startAngle; + if(angDiff < 0) { + angDiff = -angDiff; + } + let n = angDiff / 0.52359877559829837 + 0.5 | 0; + if(n == 0) { + n = 1; + } + let theta = startAngle; + let dt = (endAngle - startAngle) / n; + let _this6 = this.p; + let _this7 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this7.x = center.x; + _this7.y = center.y; + _this7.z = center.z; + let _this8 = _this7; + let s = Math.cos(startAngle); + _this8.x += _this2.x * s; + _this8.y += _this2.y * s; + _this8.z += _this2.z * s; + let s1 = Math.sin(startAngle); + _this8.x += _this5.x * s1; + _this8.y += _this5.y * s1; + _this8.z += _this5.z * s1; + let prevV = _this8; + if(drawSector) { + this.line(center,_this8,color); + } + let _g = 0; + let _g1 = n; + while(_g < _g1) { + ++_g; + theta += dt; + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = center.x; + _this1.y = center.y; + _this1.z = center.z; + let _this3 = _this1; + let s = Math.cos(theta); + _this3.x += _this2.x * s; + _this3.y += _this2.y * s; + _this3.z += _this2.z * s; + let s1 = Math.sin(theta); + _this3.x += _this5.x * s1; + _this3.y += _this5.y * s1; + _this3.z += _this5.z * s1; + this.line(prevV,_this3,color); + let _this4 = this.p; + if(prevV != null) { + prevV.zero(); + if(_this4.sizeVec3 == _this4.stackVec3.length) { + let newArray = new Array(_this4.sizeVec3 << 1); + let _g = 0; + let _g1 = _this4.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this4.stackVec3[i]; + _this4.stackVec3[i] = null; + } + _this4.stackVec3 = newArray; + } + _this4.stackVec3[_this4.sizeVec3++] = prevV; + } + prevV = _this3; + } + if(drawSector) { + this.line(center,prevV,color); + } + let _this9 = this.p; + if(prevV != null) { + prevV.zero(); + if(_this9.sizeVec3 == _this9.stackVec3.length) { + let newArray = new Array(_this9.sizeVec3 << 1); + let _g = 0; + let _g1 = _this9.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackVec3[i]; + _this9.stackVec3[i] = null; + } + _this9.stackVec3 = newArray; + } + _this9.stackVec3[_this9.sizeVec3++] = prevV; + } + let _this10 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = _this2; + } + let _this11 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this5; + } + } + cone(tf,radius,halfHeight,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + _this7.x += ey.x * halfHeight; + _this7.y += ey.y * halfHeight; + _this7.z += ey.z * halfHeight; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s = -halfHeight; + _this10.x += ey.x * s; + _this10.y += ey.y * s; + _this10.z += ey.z * s; + if(this.wireframe) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = _this10.x; + _this1.y = _this10.y; + _this1.z = _this10.z; + let _this2 = _this1; + let s = -radius; + _this2.x += ex.x * s; + _this2.y += ex.y * s; + _this2.z += ex.z * s; + _this2.x += ez.x * 0; + _this2.y += ez.y * 0; + _this2.z += ez.z * 0; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = _this10.x; + _this4.y = _this10.y; + _this4.z = _this10.z; + let _this5 = _this4; + _this5.x += ex.x * radius; + _this5.y += ex.y * radius; + _this5.z += ex.z * radius; + _this5.x += ez.x * 0; + _this5.y += ez.y * 0; + _this5.z += ez.z * 0; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = _this10.x; + _this8.y = _this10.y; + _this8.z = _this10.z; + let _this9 = _this8; + _this9.x += ex.x * 0; + _this9.y += ex.y * 0; + _this9.z += ex.z * 0; + let s1 = -radius; + _this9.x += ez.x * s1; + _this9.y += ez.y * s1; + _this9.z += ez.z * s1; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = _this10.x; + _this12.y = _this10.y; + _this12.z = _this10.z; + let _this13 = _this12; + _this13.x += ex.x * 0; + _this13.y += ex.y * 0; + _this13.z += ex.z * 0; + _this13.x += ez.x * radius; + _this13.y += ez.y * radius; + _this13.z += ez.z * radius; + this.ellipse(_this10,ex,ez,radius,radius,color); + this.line(_this7,_this2,color); + this.line(_this7,_this5,color); + this.line(_this7,_this9,color); + this.line(_this7,_this13,color); + let _this14 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = _this2; + } + let _this15 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = _this5; + } + let _this16 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = _this9; + } + let _this17 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = _this13; + } + } else { + let invDenom = 1 / Math.sqrt(radius * radius + 4 * halfHeight * halfHeight); + let cos = 2 * halfHeight * invDenom; + let sin = radius * invDenom; + let _g = 0; + while(_g < 8) { + let i = _g++; + let _this = this.tmpCircleNorms[i]; + let v = this.circleCoords[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x *= cos; + _this.y *= cos; + _this.z *= cos; + _this.y += sin; + let _this1 = this.tmpCircleNorms[i]; + let y = _this1.x * m.e10 + _this1.y * m.e11 + _this1.z * m.e12; + let z = _this1.x * m.e20 + _this1.y * m.e21 + _this1.z * m.e22; + _this1.x = _this1.x * m.e00 + _this1.y * m.e01 + _this1.z * m.e02; + _this1.y = y; + _this1.z = z; + let _this2 = this.tmpCircleVerts1[i]; + let v1 = this.circleCoordsShift[i]; + _this2.x = v1.x; + _this2.y = v1.y; + _this2.z = v1.z; + _this2.x *= cos; + _this2.y *= cos; + _this2.z *= cos; + _this2.y += sin; + let _this3 = this.tmpCircleVerts1[i]; + let y1 = _this3.x * m.e10 + _this3.y * m.e11 + _this3.z * m.e12; + let z1 = _this3.x * m.e20 + _this3.y * m.e21 + _this3.z * m.e22; + _this3.x = _this3.x * m.e00 + _this3.y * m.e01 + _this3.z * m.e02; + _this3.y = y1; + _this3.z = z1; + let _this4 = this.tmpCircleVerts2[i]; + let v2 = this.circleCoords[i]; + _this4.x = v2.x; + _this4.y = v2.y; + _this4.z = v2.z; + let y2 = _this4.x * m.e10 + _this4.y * m.e11 + _this4.z * m.e12; + let z2 = _this4.x * m.e20 + _this4.y * m.e21 + _this4.z * m.e22; + _this4.x = _this4.x * m.e00 + _this4.y * m.e01 + _this4.z * m.e02; + _this4.y = y2; + _this4.z = z2; + _this4.x *= radius; + _this4.y *= radius; + _this4.z *= radius; + _this4.x += o.x; + _this4.y += o.y; + _this4.z += o.z; + let _this5 = this.tmpCircleVerts2[i]; + let s = -halfHeight; + _this5.x += ey.x * s; + _this5.y += ey.y * s; + _this5.z += ey.z * s; + } + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + let v2 = this.tmpCircleVerts2[i]; + let v3 = this.tmpCircleVerts2[(i + 1) % 8]; + let n1 = this.tmpCircleVerts1[i]; + this.triangle(_this7,v2,v3,n1,this.tmpCircleNorms[i],this.tmpCircleNorms[(i + 1) % 8],color); + v2 = this.tmpCircleVerts2[(i + 1) % 8]; + v3 = this.tmpCircleVerts2[i]; + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ey.x; + _this1.y = ey.y; + _this1.z = ey.z; + let _this2 = _this1; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + + this.triangle(_this10,v2,v3,_this2,_this2,_this2,color); + let _this3 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = _this2; + } + } + } + let _this11 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this7; + } + let _this12 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this10; + } + let _this13 = this.p; + if(o != null) { + o.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = o; + } + let _this14 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this14.sizeMat3 == _this14.stackMat3.length) { + let newArray = new Array(_this14.sizeMat3 << 1); + let _g = 0; + let _g1 = _this14.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackMat3[i]; + _this14.stackMat3[i] = null; + } + _this14.stackMat3 = newArray; + } + _this14.stackMat3[_this14.sizeMat3++] = m; + } + let _this15 = this.p; + if(ex != null) { + ex.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = ex; + } + let _this16 = this.p; + if(ey != null) { + ey.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = ey; + } + let _this17 = this.p; + if(ez != null) { + ez.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = ez; + } + } + cylinder(tf,radius,halfHeight,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + _this7.x += ey.x * halfHeight; + _this7.y += ey.y * halfHeight; + _this7.z += ey.z * halfHeight; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s = -halfHeight; + _this10.x += ey.x * s; + _this10.y += ey.y * s; + _this10.z += ey.z * s; + if(this.wireframe) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = _this7.x; + _this1.y = _this7.y; + _this1.z = _this7.z; + let _this2 = _this1; + let s = -radius; + _this2.x += ex.x * s; + _this2.y += ex.y * s; + _this2.z += ex.z * s; + _this2.x += ez.x * 0; + _this2.y += ez.y * 0; + _this2.z += ez.z * 0; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = _this7.x; + _this4.y = _this7.y; + _this4.z = _this7.z; + let _this5 = _this4; + _this5.x += ex.x * radius; + _this5.y += ex.y * radius; + _this5.z += ex.z * radius; + _this5.x += ez.x * 0; + _this5.y += ez.y * 0; + _this5.z += ez.z * 0; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = _this7.x; + _this8.y = _this7.y; + _this8.z = _this7.z; + let _this9 = _this8; + _this9.x += ex.x * 0; + _this9.y += ex.y * 0; + _this9.z += ex.z * 0; + let s1 = -radius; + _this9.x += ez.x * s1; + _this9.y += ez.y * s1; + _this9.z += ez.z * s1; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = _this7.x; + _this12.y = _this7.y; + _this12.z = _this7.z; + let _this13 = _this12; + _this13.x += ex.x * 0; + _this13.y += ex.y * 0; + _this13.z += ex.z * 0; + _this13.x += ez.x * radius; + _this13.y += ez.y * radius; + _this13.z += ez.z * radius; + let _this14 = this.p; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = _this10.x; + _this15.y = _this10.y; + _this15.z = _this10.z; + let _this16 = _this15; + let s2 = -radius; + _this16.x += ex.x * s2; + _this16.y += ex.y * s2; + _this16.z += ex.z * s2; + _this16.x += ez.x * 0; + _this16.y += ez.y * 0; + _this16.z += ez.z * 0; + let _this17 = this.p; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = _this10.x; + _this18.y = _this10.y; + _this18.z = _this10.z; + let _this19 = _this18; + _this19.x += ex.x * radius; + _this19.y += ex.y * radius; + _this19.z += ex.z * radius; + _this19.x += ez.x * 0; + _this19.y += ez.y * 0; + _this19.z += ez.z * 0; + let _this20 = this.p; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = _this10.x; + _this21.y = _this10.y; + _this21.z = _this10.z; + let _this22 = _this21; + _this22.x += ex.x * 0; + _this22.y += ex.y * 0; + _this22.z += ex.z * 0; + let s3 = -radius; + _this22.x += ez.x * s3; + _this22.y += ez.y * s3; + _this22.z += ez.z * s3; + let _this23 = this.p; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = _this10.x; + _this24.y = _this10.y; + _this24.z = _this10.z; + let _this25 = _this24; + _this25.x += ex.x * 0; + _this25.y += ex.y * 0; + _this25.z += ex.z * 0; + _this25.x += ez.x * radius; + _this25.y += ez.y * radius; + _this25.z += ez.z * radius; + this.ellipse(_this7,ex,ez,radius,radius,color); + this.ellipse(_this10,ex,ez,radius,radius,color); + this.line(_this2,_this16,color); + this.line(_this5,_this19,color); + this.line(_this9,_this22,color); + this.line(_this13,_this25,color); + let _this26 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = _this2; + } + let _this27 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = _this5; + } + let _this28 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = _this9; + } + let _this29 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this13; + } + let _this30 = this.p; + if(_this16 != null) { + _this16.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this16; + } + let _this31 = this.p; + if(_this19 != null) { + _this19.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this19; + } + let _this32 = this.p; + if(_this22 != null) { + _this22.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this22; + } + let _this33 = this.p; + if(_this25 != null) { + _this25.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this25; + } + } else { + let _g = 0; + while(_g < 8) { + let i = _g++; + let _this = this.tmpCircleNorms[i]; + let v = this.circleCoords[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + let _this1 = this.tmpCircleVerts1[i]; + let v1 = this.tmpCircleNorms[i]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + _this1.x *= radius; + _this1.y *= radius; + _this1.z *= radius; + _this1.x += o.x; + _this1.y += o.y; + _this1.z += o.z; + let _this2 = this.tmpCircleVerts2[i]; + let v2 = this.tmpCircleVerts1[i]; + _this2.x = v2.x; + _this2.y = v2.y; + _this2.z = v2.z; + let _this3 = this.tmpCircleVerts1[i]; + _this3.x += ey.x * halfHeight; + _this3.y += ey.y * halfHeight; + _this3.z += ey.z * halfHeight; + let _this4 = this.tmpCircleVerts2[i]; + let s = -halfHeight; + _this4.x += ey.x * s; + _this4.y += ey.y * s; + _this4.z += ey.z * s; + } + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + let v1; + let v2 = this.tmpCircleVerts1[i]; + let v3 = this.tmpCircleVerts1[(i + 1) % 8]; + let n1 = ey; + this.triangle(_this7,v2,v3,n1,n1,n1,color); + + v2 = this.tmpCircleVerts2[(i + 1) % 8]; + v3 = this.tmpCircleVerts2[i]; + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ey.x; + _this1.y = ey.y; + _this1.z = ey.z; + let _this2 = _this1; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + + this.triangle(_this10,v2,v3,_this2,_this2,_this2,color); + let _this3 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = _this2; + } + v1 = this.tmpCircleVerts1[i]; + v2 = this.tmpCircleVerts2[i]; + v3 = this.tmpCircleVerts2[(i + 1) % 8]; + n1 = this.tmpCircleNorms[i]; + let n2 = this.tmpCircleNorms[(i + 1) % 8]; + this.rect(v1,v2,v3,this.tmpCircleVerts1[(i + 1) % 8],n1,n1,n2,n2,color); + } + } + let _this11 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this7; + } + let _this12 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this10; + } + let _this13 = this.p; + if(o != null) { + o.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = o; + } + let _this14 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this14.sizeMat3 == _this14.stackMat3.length) { + let newArray = new Array(_this14.sizeMat3 << 1); + let _g = 0; + let _g1 = _this14.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackMat3[i]; + _this14.stackMat3[i] = null; + } + _this14.stackMat3 = newArray; + } + _this14.stackMat3[_this14.sizeMat3++] = m; + } + let _this15 = this.p; + if(ex != null) { + ex.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = ex; + } + let _this16 = this.p; + if(ey != null) { + ey.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = ey; + } + let _this17 = this.p; + if(ez != null) { + ez.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = ez; + } + } + capsule(tf,radius,halfHeight,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let vs = this.tmpSphereVerts; + let ns = this.tmpSphereNorms; + let _g = 0; + while(_g < 5) { + let i2 = _g++; + let n = this.tmpSphereVerts[i2].length; + let _g1 = 0; + while(_g1 < n) { + let j2 = _g1++; + let _this = ns[i2][j2]; + let v = this.sphereCoords[i2][j2]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + } + } + let _g1 = 0; + while(_g1 < 4) { + let i = _g1++; + if(i == 0) { + let _g = 0; + while(_g < 3) { + let i2 = _g++; + let n = this.tmpSphereVerts[i2].length; + let _g1 = 0; + while(_g1 < n) { + let j2 = _g1++; + let _this = vs[i2][j2]; + let v = ns[i2][j2]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x *= radius; + _this.y *= radius; + _this.z *= radius; + _this.x += o.x; + _this.y += o.y; + _this.z += o.z; + _this.x += ey.x * halfHeight; + _this.y += ey.y * halfHeight; + _this.z += ey.z * halfHeight; + } + } + } + if(i == 2) { + let _g = 2; + while(_g < 5) { + let i2 = _g++; + let n = this.tmpSphereVerts[i2].length; + let _g1 = 0; + while(_g1 < n) { + let j2 = _g1++; + let _this = vs[i2][j2]; + let v = ns[i2][j2]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x *= radius; + _this.y *= radius; + _this.z *= radius; + _this.x += o.x; + _this.y += o.y; + _this.z += o.z; + let s = -halfHeight; + _this.x += ey.x * s; + _this.y += ey.y * s; + _this.z += ey.z * s; + } + } + } + let _g = 0; + while(_g < 8) { + let j = _g++; + let v1; + let v2; + let v3; + let v4; + let n1; + let n2; + let n3; + let n4; + if(i == 0) { + if(this.wireframe) { + v1 = vs[0][0]; + v2 = vs[1][j]; + this.line(v1,v2,color); + } else { + v1 = vs[0][0]; + v2 = vs[1][j]; + v3 = vs[1][(j + 1) % 8]; + n1 = ns[0][0]; + n2 = ns[1][j]; + n3 = ns[1][(j + 1) % 8]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(i == 3) { + if(this.wireframe) { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + this.line(v1,v2,color); + this.line(v2,v3,color); + } else { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + n1 = ns[4][0]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i][j]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(this.wireframe) { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + this.line(v1,v2,color); + this.line(v1,v3,color); + } else { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + v4 = vs[i + 1][(j + 1) % 8]; + n1 = ns[i][j]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i + 1][j]; + n4 = ns[i + 1][(j + 1) % 8]; + this.rect(v1,v3,v4,v2,n1,n3,n4,n2,color); + } + } + } + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + _this7.x += ey.x * halfHeight; + _this7.y += ey.y * halfHeight; + _this7.z += ey.z * halfHeight; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s = -halfHeight; + _this10.x += ey.x * s; + _this10.y += ey.y * s; + _this10.z += ey.z * s; + if(this.wireframe) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = _this7.x; + _this1.y = _this7.y; + _this1.z = _this7.z; + let _this2 = _this1; + let s = -radius; + _this2.x += ex.x * s; + _this2.y += ex.y * s; + _this2.z += ex.z * s; + _this2.x += ez.x * 0; + _this2.y += ez.y * 0; + _this2.z += ez.z * 0; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = _this7.x; + _this4.y = _this7.y; + _this4.z = _this7.z; + let _this5 = _this4; + _this5.x += ex.x * radius; + _this5.y += ex.y * radius; + _this5.z += ex.z * radius; + _this5.x += ez.x * 0; + _this5.y += ez.y * 0; + _this5.z += ez.z * 0; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = _this7.x; + _this8.y = _this7.y; + _this8.z = _this7.z; + let _this9 = _this8; + _this9.x += ex.x * 0; + _this9.y += ex.y * 0; + _this9.z += ex.z * 0; + let s1 = -radius; + _this9.x += ez.x * s1; + _this9.y += ez.y * s1; + _this9.z += ez.z * s1; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = _this7.x; + _this12.y = _this7.y; + _this12.z = _this7.z; + let _this13 = _this12; + _this13.x += ex.x * 0; + _this13.y += ex.y * 0; + _this13.z += ex.z * 0; + _this13.x += ez.x * radius; + _this13.y += ez.y * radius; + _this13.z += ez.z * radius; + let _this14 = this.p; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = _this10.x; + _this15.y = _this10.y; + _this15.z = _this10.z; + let _this16 = _this15; + let s2 = -radius; + _this16.x += ex.x * s2; + _this16.y += ex.y * s2; + _this16.z += ex.z * s2; + _this16.x += ez.x * 0; + _this16.y += ez.y * 0; + _this16.z += ez.z * 0; + let _this17 = this.p; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = _this10.x; + _this18.y = _this10.y; + _this18.z = _this10.z; + let _this19 = _this18; + _this19.x += ex.x * radius; + _this19.y += ex.y * radius; + _this19.z += ex.z * radius; + _this19.x += ez.x * 0; + _this19.y += ez.y * 0; + _this19.z += ez.z * 0; + let _this20 = this.p; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = _this10.x; + _this21.y = _this10.y; + _this21.z = _this10.z; + let _this22 = _this21; + _this22.x += ex.x * 0; + _this22.y += ex.y * 0; + _this22.z += ex.z * 0; + let s3 = -radius; + _this22.x += ez.x * s3; + _this22.y += ez.y * s3; + _this22.z += ez.z * s3; + let _this23 = this.p; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = _this10.x; + _this24.y = _this10.y; + _this24.z = _this10.z; + let _this25 = _this24; + _this25.x += ex.x * 0; + _this25.y += ex.y * 0; + _this25.z += ex.z * 0; + _this25.x += ez.x * radius; + _this25.y += ez.y * radius; + _this25.z += ez.z * radius; + this.ellipse(_this7,ex,ez,radius,radius,color); + this.ellipse(_this10,ex,ez,radius,radius,color); + this.line(_this2,_this16,color); + this.line(_this5,_this19,color); + this.line(_this9,_this22,color); + this.line(_this13,_this25,color); + let _this26 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = _this2; + } + let _this27 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = _this5; + } + let _this28 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = _this9; + } + let _this29 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this13; + } + let _this30 = this.p; + if(_this16 != null) { + _this16.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this16; + } + let _this31 = this.p; + if(_this19 != null) { + _this19.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this19; + } + let _this32 = this.p; + if(_this22 != null) { + _this22.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this22; + } + let _this33 = this.p; + if(_this25 != null) { + _this25.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this25; + } + } else { + let _g = 0; + while(_g < 8) { + let i = _g++; + let _this = this.tmpCircleNorms[i]; + let v = this.circleCoords[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + let _this1 = this.tmpCircleVerts1[i]; + let v1 = this.tmpCircleNorms[i]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + _this1.x *= radius; + _this1.y *= radius; + _this1.z *= radius; + _this1.x += o.x; + _this1.y += o.y; + _this1.z += o.z; + let _this2 = this.tmpCircleVerts2[i]; + let v2 = this.tmpCircleVerts1[i]; + _this2.x = v2.x; + _this2.y = v2.y; + _this2.z = v2.z; + let _this3 = this.tmpCircleVerts1[i]; + _this3.x += ey.x * halfHeight; + _this3.y += ey.y * halfHeight; + _this3.z += ey.z * halfHeight; + let _this4 = this.tmpCircleVerts2[i]; + let s = -halfHeight; + _this4.x += ey.x * s; + _this4.y += ey.y * s; + _this4.z += ey.z * s; + } + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + let n1 = this.tmpCircleNorms[i]; + let n2 = this.tmpCircleNorms[(i + 1) % 8]; + this.rect(this.tmpCircleVerts1[i],this.tmpCircleVerts2[i],this.tmpCircleVerts2[(i + 1) % 8],this.tmpCircleVerts1[(i + 1) % 8],n1,n1,n2,n2,color); + } + } + let _this11 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this7; + } + let _this12 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this10; + } + let _this13 = this.p; + if(o != null) { + o.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = o; + } + let _this14 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this14.sizeMat3 == _this14.stackMat3.length) { + let newArray = new Array(_this14.sizeMat3 << 1); + let _g = 0; + let _g1 = _this14.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackMat3[i]; + _this14.stackMat3[i] = null; + } + _this14.stackMat3 = newArray; + } + _this14.stackMat3[_this14.sizeMat3++] = m; + } + let _this15 = this.p; + if(ex != null) { + ex.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = ex; + } + let _this16 = this.p; + if(ey != null) { + ey.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = ey; + } + let _this17 = this.p; + if(ez != null) { + ez.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = ez; + } + } + sphere(tf,radius,color) { + let _this = this.p; + let o = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let m = _this1.sizeMat3 == 0 ? new oimo.common.Mat3() : _this1.stackMat3[--_this1.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + let vs = this.tmpSphereVerts; + let ns = this.tmpSphereNorms; + let _g = 0; + while(_g < 5) { + let i = _g++; + let n = this.tmpSphereVerts[i].length; + let _g1 = 0; + while(_g1 < n) { + let j = _g1++; + let _this = ns[i][j]; + let v = this.sphereCoords[i][j]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + let _this1 = vs[i][j]; + let v1 = ns[i][j]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + _this1.x *= radius; + _this1.y *= radius; + _this1.z *= radius; + _this1.x += o.x; + _this1.y += o.y; + _this1.z += o.z; + } + } + let _g1 = 0; + while(_g1 < 4) { + let i = _g1++; + let _g = 0; + while(_g < 8) { + let j = _g++; + let v1; + let v2; + let v3; + let v4; + let n1; + let n2; + let n3; + let n4; + if(i == 0) { + if(this.wireframe) { + v1 = vs[0][0]; + v2 = vs[1][j]; + this.line(v1,v2,color); + } else { + v1 = vs[0][0]; + v2 = vs[1][j]; + v3 = vs[1][(j + 1) % 8]; + n1 = ns[0][0]; + n2 = ns[1][j]; + n3 = ns[1][(j + 1) % 8]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(i == 3) { + if(this.wireframe) { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + this.line(v1,v2,color); + this.line(v2,v3,color); + } else { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + n1 = ns[4][0]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i][j]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(this.wireframe) { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + this.line(v1,v2,color); + this.line(v1,v3,color); + } else { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + v4 = vs[i + 1][(j + 1) % 8]; + n1 = ns[i][j]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i + 1][j]; + n4 = ns[i + 1][(j + 1) % 8]; + this.rect(v1,v3,v4,v2,n1,n3,n4,n2,color); + } + } + } + let _this2 = this.p; + if(o != null) { + o.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = o; + } + let _this3 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this3.sizeMat3 == _this3.stackMat3.length) { + let newArray = new Array(_this3.sizeMat3 << 1); + let _g = 0; + let _g1 = _this3.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackMat3[i]; + _this3.stackMat3[i] = null; + } + _this3.stackMat3 = newArray; + } + _this3.stackMat3[_this3.sizeMat3++] = m; + } + } + box(tf,halfExtents,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let hx = halfExtents.x; + let hy = halfExtents.y; + let hz = halfExtents.z; + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + let s = -hx; + _this7.x += ex.x * s; + _this7.y += ex.y * s; + _this7.z += ex.z * s; + let s1 = -hy; + _this7.x += ey.x * s1; + _this7.y += ey.y * s1; + _this7.z += ey.z * s1; + let s2 = -hz; + _this7.x += ez.x * s2; + _this7.y += ez.y * s2; + _this7.z += ez.z * s2; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s3 = -hx; + _this10.x += ex.x * s3; + _this10.y += ex.y * s3; + _this10.z += ex.z * s3; + let s4 = -hy; + _this10.x += ey.x * s4; + _this10.y += ey.y * s4; + _this10.z += ey.z * s4; + _this10.x += ez.x * hz; + _this10.y += ez.y * hz; + _this10.z += ez.z * hz; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = o.x; + _this12.y = o.y; + _this12.z = o.z; + let _this13 = _this12; + let s5 = -hx; + _this13.x += ex.x * s5; + _this13.y += ex.y * s5; + _this13.z += ex.z * s5; + _this13.x += ey.x * hy; + _this13.y += ey.y * hy; + _this13.z += ey.z * hy; + let s6 = -hz; + _this13.x += ez.x * s6; + _this13.y += ez.y * s6; + _this13.z += ez.z * s6; + let _this14 = this.p; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = o.x; + _this15.y = o.y; + _this15.z = o.z; + let _this16 = _this15; + let s7 = -hx; + _this16.x += ex.x * s7; + _this16.y += ex.y * s7; + _this16.z += ex.z * s7; + _this16.x += ey.x * hy; + _this16.y += ey.y * hy; + _this16.z += ey.z * hy; + _this16.x += ez.x * hz; + _this16.y += ez.y * hz; + _this16.z += ez.z * hz; + let _this17 = this.p; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = o.x; + _this18.y = o.y; + _this18.z = o.z; + let _this19 = _this18; + _this19.x += ex.x * hx; + _this19.y += ex.y * hx; + _this19.z += ex.z * hx; + let s8 = -hy; + _this19.x += ey.x * s8; + _this19.y += ey.y * s8; + _this19.z += ey.z * s8; + let s9 = -hz; + _this19.x += ez.x * s9; + _this19.y += ez.y * s9; + _this19.z += ez.z * s9; + let _this20 = this.p; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = o.x; + _this21.y = o.y; + _this21.z = o.z; + let _this22 = _this21; + _this22.x += ex.x * hx; + _this22.y += ex.y * hx; + _this22.z += ex.z * hx; + let s10 = -hy; + _this22.x += ey.x * s10; + _this22.y += ey.y * s10; + _this22.z += ey.z * s10; + _this22.x += ez.x * hz; + _this22.y += ez.y * hz; + _this22.z += ez.z * hz; + let _this23 = this.p; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = o.x; + _this24.y = o.y; + _this24.z = o.z; + let _this25 = _this24; + _this25.x += ex.x * hx; + _this25.y += ex.y * hx; + _this25.z += ex.z * hx; + _this25.x += ey.x * hy; + _this25.y += ey.y * hy; + _this25.z += ey.z * hy; + let s11 = -hz; + _this25.x += ez.x * s11; + _this25.y += ez.y * s11; + _this25.z += ez.z * s11; + let _this26 = this.p; + let _this27 = _this26.sizeVec3 == 0 ? new oimo.common.Vec3() : _this26.stackVec3[--_this26.sizeVec3]; + _this27.x = o.x; + _this27.y = o.y; + _this27.z = o.z; + let _this28 = _this27; + _this28.x += ex.x * hx; + _this28.y += ex.y * hx; + _this28.z += ex.z * hx; + _this28.x += ey.x * hy; + _this28.y += ey.y * hy; + _this28.z += ey.z * hy; + _this28.x += ez.x * hz; + _this28.y += ez.y * hz; + _this28.z += ez.z * hz; + if(this.wireframe) { + this.line(_this7,_this10,color); + this.line(_this13,_this16,color); + this.line(_this19,_this22,color); + this.line(_this25,_this28,color); + this.line(_this7,_this13,color); + this.line(_this10,_this16,color); + this.line(_this19,_this25,color); + this.line(_this22,_this28,color); + this.line(_this7,_this19,color); + this.line(_this10,_this22,color); + this.line(_this13,_this25,color); + this.line(_this16,_this28,color); + } else { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ex.x; + _this1.y = ex.y; + _this1.z = ex.z; + let _this2 = _this1; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = ey.x; + _this4.y = ey.y; + _this4.z = ey.z; + let _this5 = _this4; + _this5.x = -_this5.x; + _this5.y = -_this5.y; + _this5.z = -_this5.z; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = ez.x; + _this8.y = ez.y; + _this8.z = ez.z; + let _this9 = _this8; + _this9.x = -_this9.x; + _this9.y = -_this9.y; + _this9.z = -_this9.z; + this.rect(_this7,_this10,_this16,_this13,_this2,_this2,_this2,_this2,color); + this.rect(_this19,_this25,_this28,_this22,ex,ex,ex,ex,color); + this.rect(_this7,_this19,_this22,_this10,_this5,_this5,_this5,_this5,color); + this.rect(_this13,_this16,_this28,_this25,ey,ey,ey,ey,color); + this.rect(_this7,_this13,_this25,_this19,_this9,_this9,_this9,_this9,color); + this.rect(_this10,_this22,_this28,_this16,ez,ez,ez,ez,color); + let _this11 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this2; + } + let _this12 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this5; + } + let _this14 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = _this9; + } + } + let _this29 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this7; + } + let _this30 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this10; + } + let _this31 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this13; + } + let _this32 = this.p; + if(_this16 != null) { + _this16.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this16; + } + let _this33 = this.p; + if(_this19 != null) { + _this19.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this19; + } + let _this34 = this.p; + if(_this22 != null) { + _this22.zero(); + if(_this34.sizeVec3 == _this34.stackVec3.length) { + let newArray = new Array(_this34.sizeVec3 << 1); + let _g = 0; + let _g1 = _this34.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this34.stackVec3[i]; + _this34.stackVec3[i] = null; + } + _this34.stackVec3 = newArray; + } + _this34.stackVec3[_this34.sizeVec3++] = _this22; + } + let _this35 = this.p; + if(_this25 != null) { + _this25.zero(); + if(_this35.sizeVec3 == _this35.stackVec3.length) { + let newArray = new Array(_this35.sizeVec3 << 1); + let _g = 0; + let _g1 = _this35.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this35.stackVec3[i]; + _this35.stackVec3[i] = null; + } + _this35.stackVec3 = newArray; + } + _this35.stackVec3[_this35.sizeVec3++] = _this25; + } + let _this36 = this.p; + if(_this28 != null) { + _this28.zero(); + if(_this36.sizeVec3 == _this36.stackVec3.length) { + let newArray = new Array(_this36.sizeVec3 << 1); + let _g = 0; + let _g1 = _this36.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this36.stackVec3[i]; + _this36.stackVec3[i] = null; + } + _this36.stackVec3 = newArray; + } + _this36.stackVec3[_this36.sizeVec3++] = _this28; + } + let _this37 = this.p; + if(o != null) { + o.zero(); + if(_this37.sizeVec3 == _this37.stackVec3.length) { + let newArray = new Array(_this37.sizeVec3 << 1); + let _g = 0; + let _g1 = _this37.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this37.stackVec3[i]; + _this37.stackVec3[i] = null; + } + _this37.stackVec3 = newArray; + } + _this37.stackVec3[_this37.sizeVec3++] = o; + } + let _this38 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this38.sizeMat3 == _this38.stackMat3.length) { + let newArray = new Array(_this38.sizeMat3 << 1); + let _g = 0; + let _g1 = _this38.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this38.stackMat3[i]; + _this38.stackMat3[i] = null; + } + _this38.stackMat3 = newArray; + } + _this38.stackMat3[_this38.sizeMat3++] = m; + } + let _this39 = this.p; + if(ex != null) { + ex.zero(); + if(_this39.sizeVec3 == _this39.stackVec3.length) { + let newArray = new Array(_this39.sizeVec3 << 1); + let _g = 0; + let _g1 = _this39.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this39.stackVec3[i]; + _this39.stackVec3[i] = null; + } + _this39.stackVec3 = newArray; + } + _this39.stackVec3[_this39.sizeVec3++] = ex; + } + let _this40 = this.p; + if(ey != null) { + ey.zero(); + if(_this40.sizeVec3 == _this40.stackVec3.length) { + let newArray = new Array(_this40.sizeVec3 << 1); + let _g = 0; + let _g1 = _this40.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this40.stackVec3[i]; + _this40.stackVec3[i] = null; + } + _this40.stackVec3 = newArray; + } + _this40.stackVec3[_this40.sizeVec3++] = ey; + } + let _this41 = this.p; + if(ez != null) { + ez.zero(); + if(_this41.sizeVec3 == _this41.stackVec3.length) { + let newArray = new Array(_this41.sizeVec3 << 1); + let _g = 0; + let _g1 = _this41.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this41.stackVec3[i]; + _this41.stackVec3[i] = null; + } + _this41.stackVec3 = newArray; + } + _this41.stackVec3[_this41.sizeVec3++] = ez; + } + } + rect(v1,v2,v3,v4,n1,n2,n3,n4,color) { + this.triangle(v1,v2,v3,n1,n2,n3,color); + this.triangle(v1,v3,v4,n1,n3,n4,color); + } + point(v,color) { + } + triangle(v1,v2,v3,n1,n2,n3,color) { + } + line(v1,v2,color) { + } +} +oimo.dynamics.common.DebugDrawStyle = class oimo_dynamics_common_DebugDrawStyle { + constructor() { + this.basisColorZ = new oimo.common.Vec3(0.0,0.0,1.0); + this.basisColorY = new oimo.common.Vec3(0.0,1.0,0.0); + this.basisColorX = new oimo.common.Vec3(1.0,0.0,0.0); + this.basisLength = 0.5; + this.jointRotationalConstraintRadius = 0.3; + this.jointErrorColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.jointLineColor = new oimo.common.Vec3(0.8,0.8,0.8); + this.contactBinormalLength = 0.5; + this.contactTangentLength = 0.5; + this.contactNormalLength = 0.5; + this.contactBinormalColor = new oimo.common.Vec3(0.2,0.2,1.0); + this.contactTangentColor = new oimo.common.Vec3(0.1,0.8,0.1); + this.contactNormalColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.disabledContactColor = new oimo.common.Vec3(0.5,0.1,0.1); + this.newContactColor = new oimo.common.Vec3(1.0,1.0,0.1); + this.contactColor4 = new oimo.common.Vec3(0.8,0.1,1.0); + this.contactColor3 = new oimo.common.Vec3(0.1,0.8,0.6); + this.contactColor2 = new oimo.common.Vec3(1.0,0.6,0.1); + this.contactColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.pairColor = new oimo.common.Vec3(1.0,1.0,0.1); + this.bvhNodeColor = new oimo.common.Vec3(0.4,0.4,0.4); + this.aabbColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.kinematicShapeColor = new oimo.common.Vec3(1.0,0.5,0.1); + this.staticShapeColor = new oimo.common.Vec3(0.7,0.7,0.7); + this.sleepingShapeColor2 = new oimo.common.Vec3(0.2,0.8,0.5); + this.sleepingShapeColor1 = new oimo.common.Vec3(0.3,0.3,0.8); + this.sleepyShapeColor2 = new oimo.common.Vec3(0.6,0.8,0.3); + this.sleepyShapeColor1 = new oimo.common.Vec3(0.5,0.25,0.6); + this.shapeColor2 = new oimo.common.Vec3(1.0,0.8,0.1); + this.shapeColor1 = new oimo.common.Vec3(0.7,0.2,0.4); + } +} +oimo.dynamics.common.Performance = class oimo_dynamics_common_Performance { +} +if(!oimo.dynamics.constraint) oimo.dynamics.constraint = {}; +oimo.dynamics.constraint.ConstraintSolver = class oimo_dynamics_constraint_ConstraintSolver { + constructor() { + this._b1 = null; + this._b2 = null; + this._addedToIsland = false; + } + preSolveVelocity(timeStep) { + } + warmStart(timeStep) { + } + solveVelocity() { + } + postSolveVelocity(timeStep) { + } + preSolvePosition(timeStep) { + } + solvePositionSplitImpulse() { + } + solvePositionNgs(timeStep) { + } + postSolve() { + } +} +oimo.dynamics.constraint.PositionCorrectionAlgorithm = class oimo_dynamics_constraint_PositionCorrectionAlgorithm { +} +if(!oimo.dynamics.constraint.contact) oimo.dynamics.constraint.contact = {}; +oimo.dynamics.constraint.contact.ContactConstraint = class oimo_dynamics_constraint_contact_ContactConstraint { + constructor(manifold) { + this._solver = new oimo.dynamics.constraint.solver.pgs.PgsContactConstraintSolver(this); + this._manifold = manifold; + } + _getVelocitySolverInfo(timeStep,info) { + info.b1 = this._b1; + info.b2 = this._b2; + let normalX; + let normalY; + let normalZ; + let tangentX; + let tangentY; + let tangentZ; + let binormalX; + let binormalY; + let binormalZ; + normalX = this._manifold._normalX; + normalY = this._manifold._normalY; + normalZ = this._manifold._normalZ; + tangentX = this._manifold._tangentX; + tangentY = this._manifold._tangentY; + tangentZ = this._manifold._tangentZ; + binormalX = this._manifold._binormalX; + binormalY = this._manifold._binormalY; + binormalZ = this._manifold._binormalZ; + let friction = Math.sqrt(this._s1._friction * this._s2._friction); + let restitution = Math.sqrt(this._s1._restitution * this._s2._restitution); + let num = this._manifold._numPoints; + info.numRows = 0; + let _g = 0; + while(_g < num) { + let p = this._manifold._points[_g++]; + if(p._depth < 0) { + p._disabled = true; + let _this = p._impulse; + _this.impulseN = 0; + _this.impulseT = 0; + _this.impulseB = 0; + _this.impulseP = 0; + _this.impulseLX = 0; + _this.impulseLY = 0; + _this.impulseLZ = 0; + continue; + } else { + p._disabled = false; + } + let row = info.rows[info.numRows++]; + row.friction = friction; + row.cfm = 0; + let j = row.jacobianN; + j.lin1X = normalX; + j.lin1Y = normalY; + j.lin1Z = normalZ; + j.lin2X = normalX; + j.lin2Y = normalY; + j.lin2Z = normalZ; + j.ang1X = p._relPos1Y * normalZ - p._relPos1Z * normalY; + j.ang1Y = p._relPos1Z * normalX - p._relPos1X * normalZ; + j.ang1Z = p._relPos1X * normalY - p._relPos1Y * normalX; + j.ang2X = p._relPos2Y * normalZ - p._relPos2Z * normalY; + j.ang2Y = p._relPos2Z * normalX - p._relPos2X * normalZ; + j.ang2Z = p._relPos2X * normalY - p._relPos2Y * normalX; + j = row.jacobianT; + j.lin1X = tangentX; + j.lin1Y = tangentY; + j.lin1Z = tangentZ; + j.lin2X = tangentX; + j.lin2Y = tangentY; + j.lin2Z = tangentZ; + j.ang1X = p._relPos1Y * tangentZ - p._relPos1Z * tangentY; + j.ang1Y = p._relPos1Z * tangentX - p._relPos1X * tangentZ; + j.ang1Z = p._relPos1X * tangentY - p._relPos1Y * tangentX; + j.ang2X = p._relPos2Y * tangentZ - p._relPos2Z * tangentY; + j.ang2Y = p._relPos2Z * tangentX - p._relPos2X * tangentZ; + j.ang2Z = p._relPos2X * tangentY - p._relPos2Y * tangentX; + j = row.jacobianB; + j.lin1X = binormalX; + j.lin1Y = binormalY; + j.lin1Z = binormalZ; + j.lin2X = binormalX; + j.lin2Y = binormalY; + j.lin2Z = binormalZ; + j.ang1X = p._relPos1Y * binormalZ - p._relPos1Z * binormalY; + j.ang1Y = p._relPos1Z * binormalX - p._relPos1X * binormalZ; + j.ang1Z = p._relPos1X * binormalY - p._relPos1Y * binormalX; + j.ang2X = p._relPos2Y * binormalZ - p._relPos2Z * binormalY; + j.ang2Y = p._relPos2Z * binormalX - p._relPos2X * binormalZ; + j.ang2Z = p._relPos2X * binormalY - p._relPos2Y * binormalX; + j = row.jacobianN; + let rvn = j.lin1X * this._b1._velX + j.lin1Y * this._b1._velY + j.lin1Z * this._b1._velZ + (j.ang1X * this._b1._angVelX + j.ang1Y * this._b1._angVelY + j.ang1Z * this._b1._angVelZ) - (j.lin2X * this._b2._velX + j.lin2Y * this._b2._velY + j.lin2Z * this._b2._velZ + (j.ang2X * this._b2._angVelX + j.ang2Y * this._b2._angVelY + j.ang2Z * this._b2._angVelZ)); + if(rvn < -oimo.common.Setting.contactEnableBounceThreshold && !p._warmStarted) { + row.rhs = -rvn * restitution; + } else { + row.rhs = 0; + } + if(this._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE) { + if(p._depth > oimo.common.Setting.linearSlop) { + let minRhs = (p._depth - oimo.common.Setting.linearSlop) * oimo.common.Setting.velocityBaumgarte * timeStep.invDt; + if(row.rhs < minRhs) { + row.rhs = minRhs; + } + } + } + if(!p._warmStarted) { + let _this = p._impulse; + _this.impulseN = 0; + _this.impulseT = 0; + _this.impulseB = 0; + _this.impulseP = 0; + _this.impulseLX = 0; + _this.impulseLY = 0; + _this.impulseLZ = 0; + } + row.impulse = p._impulse; + } + } + _getPositionSolverInfo(info) { + info.b1 = this._b1; + info.b2 = this._b2; + let normalX; + let normalY; + let normalZ; + normalX = this._manifold._normalX; + normalY = this._manifold._normalY; + normalZ = this._manifold._normalZ; + let num = this._manifold._numPoints; + info.numRows = 0; + let _g = 0; + while(_g < num) { + let p = this._manifold._points[_g++]; + if(p._disabled) { + continue; + } + let row = info.rows[info.numRows++]; + let j = row.jacobianN; + j.lin1X = normalX; + j.lin1Y = normalY; + j.lin1Z = normalZ; + j.lin2X = normalX; + j.lin2Y = normalY; + j.lin2Z = normalZ; + j.ang1X = p._relPos1Y * normalZ - p._relPos1Z * normalY; + j.ang1Y = p._relPos1Z * normalX - p._relPos1X * normalZ; + j.ang1Z = p._relPos1X * normalY - p._relPos1Y * normalX; + j.ang2X = p._relPos2Y * normalZ - p._relPos2Z * normalY; + j.ang2Y = p._relPos2Z * normalX - p._relPos2X * normalZ; + j.ang2Z = p._relPos2X * normalY - p._relPos2Y * normalX; + row.rhs = p._depth - oimo.common.Setting.linearSlop; + if(row.rhs < 0) { + row.rhs = 0; + } + row.impulse = p._impulse; + } + } + _syncManifold() { + this._manifold._updateDepthsAndPositions(this._tf1,this._tf2); + } + getShape1() { + return this._s1; + } + getShape2() { + return this._s2; + } + getManifold() { + return this._manifold; + } + isTouching() { + let _g = 0; + let _g1 = this._manifold._numPoints; + while(_g < _g1) if(this._manifold._points[_g++]._depth >= 0) { + return true; + } + return false; + } +} +oimo.dynamics.constraint.contact.ContactImpulse = class oimo_dynamics_constraint_contact_ContactImpulse { + constructor() { + this.impulseN = 0; + this.impulseT = 0; + this.impulseB = 0; + this.impulseP = 0; + this.impulseLX = 0; + this.impulseLY = 0; + this.impulseLZ = 0; + } + copyFrom(imp) { + this.impulseN = imp.impulseN; + this.impulseT = imp.impulseT; + this.impulseB = imp.impulseB; + this.impulseLX = imp.impulseLX; + this.impulseLY = imp.impulseLY; + this.impulseLZ = imp.impulseLZ; + } +} +oimo.dynamics.constraint.contact.Manifold = class oimo_dynamics_constraint_contact_Manifold { + constructor() { + this._normalX = 0; + this._normalY = 0; + this._normalZ = 0; + this._tangentX = 0; + this._tangentY = 0; + this._tangentZ = 0; + this._binormalX = 0; + this._binormalY = 0; + this._binormalZ = 0; + this._numPoints = 0; + this._points = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = oimo.common.Setting.maxManifoldPoints; + while(_g < _g1) this._points[_g++] = new oimo.dynamics.constraint.contact.ManifoldPoint(); + } + _clear() { + let _g = 0; + let _g1 = this._numPoints; + while(_g < _g1) { + let _this = this._points[_g++]; + _this._localPos1X = 0; + _this._localPos1Y = 0; + _this._localPos1Z = 0; + _this._localPos2X = 0; + _this._localPos2Y = 0; + _this._localPos2Z = 0; + _this._relPos1X = 0; + _this._relPos1Y = 0; + _this._relPos1Z = 0; + _this._relPos2X = 0; + _this._relPos2Y = 0; + _this._relPos2Z = 0; + _this._pos1X = 0; + _this._pos1Y = 0; + _this._pos1Z = 0; + _this._pos2X = 0; + _this._pos2Y = 0; + _this._pos2Z = 0; + _this._depth = 0; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._warmStarted = false; + _this._disabled = false; + _this._id = -1; + } + this._numPoints = 0; + } + _buildBasis(normal) { + this._normalX = normal.x; + this._normalY = normal.y; + this._normalZ = normal.z; + let nx = normal.x; + let ny = normal.y; + let nz = normal.z; + let nx2 = nx * nx; + let ny2 = ny * ny; + let nz2 = nz * nz; + let tx; + let ty; + let tz; + let bx; + let by; + let bz; + if(nx2 < ny2) { + if(nx2 < nz2) { + let invL = 1 / Math.sqrt(ny2 + nz2); + tx = 0; + ty = -nz * invL; + tz = ny * invL; + bx = ny * tz - nz * ty; + by = -nx * tz; + bz = nx * ty; + } else { + let invL = 1 / Math.sqrt(nx2 + ny2); + tx = -ny * invL; + ty = nx * invL; + tz = 0; + bx = -nz * ty; + by = nz * tx; + bz = nx * ty - ny * tx; + } + } else if(ny2 < nz2) { + let invL = 1 / Math.sqrt(nx2 + nz2); + tx = nz * invL; + ty = 0; + tz = -nx * invL; + bx = ny * tz; + by = nz * tx - nx * tz; + bz = -ny * tx; + } else { + let invL = 1 / Math.sqrt(nx2 + ny2); + tx = -ny * invL; + ty = nx * invL; + tz = 0; + bx = -nz * ty; + by = nz * tx; + bz = nx * ty - ny * tx; + } + this._tangentX = tx; + this._tangentY = ty; + this._tangentZ = tz; + this._binormalX = bx; + this._binormalY = by; + this._binormalZ = bz; + } + _updateDepthsAndPositions(tf1,tf2) { + let _g = 0; + let _g1 = this._numPoints; + while(_g < _g1) { + let p = this._points[_g++]; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * p._localPos1X + tf1._rotation01 * p._localPos1Y + tf1._rotation02 * p._localPos1Z; + __tmp__Y = tf1._rotation10 * p._localPos1X + tf1._rotation11 * p._localPos1Y + tf1._rotation12 * p._localPos1Z; + __tmp__Z = tf1._rotation20 * p._localPos1X + tf1._rotation21 * p._localPos1Y + tf1._rotation22 * p._localPos1Z; + p._relPos1X = __tmp__X; + p._relPos1Y = __tmp__Y; + p._relPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * p._localPos2X + tf2._rotation01 * p._localPos2Y + tf2._rotation02 * p._localPos2Z; + __tmp__Y1 = tf2._rotation10 * p._localPos2X + tf2._rotation11 * p._localPos2Y + tf2._rotation12 * p._localPos2Z; + __tmp__Z1 = tf2._rotation20 * p._localPos2X + tf2._rotation21 * p._localPos2Y + tf2._rotation22 * p._localPos2Z; + p._relPos2X = __tmp__X1; + p._relPos2Y = __tmp__Y1; + p._relPos2Z = __tmp__Z1; + p._pos1X = p._relPos1X + tf1._positionX; + p._pos1Y = p._relPos1Y + tf1._positionY; + p._pos1Z = p._relPos1Z + tf1._positionZ; + p._pos2X = p._relPos2X + tf2._positionX; + p._pos2Y = p._relPos2Y + tf2._positionY; + p._pos2Z = p._relPos2Z + tf2._positionZ; + let diffX; + let diffY; + let diffZ; + diffX = p._pos1X - p._pos2X; + diffY = p._pos1Y - p._pos2Y; + diffZ = p._pos1Z - p._pos2Z; + p._depth = -(diffX * this._normalX + diffY * this._normalY + diffZ * this._normalZ); + } + } + getNormal() { + let v = new oimo.common.Vec3(); + v.x = this._normalX; + v.y = this._normalY; + v.z = this._normalZ; + return v; + } + getNormalTo(normal) { + normal.x = this._normalX; + normal.y = this._normalY; + normal.z = this._normalZ; + } + getTangent() { + let v = new oimo.common.Vec3(); + v.x = this._tangentX; + v.y = this._tangentY; + v.z = this._tangentZ; + return v; + } + getTangentTo(tangent) { + tangent.x = this._tangentX; + tangent.y = this._tangentY; + tangent.z = this._tangentZ; + } + getBinormal() { + let v = new oimo.common.Vec3(); + v.x = this._binormalX; + v.y = this._binormalY; + v.z = this._binormalZ; + return v; + } + getBinormalTo(binormal) { + binormal.x = this._binormalX; + binormal.y = this._binormalY; + binormal.z = this._binormalZ; + } + getPoints() { + return this._points; + } + getNumPoints() { + return this._numPoints; + } +} +oimo.dynamics.constraint.contact.ManifoldPoint = class oimo_dynamics_constraint_contact_ManifoldPoint { + constructor() { + this._localPos1X = 0; + this._localPos1Y = 0; + this._localPos1Z = 0; + this._localPos2X = 0; + this._localPos2Y = 0; + this._localPos2Z = 0; + this._relPos1X = 0; + this._relPos1Y = 0; + this._relPos1Z = 0; + this._relPos2X = 0; + this._relPos2Y = 0; + this._relPos2Z = 0; + this._pos1X = 0; + this._pos1Y = 0; + this._pos1Z = 0; + this._pos2X = 0; + this._pos2Y = 0; + this._pos2Z = 0; + this._depth = 0; + this._impulse = new oimo.dynamics.constraint.contact.ContactImpulse(); + this._warmStarted = false; + this._disabled = false; + this._id = -1; + } + getPosition1() { + let v = new oimo.common.Vec3(); + v.x = this._pos1X; + v.y = this._pos1Y; + v.z = this._pos1Z; + return v; + } + getPosition1To(position) { + position.x = this._pos1X; + position.y = this._pos1Y; + position.z = this._pos1Z; + } + getPosition2() { + let v = new oimo.common.Vec3(); + v.x = this._pos2X; + v.y = this._pos2Y; + v.z = this._pos2Z; + return v; + } + getPosition2To(position) { + position.x = this._pos2X; + position.y = this._pos2Y; + position.z = this._pos2Z; + } + getDepth() { + return this._depth; + } + isWarmStarted() { + return this._warmStarted; + } + getNormalImpulse() { + return this._impulse.impulseN; + } + getTangentImpulse() { + return this._impulse.impulseT; + } + getBinormalImpulse() { + return this._impulse.impulseB; + } + isEnabled() { + return !this._disabled; + } +} +oimo.dynamics.constraint.contact.ManifoldUpdater = class oimo_dynamics_constraint_contact_ManifoldUpdater { + constructor(manifold) { + this._manifold = manifold; + this.numOldPoints = 0; + this.oldPoints = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = oimo.common.Setting.maxManifoldPoints; + while(_g < _g1) this.oldPoints[_g++] = new oimo.dynamics.constraint.contact.ManifoldPoint(); + } + removeOutdatedPoints() { + let index = this._manifold._numPoints; + while(--index >= 0) { + let p = this._manifold._points[index]; + let diffX; + let diffY; + let diffZ; + diffX = p._pos1X - p._pos2X; + diffY = p._pos1Y - p._pos2Y; + diffZ = p._pos1Z - p._pos2Z; + let dotN = this._manifold._normalX * diffX + this._manifold._normalY * diffY + this._manifold._normalZ * diffZ; + if(dotN > oimo.common.Setting.contactPersistenceThreshold) { + this.removeManifoldPoint(index); + continue; + } + diffX += this._manifold._normalX * -dotN; + diffY += this._manifold._normalY * -dotN; + diffZ += this._manifold._normalZ * -dotN; + if(diffX * diffX + diffY * diffY + diffZ * diffZ > oimo.common.Setting.contactPersistenceThreshold * oimo.common.Setting.contactPersistenceThreshold) { + this.removeManifoldPoint(index); + continue; + } + } + } + removeManifoldPoint(index) { + let lastIndex = --this._manifold._numPoints; + if(index != lastIndex) { + let tmp = this._manifold._points[index]; + this._manifold._points[index] = this._manifold._points[lastIndex]; + this._manifold._points[lastIndex] = tmp; + } + let _this = this._manifold._points[lastIndex]; + _this._localPos1X = 0; + _this._localPos1Y = 0; + _this._localPos1Z = 0; + _this._localPos2X = 0; + _this._localPos2Y = 0; + _this._localPos2Z = 0; + _this._relPos1X = 0; + _this._relPos1Y = 0; + _this._relPos1Z = 0; + _this._relPos2X = 0; + _this._relPos2Y = 0; + _this._relPos2Z = 0; + _this._pos1X = 0; + _this._pos1Y = 0; + _this._pos1Z = 0; + _this._pos2X = 0; + _this._pos2Y = 0; + _this._pos2Z = 0; + _this._depth = 0; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._warmStarted = false; + _this._disabled = false; + _this._id = -1; + } + addManifoldPoint(point,tf1,tf2) { + let num = this._manifold._numPoints; + if(num == oimo.common.Setting.maxManifoldPoints) { + let targetIndex = this.computeTargetIndex(point,tf1,tf2); + let _this = this._manifold._points[targetIndex]; + let v = point.position1; + _this._pos1X = v.x; + _this._pos1Y = v.y; + _this._pos1Z = v.z; + let v1 = point.position2; + _this._pos2X = v1.x; + _this._pos2Y = v1.y; + _this._pos2Z = v1.z; + _this._relPos1X = _this._pos1X - tf1._positionX; + _this._relPos1Y = _this._pos1Y - tf1._positionY; + _this._relPos1Z = _this._pos1Z - tf1._positionZ; + _this._relPos2X = _this._pos2X - tf2._positionX; + _this._relPos2Y = _this._pos2Y - tf2._positionY; + _this._relPos2Z = _this._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * _this._relPos1X + tf1._rotation10 * _this._relPos1Y + tf1._rotation20 * _this._relPos1Z; + __tmp__Y = tf1._rotation01 * _this._relPos1X + tf1._rotation11 * _this._relPos1Y + tf1._rotation21 * _this._relPos1Z; + __tmp__Z = tf1._rotation02 * _this._relPos1X + tf1._rotation12 * _this._relPos1Y + tf1._rotation22 * _this._relPos1Z; + _this._localPos1X = __tmp__X; + _this._localPos1Y = __tmp__Y; + _this._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * _this._relPos2X + tf2._rotation10 * _this._relPos2Y + tf2._rotation20 * _this._relPos2Z; + __tmp__Y1 = tf2._rotation01 * _this._relPos2X + tf2._rotation11 * _this._relPos2Y + tf2._rotation21 * _this._relPos2Z; + __tmp__Z1 = tf2._rotation02 * _this._relPos2X + tf2._rotation12 * _this._relPos2Y + tf2._rotation22 * _this._relPos2Z; + _this._localPos2X = __tmp__X1; + _this._localPos2Y = __tmp__Y1; + _this._localPos2Z = __tmp__Z1; + _this._depth = point.depth; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._id = point.id; + _this._warmStarted = false; + _this._disabled = false; + return; + } + let _this = this._manifold._points[num]; + let v = point.position1; + _this._pos1X = v.x; + _this._pos1Y = v.y; + _this._pos1Z = v.z; + let v1 = point.position2; + _this._pos2X = v1.x; + _this._pos2Y = v1.y; + _this._pos2Z = v1.z; + _this._relPos1X = _this._pos1X - tf1._positionX; + _this._relPos1Y = _this._pos1Y - tf1._positionY; + _this._relPos1Z = _this._pos1Z - tf1._positionZ; + _this._relPos2X = _this._pos2X - tf2._positionX; + _this._relPos2Y = _this._pos2Y - tf2._positionY; + _this._relPos2Z = _this._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * _this._relPos1X + tf1._rotation10 * _this._relPos1Y + tf1._rotation20 * _this._relPos1Z; + __tmp__Y = tf1._rotation01 * _this._relPos1X + tf1._rotation11 * _this._relPos1Y + tf1._rotation21 * _this._relPos1Z; + __tmp__Z = tf1._rotation02 * _this._relPos1X + tf1._rotation12 * _this._relPos1Y + tf1._rotation22 * _this._relPos1Z; + _this._localPos1X = __tmp__X; + _this._localPos1Y = __tmp__Y; + _this._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * _this._relPos2X + tf2._rotation10 * _this._relPos2Y + tf2._rotation20 * _this._relPos2Z; + __tmp__Y1 = tf2._rotation01 * _this._relPos2X + tf2._rotation11 * _this._relPos2Y + tf2._rotation21 * _this._relPos2Z; + __tmp__Z1 = tf2._rotation02 * _this._relPos2X + tf2._rotation12 * _this._relPos2Y + tf2._rotation22 * _this._relPos2Z; + _this._localPos2X = __tmp__X1; + _this._localPos2Y = __tmp__Y1; + _this._localPos2Z = __tmp__Z1; + _this._depth = point.depth; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._id = point.id; + _this._warmStarted = false; + _this._disabled = false; + this._manifold._numPoints++; + } + computeTargetIndex(newPoint,tf1,tf2) { + let p1 = this._manifold._points[0]; + let p2 = this._manifold._points[1]; + let p3 = this._manifold._points[2]; + let p4 = this._manifold._points[3]; + let maxDepth = p1._depth; + let maxDepthIndex = 0; + if(p2._depth > maxDepth) { + maxDepth = p2._depth; + maxDepthIndex = 1; + } + if(p3._depth > maxDepth) { + maxDepth = p3._depth; + maxDepthIndex = 2; + } + if(p4._depth > maxDepth) { + + maxDepthIndex = 3; + } + let rp1X; + let rp1Y; + let rp1Z; + let v = newPoint.position1; + rp1X = v.x; + rp1Y = v.y; + rp1Z = v.z; + rp1X -= tf1._positionX; + rp1Y -= tf1._positionY; + rp1Z -= tf1._positionZ; + let p1X = p2._relPos1X; + let p1Y = p2._relPos1Y; + let p1Z = p2._relPos1Z; + let p2X = p3._relPos1X; + let p2Y = p3._relPos1Y; + let p2Z = p3._relPos1Z; + let p3X = p4._relPos1X; + let p3Y = p4._relPos1Y; + let p3Z = p4._relPos1Z; + let v12X; + let v12Y; + let v12Z; + let v34X; + let v34Y; + let v34Z; + let v13X; + let v13Y; + let v13Z; + let v24X; + let v24Y; + let v24Z; + let v14X; + let v14Y; + let v14Z; + let v23X; + let v23Y; + let v23Z; + v12X = p2X - p1X; + v12Y = p2Y - p1Y; + v12Z = p2Z - p1Z; + v34X = rp1X - p3X; + v34Y = rp1Y - p3Y; + v34Z = rp1Z - p3Z; + v13X = p3X - p1X; + v13Y = p3Y - p1Y; + v13Z = p3Z - p1Z; + v24X = rp1X - p2X; + v24Y = rp1Y - p2Y; + v24Z = rp1Z - p2Z; + v14X = rp1X - p1X; + v14Y = rp1Y - p1Y; + v14Z = rp1Z - p1Z; + v23X = p3X - p2X; + v23Y = p3Y - p2Y; + v23Z = p3Z - p2Z; + let cross1X; + let cross1Y; + let cross1Z; + let cross2X; + let cross2Y; + let cross2Z; + let cross3X; + let cross3Y; + let cross3Z; + cross1X = v12Y * v34Z - v12Z * v34Y; + cross1Y = v12Z * v34X - v12X * v34Z; + cross1Z = v12X * v34Y - v12Y * v34X; + cross2X = v13Y * v24Z - v13Z * v24Y; + cross2Y = v13Z * v24X - v13X * v24Z; + cross2Z = v13X * v24Y - v13Y * v24X; + cross3X = v14Y * v23Z - v14Z * v23Y; + cross3Y = v14Z * v23X - v14X * v23Z; + cross3Z = v14X * v23Y - v14Y * v23X; + let a1 = cross1X * cross1X + cross1Y * cross1Y + cross1Z * cross1Z; + let a2 = cross2X * cross2X + cross2Y * cross2Y + cross2Z * cross2Z; + let a3 = cross3X * cross3X + cross3Y * cross3Y + cross3Z * cross3Z; + let p1X1 = p1._relPos1X; + let p1Y1 = p1._relPos1Y; + let p1Z1 = p1._relPos1Z; + let p2X1 = p3._relPos1X; + let p2Y1 = p3._relPos1Y; + let p2Z1 = p3._relPos1Z; + let p3X1 = p4._relPos1X; + let p3Y1 = p4._relPos1Y; + let p3Z1 = p4._relPos1Z; + let v12X1; + let v12Y1; + let v12Z1; + let v34X1; + let v34Y1; + let v34Z1; + let v13X1; + let v13Y1; + let v13Z1; + let v24X1; + let v24Y1; + let v24Z1; + let v14X1; + let v14Y1; + let v14Z1; + let v23X1; + let v23Y1; + let v23Z1; + v12X1 = p2X1 - p1X1; + v12Y1 = p2Y1 - p1Y1; + v12Z1 = p2Z1 - p1Z1; + v34X1 = rp1X - p3X1; + v34Y1 = rp1Y - p3Y1; + v34Z1 = rp1Z - p3Z1; + v13X1 = p3X1 - p1X1; + v13Y1 = p3Y1 - p1Y1; + v13Z1 = p3Z1 - p1Z1; + v24X1 = rp1X - p2X1; + v24Y1 = rp1Y - p2Y1; + v24Z1 = rp1Z - p2Z1; + v14X1 = rp1X - p1X1; + v14Y1 = rp1Y - p1Y1; + v14Z1 = rp1Z - p1Z1; + v23X1 = p3X1 - p2X1; + v23Y1 = p3Y1 - p2Y1; + v23Z1 = p3Z1 - p2Z1; + let cross1X1; + let cross1Y1; + let cross1Z1; + let cross2X1; + let cross2Y1; + let cross2Z1; + let cross3X1; + let cross3Y1; + let cross3Z1; + cross1X1 = v12Y1 * v34Z1 - v12Z1 * v34Y1; + cross1Y1 = v12Z1 * v34X1 - v12X1 * v34Z1; + cross1Z1 = v12X1 * v34Y1 - v12Y1 * v34X1; + cross2X1 = v13Y1 * v24Z1 - v13Z1 * v24Y1; + cross2Y1 = v13Z1 * v24X1 - v13X1 * v24Z1; + cross2Z1 = v13X1 * v24Y1 - v13Y1 * v24X1; + cross3X1 = v14Y1 * v23Z1 - v14Z1 * v23Y1; + cross3Y1 = v14Z1 * v23X1 - v14X1 * v23Z1; + cross3Z1 = v14X1 * v23Y1 - v14Y1 * v23X1; + let a11 = cross1X1 * cross1X1 + cross1Y1 * cross1Y1 + cross1Z1 * cross1Z1; + let a21 = cross2X1 * cross2X1 + cross2Y1 * cross2Y1 + cross2Z1 * cross2Z1; + let a31 = cross3X1 * cross3X1 + cross3Y1 * cross3Y1 + cross3Z1 * cross3Z1; + let a22 = a11 > a21 ? a11 > a31 ? a11 : a31 : a21 > a31 ? a21 : a31; + let p1X2 = p1._relPos1X; + let p1Y2 = p1._relPos1Y; + let p1Z2 = p1._relPos1Z; + let p2X2 = p2._relPos1X; + let p2Y2 = p2._relPos1Y; + let p2Z2 = p2._relPos1Z; + let p3X2 = p4._relPos1X; + let p3Y2 = p4._relPos1Y; + let p3Z2 = p4._relPos1Z; + let v12X2; + let v12Y2; + let v12Z2; + let v34X2; + let v34Y2; + let v34Z2; + let v13X2; + let v13Y2; + let v13Z2; + let v24X2; + let v24Y2; + let v24Z2; + let v14X2; + let v14Y2; + let v14Z2; + let v23X2; + let v23Y2; + let v23Z2; + v12X2 = p2X2 - p1X2; + v12Y2 = p2Y2 - p1Y2; + v12Z2 = p2Z2 - p1Z2; + v34X2 = rp1X - p3X2; + v34Y2 = rp1Y - p3Y2; + v34Z2 = rp1Z - p3Z2; + v13X2 = p3X2 - p1X2; + v13Y2 = p3Y2 - p1Y2; + v13Z2 = p3Z2 - p1Z2; + v24X2 = rp1X - p2X2; + v24Y2 = rp1Y - p2Y2; + v24Z2 = rp1Z - p2Z2; + v14X2 = rp1X - p1X2; + v14Y2 = rp1Y - p1Y2; + v14Z2 = rp1Z - p1Z2; + v23X2 = p3X2 - p2X2; + v23Y2 = p3Y2 - p2Y2; + v23Z2 = p3Z2 - p2Z2; + let cross1X2; + let cross1Y2; + let cross1Z2; + let cross2X2; + let cross2Y2; + let cross2Z2; + let cross3X2; + let cross3Y2; + let cross3Z2; + cross1X2 = v12Y2 * v34Z2 - v12Z2 * v34Y2; + cross1Y2 = v12Z2 * v34X2 - v12X2 * v34Z2; + cross1Z2 = v12X2 * v34Y2 - v12Y2 * v34X2; + cross2X2 = v13Y2 * v24Z2 - v13Z2 * v24Y2; + cross2Y2 = v13Z2 * v24X2 - v13X2 * v24Z2; + cross2Z2 = v13X2 * v24Y2 - v13Y2 * v24X2; + cross3X2 = v14Y2 * v23Z2 - v14Z2 * v23Y2; + cross3Y2 = v14Z2 * v23X2 - v14X2 * v23Z2; + cross3Z2 = v14X2 * v23Y2 - v14Y2 * v23X2; + let a12 = cross1X2 * cross1X2 + cross1Y2 * cross1Y2 + cross1Z2 * cross1Z2; + let a23 = cross2X2 * cross2X2 + cross2Y2 * cross2Y2 + cross2Z2 * cross2Z2; + let a32 = cross3X2 * cross3X2 + cross3Y2 * cross3Y2 + cross3Z2 * cross3Z2; + let a33 = a12 > a23 ? a12 > a32 ? a12 : a32 : a23 > a32 ? a23 : a32; + let p1X3 = p1._relPos1X; + let p1Y3 = p1._relPos1Y; + let p1Z3 = p1._relPos1Z; + let p2X3 = p2._relPos1X; + let p2Y3 = p2._relPos1Y; + let p2Z3 = p2._relPos1Z; + let p3X3 = p3._relPos1X; + let p3Y3 = p3._relPos1Y; + let p3Z3 = p3._relPos1Z; + let v12X3; + let v12Y3; + let v12Z3; + let v34X3; + let v34Y3; + let v34Z3; + let v13X3; + let v13Y3; + let v13Z3; + let v24X3; + let v24Y3; + let v24Z3; + let v14X3; + let v14Y3; + let v14Z3; + let v23X3; + let v23Y3; + let v23Z3; + v12X3 = p2X3 - p1X3; + v12Y3 = p2Y3 - p1Y3; + v12Z3 = p2Z3 - p1Z3; + v34X3 = rp1X - p3X3; + v34Y3 = rp1Y - p3Y3; + v34Z3 = rp1Z - p3Z3; + v13X3 = p3X3 - p1X3; + v13Y3 = p3Y3 - p1Y3; + v13Z3 = p3Z3 - p1Z3; + v24X3 = rp1X - p2X3; + v24Y3 = rp1Y - p2Y3; + v24Z3 = rp1Z - p2Z3; + v14X3 = rp1X - p1X3; + v14Y3 = rp1Y - p1Y3; + v14Z3 = rp1Z - p1Z3; + v23X3 = p3X3 - p2X3; + v23Y3 = p3Y3 - p2Y3; + v23Z3 = p3Z3 - p2Z3; + let cross1X3; + let cross1Y3; + let cross1Z3; + let cross2X3; + let cross2Y3; + let cross2Z3; + let cross3X3; + let cross3Y3; + let cross3Z3; + cross1X3 = v12Y3 * v34Z3 - v12Z3 * v34Y3; + cross1Y3 = v12Z3 * v34X3 - v12X3 * v34Z3; + cross1Z3 = v12X3 * v34Y3 - v12Y3 * v34X3; + cross2X3 = v13Y3 * v24Z3 - v13Z3 * v24Y3; + cross2Y3 = v13Z3 * v24X3 - v13X3 * v24Z3; + cross2Z3 = v13X3 * v24Y3 - v13Y3 * v24X3; + cross3X3 = v14Y3 * v23Z3 - v14Z3 * v23Y3; + cross3Y3 = v14Z3 * v23X3 - v14X3 * v23Z3; + cross3Z3 = v14X3 * v23Y3 - v14Y3 * v23X3; + let a13 = cross1X3 * cross1X3 + cross1Y3 * cross1Y3 + cross1Z3 * cross1Z3; + let a24 = cross2X3 * cross2X3 + cross2Y3 * cross2Y3 + cross2Z3 * cross2Z3; + let a34 = cross3X3 * cross3X3 + cross3Y3 * cross3Y3 + cross3Z3 * cross3Z3; + let a4 = a13 > a24 ? a13 > a34 ? a13 : a34 : a24 > a34 ? a24 : a34; + let max = a1 > a2 ? a1 > a3 ? a1 : a3 : a2 > a3 ? a2 : a3; + let target = 0; + if(a22 > max && maxDepthIndex != 1 || maxDepthIndex == 0) { + max = a22; + target = 1; + } + if(a33 > max && maxDepthIndex != 2) { + max = a33; + target = 2; + } + if(a4 > max && maxDepthIndex != 3) { + + target = 3; + } + return target; + } + computeRelativePositions(tf1,tf2) { + let num = this._manifold._numPoints; + let _g = 0; + while(_g < num) { + let p = this._manifold._points[_g++]; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * p._localPos1X + tf1._rotation01 * p._localPos1Y + tf1._rotation02 * p._localPos1Z; + __tmp__Y = tf1._rotation10 * p._localPos1X + tf1._rotation11 * p._localPos1Y + tf1._rotation12 * p._localPos1Z; + __tmp__Z = tf1._rotation20 * p._localPos1X + tf1._rotation21 * p._localPos1Y + tf1._rotation22 * p._localPos1Z; + p._relPos1X = __tmp__X; + p._relPos1Y = __tmp__Y; + p._relPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * p._localPos2X + tf2._rotation01 * p._localPos2Y + tf2._rotation02 * p._localPos2Z; + __tmp__Y1 = tf2._rotation10 * p._localPos2X + tf2._rotation11 * p._localPos2Y + tf2._rotation12 * p._localPos2Z; + __tmp__Z1 = tf2._rotation20 * p._localPos2X + tf2._rotation21 * p._localPos2Y + tf2._rotation22 * p._localPos2Z; + p._relPos2X = __tmp__X1; + p._relPos2Y = __tmp__Y1; + p._relPos2Z = __tmp__Z1; + p._warmStarted = true; + } + } + findNearestContactPointIndex(target,tf1,tf2) { + let nearestSq = oimo.common.Setting.contactPersistenceThreshold * oimo.common.Setting.contactPersistenceThreshold; + let idx = -1; + let _g = 0; + let _g1 = this._manifold._numPoints; + while(_g < _g1) { + let i = _g++; + let mp = this._manifold._points[i]; + let rp1X; + let rp1Y; + let rp1Z; + let rp2X; + let rp2Y; + let rp2Z; + let v = target.position1; + rp1X = v.x; + rp1Y = v.y; + rp1Z = v.z; + let v1 = target.position2; + rp2X = v1.x; + rp2Y = v1.y; + rp2Z = v1.z; + rp1X -= tf1._positionX; + rp1Y -= tf1._positionY; + rp1Z -= tf1._positionZ; + rp2X -= tf2._positionX; + rp2Y -= tf2._positionY; + rp2Z -= tf2._positionZ; + let diff1X; + let diff1Y; + let diff1Z; + let diff2X; + let diff2Y; + let diff2Z; + diff1X = mp._relPos1X - rp1X; + diff1Y = mp._relPos1Y - rp1Y; + diff1Z = mp._relPos1Z - rp1Z; + diff2X = mp._relPos2X - rp2X; + diff2Y = mp._relPos2Y - rp2Y; + diff2Z = mp._relPos2Z - rp2Z; + let sq1 = diff1X * diff1X + diff1Y * diff1Y + diff1Z * diff1Z; + let sq2 = diff2X * diff2X + diff2Y * diff2Y + diff2Z * diff2Z; + let d = sq1 < sq2 ? sq1 : sq2; + if(d < nearestSq) { + nearestSq = d; + idx = i; + } + } + return idx; + } + totalUpdate(result,tf1,tf2) { + this.numOldPoints = this._manifold._numPoints; + let _g = 0; + let _g1 = this.numOldPoints; + while(_g < _g1) { + let i = _g++; + let _this = this.oldPoints[i]; + let cp = this._manifold._points[i]; + _this._localPos1X = cp._localPos1X; + _this._localPos1Y = cp._localPos1Y; + _this._localPos1Z = cp._localPos1Z; + _this._localPos2X = cp._localPos2X; + _this._localPos2Y = cp._localPos2Y; + _this._localPos2Z = cp._localPos2Z; + _this._relPos1X = cp._relPos1X; + _this._relPos1Y = cp._relPos1Y; + _this._relPos1Z = cp._relPos1Z; + _this._relPos2X = cp._relPos2X; + _this._relPos2Y = cp._relPos2Y; + _this._relPos2Z = cp._relPos2Z; + _this._pos1X = cp._pos1X; + _this._pos1Y = cp._pos1Y; + _this._pos1Z = cp._pos1Z; + _this._pos2X = cp._pos2X; + _this._pos2Y = cp._pos2Y; + _this._pos2Z = cp._pos2Z; + _this._depth = cp._depth; + _this._impulse.copyFrom(cp._impulse); + _this._id = cp._id; + _this._warmStarted = cp._warmStarted; + _this._disabled = false; + } + let num = result.numPoints; + this._manifold._numPoints = num; + let _g2 = 0; + while(_g2 < num) { + let i = _g2++; + let p = this._manifold._points[i]; + let ref = result.points[i]; + let v = ref.position1; + p._pos1X = v.x; + p._pos1Y = v.y; + p._pos1Z = v.z; + let v1 = ref.position2; + p._pos2X = v1.x; + p._pos2Y = v1.y; + p._pos2Z = v1.z; + p._relPos1X = p._pos1X - tf1._positionX; + p._relPos1Y = p._pos1Y - tf1._positionY; + p._relPos1Z = p._pos1Z - tf1._positionZ; + p._relPos2X = p._pos2X - tf2._positionX; + p._relPos2Y = p._pos2Y - tf2._positionY; + p._relPos2Z = p._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * p._relPos1X + tf1._rotation10 * p._relPos1Y + tf1._rotation20 * p._relPos1Z; + __tmp__Y = tf1._rotation01 * p._relPos1X + tf1._rotation11 * p._relPos1Y + tf1._rotation21 * p._relPos1Z; + __tmp__Z = tf1._rotation02 * p._relPos1X + tf1._rotation12 * p._relPos1Y + tf1._rotation22 * p._relPos1Z; + p._localPos1X = __tmp__X; + p._localPos1Y = __tmp__Y; + p._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * p._relPos2X + tf2._rotation10 * p._relPos2Y + tf2._rotation20 * p._relPos2Z; + __tmp__Y1 = tf2._rotation01 * p._relPos2X + tf2._rotation11 * p._relPos2Y + tf2._rotation21 * p._relPos2Z; + __tmp__Z1 = tf2._rotation02 * p._relPos2X + tf2._rotation12 * p._relPos2Y + tf2._rotation22 * p._relPos2Z; + p._localPos2X = __tmp__X1; + p._localPos2Y = __tmp__Y1; + p._localPos2Z = __tmp__Z1; + p._depth = ref.depth; + let _this = p._impulse; + _this.impulseN = 0; + _this.impulseT = 0; + _this.impulseB = 0; + _this.impulseP = 0; + _this.impulseLX = 0; + _this.impulseLY = 0; + _this.impulseLZ = 0; + p._id = ref.id; + p._warmStarted = false; + p._disabled = false; + let _g = 0; + let _g1 = this.numOldPoints; + while(_g < _g1) { + let ocp = this.oldPoints[_g++]; + if(p._id == ocp._id) { + p._impulse.copyFrom(ocp._impulse); + p._warmStarted = true; + break; + } + } + } + } + incrementalUpdate(result,tf1,tf2) { + this._manifold._updateDepthsAndPositions(tf1,tf2); + let _g = 0; + let _g1 = this._manifold._numPoints; + while(_g < _g1) this._manifold._points[_g++]._warmStarted = true; + let newPoint = result.points[0]; + let index = this.findNearestContactPointIndex(newPoint,tf1,tf2); + if(index == -1) { + this.addManifoldPoint(newPoint,tf1,tf2); + } else { + let cp = this._manifold._points[index]; + let v = newPoint.position1; + cp._pos1X = v.x; + cp._pos1Y = v.y; + cp._pos1Z = v.z; + let v1 = newPoint.position2; + cp._pos2X = v1.x; + cp._pos2Y = v1.y; + cp._pos2Z = v1.z; + cp._relPos1X = cp._pos1X - tf1._positionX; + cp._relPos1Y = cp._pos1Y - tf1._positionY; + cp._relPos1Z = cp._pos1Z - tf1._positionZ; + cp._relPos2X = cp._pos2X - tf2._positionX; + cp._relPos2Y = cp._pos2Y - tf2._positionY; + cp._relPos2Z = cp._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * cp._relPos1X + tf1._rotation10 * cp._relPos1Y + tf1._rotation20 * cp._relPos1Z; + __tmp__Y = tf1._rotation01 * cp._relPos1X + tf1._rotation11 * cp._relPos1Y + tf1._rotation21 * cp._relPos1Z; + __tmp__Z = tf1._rotation02 * cp._relPos1X + tf1._rotation12 * cp._relPos1Y + tf1._rotation22 * cp._relPos1Z; + cp._localPos1X = __tmp__X; + cp._localPos1Y = __tmp__Y; + cp._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * cp._relPos2X + tf2._rotation10 * cp._relPos2Y + tf2._rotation20 * cp._relPos2Z; + __tmp__Y1 = tf2._rotation01 * cp._relPos2X + tf2._rotation11 * cp._relPos2Y + tf2._rotation21 * cp._relPos2Z; + __tmp__Z1 = tf2._rotation02 * cp._relPos2X + tf2._rotation12 * cp._relPos2Y + tf2._rotation22 * cp._relPos2Z; + cp._localPos2X = __tmp__X1; + cp._localPos2Y = __tmp__Y1; + cp._localPos2Z = __tmp__Z1; + cp._depth = newPoint.depth; + } + this.removeOutdatedPoints(); + } +} +if(!oimo.dynamics.constraint.info) oimo.dynamics.constraint.info = {}; +oimo.dynamics.constraint.info.JacobianRow = class oimo_dynamics_constraint_info_JacobianRow { + constructor() { + this.lin1X = 0; + this.lin1Y = 0; + this.lin1Z = 0; + this.lin2X = 0; + this.lin2Y = 0; + this.lin2Z = 0; + this.ang1X = 0; + this.ang1Y = 0; + this.ang1Z = 0; + this.ang2X = 0; + this.ang2Y = 0; + this.ang2Z = 0; + this.flag = 0; + } + updateSparsity() { + this.flag = 0; + if(!(this.lin1X == 0 && this.lin1Y == 0 && this.lin1Z == 0) || !(this.lin2X == 0 && this.lin2Y == 0 && this.lin2Z == 0)) { + this.flag |= 1; + } + if(!(this.ang1X == 0 && this.ang1Y == 0 && this.ang1Z == 0) || !(this.ang2X == 0 && this.ang2Y == 0 && this.ang2Z == 0)) { + this.flag |= 2; + } + } +} +if(!oimo.dynamics.constraint.info.contact) oimo.dynamics.constraint.info.contact = {}; +oimo.dynamics.constraint.info.contact.ContactSolverInfo = class oimo_dynamics_constraint_info_contact_ContactSolverInfo { + constructor() { + this.b1 = null; + this.b2 = null; + this.numRows = 0; + this.rows = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = this.rows.length; + while(_g < _g1) this.rows[_g++] = new oimo.dynamics.constraint.info.contact.ContactSolverInfoRow(); + } +} +oimo.dynamics.constraint.info.contact.ContactSolverInfoRow = class oimo_dynamics_constraint_info_contact_ContactSolverInfoRow { + constructor() { + this.jacobianN = new oimo.dynamics.constraint.info.JacobianRow(); + this.jacobianT = new oimo.dynamics.constraint.info.JacobianRow(); + this.jacobianB = new oimo.dynamics.constraint.info.JacobianRow(); + this.rhs = 0; + this.cfm = 0; + this.friction = 0; + this.impulse = null; + } +} +if(!oimo.dynamics.constraint.info.joint) oimo.dynamics.constraint.info.joint = {}; +oimo.dynamics.constraint.info.joint.JointSolverInfo = class oimo_dynamics_constraint_info_joint_JointSolverInfo { + constructor() { + this.b1 = null; + this.b2 = null; + this.numRows = 0; + this.rows = new Array(oimo.common.Setting.maxJacobianRows); + let _g = 0; + let _g1 = this.rows.length; + while(_g < _g1) this.rows[_g++] = new oimo.dynamics.constraint.info.joint.JointSolverInfoRow(); + } +} +oimo.dynamics.constraint.info.joint.JointSolverInfoRow = class oimo_dynamics_constraint_info_joint_JointSolverInfoRow { + constructor() { + this.jacobian = new oimo.dynamics.constraint.info.JacobianRow(); + this.rhs = 0; + this.cfm = 0; + this.minImpulse = 0; + this.maxImpulse = 0; + this.motorSpeed = 0; + this.motorMaxImpulse = 0; + this.impulse = null; + } +} +if(!oimo.dynamics.constraint.joint) oimo.dynamics.constraint.joint = {}; +oimo.dynamics.constraint.joint.BasisTracker = class oimo_dynamics_constraint_joint_BasisTracker { + constructor(joint) { + this.joint = joint; + this.xX = 0; + this.xY = 0; + this.xZ = 0; + this.yX = 0; + this.yY = 0; + this.yZ = 0; + this.zX = 0; + this.zY = 0; + this.zZ = 0; + } +} +oimo.dynamics.constraint.joint.Joint = class oimo_dynamics_constraint_joint_Joint { + constructor(config,type) { + this._link1 = new oimo.dynamics.constraint.joint.JointLink(this); + this._link2 = new oimo.dynamics.constraint.joint.JointLink(this); + this._positionCorrectionAlgorithm = oimo.common.Setting.defaultJointPositionCorrectionAlgorithm; + this._type = type; + this._world = null; + this._b1 = config.rigidBody1; + this._b2 = config.rigidBody2; + this._allowCollision = config.allowCollision; + this._breakForce = config.breakForce; + this._breakTorque = config.breakTorque; + switch(config.solverType) { + case 0: + this._solver = new oimo.dynamics.constraint.solver.pgs.PgsJointConstraintSolver(this); + break; + case 1: + this._solver = new oimo.dynamics.constraint.solver.direct.DirectJointConstraintSolver(this); + break; + } + let v = config.localAnchor1; + this._localAnchor1X = v.x; + this._localAnchor1Y = v.y; + this._localAnchor1Z = v.z; + let v1 = config.localAnchor2; + this._localAnchor2X = v1.x; + this._localAnchor2Y = v1.y; + this._localAnchor2Z = v1.z; + this._relativeAnchor1X = 0; + this._relativeAnchor1Y = 0; + this._relativeAnchor1Z = 0; + this._relativeAnchor2X = 0; + this._relativeAnchor2Y = 0; + this._relativeAnchor2Z = 0; + this._anchor1X = 0; + this._anchor1Y = 0; + this._anchor1Z = 0; + this._anchor2X = 0; + this._anchor2Y = 0; + this._anchor2Z = 0; + this._localBasisX1X = 0; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + this._localBasisY1X = 0; + this._localBasisY1Y = 0; + this._localBasisY1Z = 0; + this._localBasisZ1X = 0; + this._localBasisZ1Y = 0; + this._localBasisZ1Z = 0; + this._localBasisX2X = 0; + this._localBasisX2Y = 0; + this._localBasisX2Z = 0; + this._localBasisY2X = 0; + this._localBasisY2Y = 0; + this._localBasisY2Z = 0; + this._localBasisZ2X = 0; + this._localBasisZ2Y = 0; + this._localBasisZ2Z = 0; + this._impulses = new Array(oimo.common.Setting.maxJacobianRows); + let _g = 0; + let _g1 = oimo.common.Setting.maxJacobianRows; + while(_g < _g1) this._impulses[_g++] = new oimo.dynamics.constraint.joint.JointImpulse(); + } + buildLocalBasesFromX() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + if(this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z == 0) { + this._localBasisX2X = 1; + this._localBasisX2Y = 0; + this._localBasisX2Z = 0; + } else { + let l = this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX2X *= l; + this._localBasisX2Y *= l; + this._localBasisX2Z *= l; + } + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let d = this._localBasisX1X * this._localBasisX2X + this._localBasisX1Y * this._localBasisX2Y + this._localBasisX1Z * this._localBasisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = this._localBasisX1Y * this._localBasisX2Z - this._localBasisX1Z * this._localBasisX2Y; + cY = this._localBasisX1Z * this._localBasisX2X - this._localBasisX1X * this._localBasisX2Z; + cZ = this._localBasisX1X * this._localBasisX2Y - this._localBasisX1Y * this._localBasisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x21 = x1 * x1; + let y21 = y1 * y1; + let z21 = z1 * z1; + let d1; + if(x21 < y21) { + if(x21 < z21) { + d1 = 1 / Math.sqrt(y21 + z21); + this._localBasisY1X = 0; + this._localBasisY1Y = z1 * d1; + this._localBasisY1Z = -y1 * d1; + } else { + d1 = 1 / Math.sqrt(x21 + y21); + this._localBasisY1X = y1 * d1; + this._localBasisY1Y = -x1 * d1; + this._localBasisY1Z = 0; + } + } else if(y21 < z21) { + d1 = 1 / Math.sqrt(z21 + x21); + this._localBasisY1X = -z1 * d1; + this._localBasisY1Y = 0; + this._localBasisY1Z = x1 * d1; + } else { + d1 = 1 / Math.sqrt(x21 + y21); + this._localBasisY1X = y1 * d1; + this._localBasisY1Y = -x1 * d1; + this._localBasisY1Z = 0; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * this._localBasisX1X + slerpM01 * this._localBasisX1Y + slerpM02 * this._localBasisX1Z; + __tmp__Y = slerpM10 * this._localBasisX1X + slerpM11 * this._localBasisX1Y + slerpM12 * this._localBasisX1Z; + __tmp__Z = slerpM20 * this._localBasisX1X + slerpM21 * this._localBasisX1Y + slerpM22 * this._localBasisX1Z; + this._localBasisX2X = __tmp__X; + this._localBasisX2Y = __tmp__Y; + this._localBasisX2Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * this._localBasisY1X + slerpM01 * this._localBasisY1Y + slerpM02 * this._localBasisY1Z; + __tmp__Y1 = slerpM10 * this._localBasisY1X + slerpM11 * this._localBasisY1Y + slerpM12 * this._localBasisY1Z; + __tmp__Z1 = slerpM20 * this._localBasisY1X + slerpM21 * this._localBasisY1Y + slerpM22 * this._localBasisY1Z; + this._localBasisY2X = __tmp__X1; + this._localBasisY2Y = __tmp__Y1; + this._localBasisY2Z = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = slerpM00 * this._localBasisZ1X + slerpM01 * this._localBasisZ1Y + slerpM02 * this._localBasisZ1Z; + __tmp__Y2 = slerpM10 * this._localBasisZ1X + slerpM11 * this._localBasisZ1Y + slerpM12 * this._localBasisZ1Z; + __tmp__Z2 = slerpM20 * this._localBasisZ1X + slerpM21 * this._localBasisZ1Y + slerpM22 * this._localBasisZ1Z; + this._localBasisZ2X = __tmp__X2; + this._localBasisZ2Y = __tmp__Y2; + this._localBasisZ2Z = __tmp__Z2; + } + buildLocalBasesFromXY() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + if(this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z == 0) { + this._localBasisX2X = 1; + this._localBasisX2Y = 0; + this._localBasisX2Z = 0; + } else { + let l = this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX2X *= l; + this._localBasisX2Y *= l; + this._localBasisX2Z *= l; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + this._localBasisZ2X = this._localBasisX2Y * this._localBasisY2Z - this._localBasisX2Z * this._localBasisY2Y; + this._localBasisZ2Y = this._localBasisX2Z * this._localBasisY2X - this._localBasisX2X * this._localBasisY2Z; + this._localBasisZ2Z = this._localBasisX2X * this._localBasisY2Y - this._localBasisX2Y * this._localBasisY2X; + if(this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z == 0) { + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this._localBasisY1X = 0; + this._localBasisY1Y = z1 * d; + this._localBasisY1Z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this._localBasisY1X = -z1 * d; + this._localBasisY1Y = 0; + this._localBasisY1Z = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + } else { + let l = this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ1X *= l; + this._localBasisZ1Y *= l; + this._localBasisZ1Z *= l; + this._localBasisY1X = this._localBasisZ1Y * this._localBasisX1Z - this._localBasisZ1Z * this._localBasisX1Y; + this._localBasisY1Y = this._localBasisZ1Z * this._localBasisX1X - this._localBasisZ1X * this._localBasisX1Z; + this._localBasisY1Z = this._localBasisZ1X * this._localBasisX1Y - this._localBasisZ1Y * this._localBasisX1X; + } + if(this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z == 0) { + let x1 = this._localBasisX2X; + let y1 = this._localBasisX2Y; + let z1 = this._localBasisX2Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this._localBasisY2X = 0; + this._localBasisY2Y = z1 * d; + this._localBasisY2Z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY2X = y1 * d; + this._localBasisY2Y = -x1 * d; + this._localBasisY2Z = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this._localBasisY2X = -z1 * d; + this._localBasisY2Y = 0; + this._localBasisY2Z = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY2X = y1 * d; + this._localBasisY2Y = -x1 * d; + this._localBasisY2Z = 0; + } + this._localBasisZ2X = this._localBasisX2Y * this._localBasisY2Z - this._localBasisX2Z * this._localBasisY2Y; + this._localBasisZ2Y = this._localBasisX2Z * this._localBasisY2X - this._localBasisX2X * this._localBasisY2Z; + this._localBasisZ2Z = this._localBasisX2X * this._localBasisY2Y - this._localBasisX2Y * this._localBasisY2X; + } else { + let l = this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ2X *= l; + this._localBasisZ2Y *= l; + this._localBasisZ2Z *= l; + this._localBasisY2X = this._localBasisZ2Y * this._localBasisX2Z - this._localBasisZ2Z * this._localBasisX2Y; + this._localBasisY2Y = this._localBasisZ2Z * this._localBasisX2X - this._localBasisZ2X * this._localBasisX2Z; + this._localBasisY2Z = this._localBasisZ2X * this._localBasisX2Y - this._localBasisZ2Y * this._localBasisX2X; + } + } + buildLocalBasesFromX1Z2() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + if(this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z == 0) { + this._localBasisZ2X = 0; + this._localBasisZ2Y = 0; + this._localBasisZ2Z = 1; + } else { + let l = this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ2X *= l; + this._localBasisZ2Y *= l; + this._localBasisZ2Z *= l; + } + let tf1 = this._b1._transform; + let tf2 = this._b2._transform; + let worldX1X; + let worldX1Y; + let worldX1Z; + let worldZ1X; + let worldZ1Y; + let worldZ1Z; + let worldYX; + let worldYY; + let worldYZ; + let worldX2X; + let worldX2Y; + let worldX2Z; + let worldZ2X; + let worldZ2Y; + let worldZ2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * this._localBasisX1X + tf1._rotation01 * this._localBasisX1Y + tf1._rotation02 * this._localBasisX1Z; + __tmp__Y = tf1._rotation10 * this._localBasisX1X + tf1._rotation11 * this._localBasisX1Y + tf1._rotation12 * this._localBasisX1Z; + __tmp__Z = tf1._rotation20 * this._localBasisX1X + tf1._rotation21 * this._localBasisX1Y + tf1._rotation22 * this._localBasisX1Z; + worldX1X = __tmp__X; + worldX1Y = __tmp__Y; + worldX1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * this._localBasisZ2X + tf2._rotation01 * this._localBasisZ2Y + tf2._rotation02 * this._localBasisZ2Z; + __tmp__Y1 = tf2._rotation10 * this._localBasisZ2X + tf2._rotation11 * this._localBasisZ2Y + tf2._rotation12 * this._localBasisZ2Z; + __tmp__Z1 = tf2._rotation20 * this._localBasisZ2X + tf2._rotation21 * this._localBasisZ2Y + tf2._rotation22 * this._localBasisZ2Z; + worldZ2X = __tmp__X1; + worldZ2Y = __tmp__Y1; + worldZ2Z = __tmp__Z1; + worldYX = worldZ2Y * worldX1Z - worldZ2Z * worldX1Y; + worldYY = worldZ2Z * worldX1X - worldZ2X * worldX1Z; + worldYZ = worldZ2X * worldX1Y - worldZ2Y * worldX1X; + if(worldYX * worldYX + worldYY * worldYY + worldYZ * worldYZ == 0) { + let x1 = worldX1X; + let y1 = worldX1Y; + let z1 = worldX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + worldYX = 0; + worldYY = z1 * d; + worldYZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + worldYX = y1 * d; + worldYY = -x1 * d; + worldYZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + worldYX = -z1 * d; + worldYY = 0; + worldYZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + worldYX = y1 * d; + worldYY = -x1 * d; + worldYZ = 0; + } + } + worldZ1X = worldX1Y * worldYZ - worldX1Z * worldYY; + worldZ1Y = worldX1Z * worldYX - worldX1X * worldYZ; + worldZ1Z = worldX1X * worldYY - worldX1Y * worldYX; + worldX2X = worldYY * worldZ2Z - worldYZ * worldZ2Y; + worldX2Y = worldYZ * worldZ2X - worldYX * worldZ2Z; + worldX2Z = worldYX * worldZ2Y - worldYY * worldZ2X; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf1._rotation00 * worldX1X + tf1._rotation10 * worldX1Y + tf1._rotation20 * worldX1Z; + __tmp__Y2 = tf1._rotation01 * worldX1X + tf1._rotation11 * worldX1Y + tf1._rotation21 * worldX1Z; + __tmp__Z2 = tf1._rotation02 * worldX1X + tf1._rotation12 * worldX1Y + tf1._rotation22 * worldX1Z; + this._localBasisX1X = __tmp__X2; + this._localBasisX1Y = __tmp__Y2; + this._localBasisX1Z = __tmp__Z2; + let __tmp__X3; + let __tmp__Y3; + let __tmp__Z3; + __tmp__X3 = tf1._rotation00 * worldYX + tf1._rotation10 * worldYY + tf1._rotation20 * worldYZ; + __tmp__Y3 = tf1._rotation01 * worldYX + tf1._rotation11 * worldYY + tf1._rotation21 * worldYZ; + __tmp__Z3 = tf1._rotation02 * worldYX + tf1._rotation12 * worldYY + tf1._rotation22 * worldYZ; + this._localBasisY1X = __tmp__X3; + this._localBasisY1Y = __tmp__Y3; + this._localBasisY1Z = __tmp__Z3; + let __tmp__X4; + let __tmp__Y4; + let __tmp__Z4; + __tmp__X4 = tf1._rotation00 * worldZ1X + tf1._rotation10 * worldZ1Y + tf1._rotation20 * worldZ1Z; + __tmp__Y4 = tf1._rotation01 * worldZ1X + tf1._rotation11 * worldZ1Y + tf1._rotation21 * worldZ1Z; + __tmp__Z4 = tf1._rotation02 * worldZ1X + tf1._rotation12 * worldZ1Y + tf1._rotation22 * worldZ1Z; + this._localBasisZ1X = __tmp__X4; + this._localBasisZ1Y = __tmp__Y4; + this._localBasisZ1Z = __tmp__Z4; + let __tmp__X5; + let __tmp__Y5; + let __tmp__Z5; + __tmp__X5 = tf2._rotation00 * worldX2X + tf2._rotation10 * worldX2Y + tf2._rotation20 * worldX2Z; + __tmp__Y5 = tf2._rotation01 * worldX2X + tf2._rotation11 * worldX2Y + tf2._rotation21 * worldX2Z; + __tmp__Z5 = tf2._rotation02 * worldX2X + tf2._rotation12 * worldX2Y + tf2._rotation22 * worldX2Z; + this._localBasisX2X = __tmp__X5; + this._localBasisX2Y = __tmp__Y5; + this._localBasisX2Z = __tmp__Z5; + let __tmp__X6; + let __tmp__Y6; + let __tmp__Z6; + __tmp__X6 = tf2._rotation00 * worldYX + tf2._rotation10 * worldYY + tf2._rotation20 * worldYZ; + __tmp__Y6 = tf2._rotation01 * worldYX + tf2._rotation11 * worldYY + tf2._rotation21 * worldYZ; + __tmp__Z6 = tf2._rotation02 * worldYX + tf2._rotation12 * worldYY + tf2._rotation22 * worldYZ; + this._localBasisY2X = __tmp__X6; + this._localBasisY2Y = __tmp__Y6; + this._localBasisY2Z = __tmp__Z6; + let __tmp__X7; + let __tmp__Y7; + let __tmp__Z7; + __tmp__X7 = tf2._rotation00 * worldZ2X + tf2._rotation10 * worldZ2Y + tf2._rotation20 * worldZ2Z; + __tmp__Y7 = tf2._rotation01 * worldZ2X + tf2._rotation11 * worldZ2Y + tf2._rotation21 * worldZ2Z; + __tmp__Z7 = tf2._rotation02 * worldZ2X + tf2._rotation12 * worldZ2Y + tf2._rotation22 * worldZ2Z; + this._localBasisZ2X = __tmp__X7; + this._localBasisZ2Y = __tmp__Y7; + this._localBasisZ2Z = __tmp__Z7; + } + buildLocalBasesFromXY1X2() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + if(this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z == 0) { + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this._localBasisY1X = 0; + this._localBasisY1Y = z1 * d; + this._localBasisY1Z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this._localBasisY1X = -z1 * d; + this._localBasisY1Y = 0; + this._localBasisY1Z = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + } else { + let l = this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ1X *= l; + this._localBasisZ1Y *= l; + this._localBasisZ1Z *= l; + this._localBasisY1X = this._localBasisZ1Y * this._localBasisX1Z - this._localBasisZ1Z * this._localBasisX1Y; + this._localBasisY1Y = this._localBasisZ1Z * this._localBasisX1X - this._localBasisZ1X * this._localBasisX1Z; + this._localBasisY1Z = this._localBasisZ1X * this._localBasisX1Y - this._localBasisZ1Y * this._localBasisX1X; + } + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let d = this._localBasisX1X * this._localBasisX2X + this._localBasisX1Y * this._localBasisX2Y + this._localBasisX1Z * this._localBasisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = this._localBasisX1Y * this._localBasisX2Z - this._localBasisX1Z * this._localBasisX2Y; + cY = this._localBasisX1Z * this._localBasisX2X - this._localBasisX1X * this._localBasisX2Z; + cZ = this._localBasisX1X * this._localBasisX2Y - this._localBasisX1Y * this._localBasisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * this._localBasisX1X + slerpM01 * this._localBasisX1Y + slerpM02 * this._localBasisX1Z; + __tmp__Y = slerpM10 * this._localBasisX1X + slerpM11 * this._localBasisX1Y + slerpM12 * this._localBasisX1Z; + __tmp__Z = slerpM20 * this._localBasisX1X + slerpM21 * this._localBasisX1Y + slerpM22 * this._localBasisX1Z; + this._localBasisX2X = __tmp__X; + this._localBasisX2Y = __tmp__Y; + this._localBasisX2Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * this._localBasisY1X + slerpM01 * this._localBasisY1Y + slerpM02 * this._localBasisY1Z; + __tmp__Y1 = slerpM10 * this._localBasisY1X + slerpM11 * this._localBasisY1Y + slerpM12 * this._localBasisY1Z; + __tmp__Z1 = slerpM20 * this._localBasisY1X + slerpM21 * this._localBasisY1Y + slerpM22 * this._localBasisY1Z; + this._localBasisY2X = __tmp__X1; + this._localBasisY2Y = __tmp__Y1; + this._localBasisY2Z = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = slerpM00 * this._localBasisZ1X + slerpM01 * this._localBasisZ1Y + slerpM02 * this._localBasisZ1Z; + __tmp__Y2 = slerpM10 * this._localBasisZ1X + slerpM11 * this._localBasisZ1Y + slerpM12 * this._localBasisZ1Z; + __tmp__Z2 = slerpM20 * this._localBasisZ1X + slerpM21 * this._localBasisZ1Y + slerpM22 * this._localBasisZ1Z; + this._localBasisZ2X = __tmp__X2; + this._localBasisZ2Y = __tmp__Y2; + this._localBasisZ2Z = __tmp__Z2; + } + setSolverInfoRowLinear(row,diff,lm,mass,sd,timeStep,isPositionPart) { + let cfmFactor; + let erp; + let slop = oimo.common.Setting.linearSlop; + if(isPositionPart) { + cfmFactor = 0; + erp = 1; + } else { + if(sd.frequency > 0) { + slop = 0; + let omega = 6.28318530717958 * sd.frequency; + let zeta = sd.dampingRatio; + if(zeta < oimo.common.Setting.minSpringDamperDampingRatio) { + zeta = oimo.common.Setting.minSpringDamperDampingRatio; + } + let h = timeStep.dt; + let c = 2 * zeta * omega; + let k = omega * omega; + if(sd.useSymplecticEuler) { + cfmFactor = 1 / (h * c); + erp = k / c; + } else { + cfmFactor = 1 / (h * (h * k + c)); + erp = k / (h * k + c); + } + } else { + cfmFactor = 0; + erp = this.getErp(timeStep,false); + } + if(lm.motorForce > 0) { + row.motorSpeed = lm.motorSpeed; + row.motorMaxImpulse = lm.motorForce * timeStep.dt; + } else { + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + } + } + let lower = lm.lowerLimit; + let upper = lm.upperLimit; + let minImp; + let maxImp; + let error; + if(lower > upper) { + minImp = 0; + maxImp = 0; + error = 0; + } else if(lower == upper) { + minImp = -1e65536; + maxImp = 1e65536; + error = diff - lower; + } else if(diff < lower) { + minImp = -1e65536; + maxImp = 0; + error = diff - lower + slop; + if(error > 0) { + error = 0; + } + } else if(diff > upper) { + minImp = 0; + maxImp = 1e65536; + error = diff - upper - slop; + if(error < 0) { + error = 0; + } + } else { + minImp = 0; + maxImp = 0; + error = 0; + } + row.minImpulse = minImp; + row.maxImpulse = maxImp; + row.cfm = cfmFactor * (mass == 0 ? 0 : 1 / mass); + row.rhs = error * erp; + } + setSolverInfoRowAngular(row,diff,lm,mass,sd,timeStep,isPositionPart) { + let cfmFactor; + let erp; + let slop = oimo.common.Setting.angularSlop; + if(isPositionPart) { + cfmFactor = 0; + erp = 1; + } else { + if(sd.frequency > 0) { + slop = 0; + let omega = 6.28318530717958 * sd.frequency; + let zeta = sd.dampingRatio; + if(zeta < oimo.common.Setting.minSpringDamperDampingRatio) { + zeta = oimo.common.Setting.minSpringDamperDampingRatio; + } + let h = timeStep.dt; + let c = 2 * zeta * omega; + let k = omega * omega; + if(sd.useSymplecticEuler) { + cfmFactor = 1 / (h * c); + erp = k / c; + } else { + cfmFactor = 1 / (h * (h * k + c)); + erp = k / (h * k + c); + } + } else { + cfmFactor = 0; + erp = this.getErp(timeStep,false); + } + if(lm.motorTorque > 0) { + row.motorSpeed = lm.motorSpeed; + row.motorMaxImpulse = lm.motorTorque * timeStep.dt; + } else { + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + } + } + let lower = lm.lowerLimit; + let upper = lm.upperLimit; + let mid = (lower + upper) * 0.5; + diff -= mid; + diff = ((diff + 3.14159265358979) % 6.28318530717958 + 6.28318530717958) % 6.28318530717958 - 3.14159265358979; + diff += mid; + let minImp; + let maxImp; + let error; + if(lower > upper) { + minImp = 0; + maxImp = 0; + error = 0; + } else if(lower == upper) { + minImp = -1e65536; + maxImp = 1e65536; + error = diff - lower; + } else if(diff < lower) { + minImp = -1e65536; + maxImp = 0; + error = diff - lower + slop; + if(error > 0) { + error = 0; + } + } else if(diff > upper) { + minImp = 0; + maxImp = 1e65536; + error = diff - upper - slop; + if(error < 0) { + error = 0; + } + } else { + minImp = 0; + maxImp = 0; + error = 0; + } + row.minImpulse = minImp; + row.maxImpulse = maxImp; + row.cfm = cfmFactor * (mass == 0 ? 0 : 1 / mass); + row.rhs = error * erp; + } + getErp(timeStep,isPositionPart) { + if(isPositionPart) { + return 1; + } else if(this._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE) { + return timeStep.invDt * oimo.common.Setting.velocityBaumgarte; + } else { + return 0; + } + } + computeEffectiveInertiaMoment(axisX,axisY,axisZ) { + let ia1X; + let ia1Y; + let ia1Z; + let ia2X; + let ia2Y; + let ia2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._b1._invInertia00 * axisX + this._b1._invInertia01 * axisY + this._b1._invInertia02 * axisZ; + __tmp__Y = this._b1._invInertia10 * axisX + this._b1._invInertia11 * axisY + this._b1._invInertia12 * axisZ; + __tmp__Z = this._b1._invInertia20 * axisX + this._b1._invInertia21 * axisY + this._b1._invInertia22 * axisZ; + ia1X = __tmp__X; + ia1Y = __tmp__Y; + ia1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this._b2._invInertia00 * axisX + this._b2._invInertia01 * axisY + this._b2._invInertia02 * axisZ; + __tmp__Y1 = this._b2._invInertia10 * axisX + this._b2._invInertia11 * axisY + this._b2._invInertia12 * axisZ; + __tmp__Z1 = this._b2._invInertia20 * axisX + this._b2._invInertia21 * axisY + this._b2._invInertia22 * axisZ; + ia2X = __tmp__X1; + ia2Y = __tmp__Y1; + ia2Z = __tmp__Z1; + let invI1 = ia1X * axisX + ia1Y * axisY + ia1Z * axisZ; + let invI2 = ia2X * axisX + ia2Y * axisY + ia2Z * axisZ; + if(invI1 > 0) { + let dot = axisX * this._relativeAnchor1X + axisY * this._relativeAnchor1Y + axisZ * this._relativeAnchor1Z; + let projsq = this._relativeAnchor1X * this._relativeAnchor1X + this._relativeAnchor1Y * this._relativeAnchor1Y + this._relativeAnchor1Z * this._relativeAnchor1Z - dot * dot; + if(projsq > 0) { + if(this._b1._invMass > 0) { + invI1 = 1 / (1 / invI1 + this._b1._mass * projsq); + } else { + invI1 = 0; + } + } + } + if(invI2 > 0) { + let dot = axisX * this._relativeAnchor2X + axisY * this._relativeAnchor2Y + axisZ * this._relativeAnchor2Z; + let projsq = this._relativeAnchor2X * this._relativeAnchor2X + this._relativeAnchor2Y * this._relativeAnchor2Y + this._relativeAnchor2Z * this._relativeAnchor2Z - dot * dot; + if(projsq > 0) { + if(this._b2._invMass > 0) { + invI2 = 1 / (1 / invI2 + this._b2._mass * projsq); + } else { + invI2 = 0; + } + } + } + if(invI1 + invI2 == 0) { + return 0; + } else { + return 1 / (invI1 + invI2); + } + } + computeEffectiveInertiaMoment2(axis1X,axis1Y,axis1Z,axis2X,axis2Y,axis2Z) { + let ia1X; + let ia1Y; + let ia1Z; + let ia2X; + let ia2Y; + let ia2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._b1._invInertia00 * axis1X + this._b1._invInertia01 * axis1Y + this._b1._invInertia02 * axis1Z; + __tmp__Y = this._b1._invInertia10 * axis1X + this._b1._invInertia11 * axis1Y + this._b1._invInertia12 * axis1Z; + __tmp__Z = this._b1._invInertia20 * axis1X + this._b1._invInertia21 * axis1Y + this._b1._invInertia22 * axis1Z; + ia1X = __tmp__X; + ia1Y = __tmp__Y; + ia1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this._b2._invInertia00 * axis2X + this._b2._invInertia01 * axis2Y + this._b2._invInertia02 * axis2Z; + __tmp__Y1 = this._b2._invInertia10 * axis2X + this._b2._invInertia11 * axis2Y + this._b2._invInertia12 * axis2Z; + __tmp__Z1 = this._b2._invInertia20 * axis2X + this._b2._invInertia21 * axis2Y + this._b2._invInertia22 * axis2Z; + ia2X = __tmp__X1; + ia2Y = __tmp__Y1; + ia2Z = __tmp__Z1; + let invI1 = ia1X * axis1X + ia1Y * axis1Y + ia1Z * axis1Z; + let invI2 = ia2X * axis2X + ia2Y * axis2Y + ia2Z * axis2Z; + if(invI1 > 0) { + let rsq = this._relativeAnchor1X * this._relativeAnchor1X + this._relativeAnchor1Y * this._relativeAnchor1Y + this._relativeAnchor1Z * this._relativeAnchor1Z; + let dot = axis1X * this._relativeAnchor1X + axis1Y * this._relativeAnchor1Y + axis1Z * this._relativeAnchor1Z; + let projsq = rsq * rsq - dot * dot; + if(projsq > 0) { + if(this._b1._invMass > 0) { + invI1 = 1 / (1 / invI1 + this._b1._mass * projsq); + } else { + invI1 = 0; + } + } + } + if(invI2 > 0) { + let rsq = this._relativeAnchor2X * this._relativeAnchor2X + this._relativeAnchor2Y * this._relativeAnchor2Y + this._relativeAnchor2Z * this._relativeAnchor2Z; + let dot = axis2X * this._relativeAnchor2X + axis2Y * this._relativeAnchor2Y + axis2Z * this._relativeAnchor2Z; + let projsq = rsq * rsq - dot * dot; + if(projsq > 0) { + if(this._b2._invMass > 0) { + invI2 = 1 / (1 / invI2 + this._b2._mass * projsq); + } else { + invI2 = 0; + } + } + } + if(invI1 + invI2 == 0) { + return 0; + } else { + return 1 / (invI1 + invI2); + } + } + _syncAnchors() { + let tf1 = this._b1._transform; + let tf2 = this._b2._transform; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * this._localAnchor1X + tf1._rotation01 * this._localAnchor1Y + tf1._rotation02 * this._localAnchor1Z; + __tmp__Y = tf1._rotation10 * this._localAnchor1X + tf1._rotation11 * this._localAnchor1Y + tf1._rotation12 * this._localAnchor1Z; + __tmp__Z = tf1._rotation20 * this._localAnchor1X + tf1._rotation21 * this._localAnchor1Y + tf1._rotation22 * this._localAnchor1Z; + this._relativeAnchor1X = __tmp__X; + this._relativeAnchor1Y = __tmp__Y; + this._relativeAnchor1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * this._localAnchor2X + tf2._rotation01 * this._localAnchor2Y + tf2._rotation02 * this._localAnchor2Z; + __tmp__Y1 = tf2._rotation10 * this._localAnchor2X + tf2._rotation11 * this._localAnchor2Y + tf2._rotation12 * this._localAnchor2Z; + __tmp__Z1 = tf2._rotation20 * this._localAnchor2X + tf2._rotation21 * this._localAnchor2Y + tf2._rotation22 * this._localAnchor2Z; + this._relativeAnchor2X = __tmp__X1; + this._relativeAnchor2Y = __tmp__Y1; + this._relativeAnchor2Z = __tmp__Z1; + this._anchor1X = this._relativeAnchor1X + tf1._positionX; + this._anchor1Y = this._relativeAnchor1Y + tf1._positionY; + this._anchor1Z = this._relativeAnchor1Z + tf1._positionZ; + this._anchor2X = this._relativeAnchor2X + tf2._positionX; + this._anchor2Y = this._relativeAnchor2Y + tf2._positionY; + this._anchor2Z = this._relativeAnchor2Z + tf2._positionZ; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf1._rotation00 * this._localBasisX1X + tf1._rotation01 * this._localBasisX1Y + tf1._rotation02 * this._localBasisX1Z; + __tmp__Y2 = tf1._rotation10 * this._localBasisX1X + tf1._rotation11 * this._localBasisX1Y + tf1._rotation12 * this._localBasisX1Z; + __tmp__Z2 = tf1._rotation20 * this._localBasisX1X + tf1._rotation21 * this._localBasisX1Y + tf1._rotation22 * this._localBasisX1Z; + this._basisX1X = __tmp__X2; + this._basisX1Y = __tmp__Y2; + this._basisX1Z = __tmp__Z2; + let __tmp__X3; + let __tmp__Y3; + let __tmp__Z3; + __tmp__X3 = tf1._rotation00 * this._localBasisY1X + tf1._rotation01 * this._localBasisY1Y + tf1._rotation02 * this._localBasisY1Z; + __tmp__Y3 = tf1._rotation10 * this._localBasisY1X + tf1._rotation11 * this._localBasisY1Y + tf1._rotation12 * this._localBasisY1Z; + __tmp__Z3 = tf1._rotation20 * this._localBasisY1X + tf1._rotation21 * this._localBasisY1Y + tf1._rotation22 * this._localBasisY1Z; + this._basisY1X = __tmp__X3; + this._basisY1Y = __tmp__Y3; + this._basisY1Z = __tmp__Z3; + let __tmp__X4; + let __tmp__Y4; + let __tmp__Z4; + __tmp__X4 = tf1._rotation00 * this._localBasisZ1X + tf1._rotation01 * this._localBasisZ1Y + tf1._rotation02 * this._localBasisZ1Z; + __tmp__Y4 = tf1._rotation10 * this._localBasisZ1X + tf1._rotation11 * this._localBasisZ1Y + tf1._rotation12 * this._localBasisZ1Z; + __tmp__Z4 = tf1._rotation20 * this._localBasisZ1X + tf1._rotation21 * this._localBasisZ1Y + tf1._rotation22 * this._localBasisZ1Z; + this._basisZ1X = __tmp__X4; + this._basisZ1Y = __tmp__Y4; + this._basisZ1Z = __tmp__Z4; + let __tmp__X5; + let __tmp__Y5; + let __tmp__Z5; + __tmp__X5 = tf2._rotation00 * this._localBasisX2X + tf2._rotation01 * this._localBasisX2Y + tf2._rotation02 * this._localBasisX2Z; + __tmp__Y5 = tf2._rotation10 * this._localBasisX2X + tf2._rotation11 * this._localBasisX2Y + tf2._rotation12 * this._localBasisX2Z; + __tmp__Z5 = tf2._rotation20 * this._localBasisX2X + tf2._rotation21 * this._localBasisX2Y + tf2._rotation22 * this._localBasisX2Z; + this._basisX2X = __tmp__X5; + this._basisX2Y = __tmp__Y5; + this._basisX2Z = __tmp__Z5; + let __tmp__X6; + let __tmp__Y6; + let __tmp__Z6; + __tmp__X6 = tf2._rotation00 * this._localBasisY2X + tf2._rotation01 * this._localBasisY2Y + tf2._rotation02 * this._localBasisY2Z; + __tmp__Y6 = tf2._rotation10 * this._localBasisY2X + tf2._rotation11 * this._localBasisY2Y + tf2._rotation12 * this._localBasisY2Z; + __tmp__Z6 = tf2._rotation20 * this._localBasisY2X + tf2._rotation21 * this._localBasisY2Y + tf2._rotation22 * this._localBasisY2Z; + this._basisY2X = __tmp__X6; + this._basisY2Y = __tmp__Y6; + this._basisY2Z = __tmp__Z6; + let __tmp__X7; + let __tmp__Y7; + let __tmp__Z7; + __tmp__X7 = tf2._rotation00 * this._localBasisZ2X + tf2._rotation01 * this._localBasisZ2Y + tf2._rotation02 * this._localBasisZ2Z; + __tmp__Y7 = tf2._rotation10 * this._localBasisZ2X + tf2._rotation11 * this._localBasisZ2Y + tf2._rotation12 * this._localBasisZ2Z; + __tmp__Z7 = tf2._rotation20 * this._localBasisZ2X + tf2._rotation21 * this._localBasisZ2Y + tf2._rotation22 * this._localBasisZ2Z; + this._basisZ2X = __tmp__X7; + this._basisZ2Y = __tmp__Y7; + this._basisZ2Z = __tmp__Z7; + } + _getVelocitySolverInfo(timeStep,info) { + info.b1 = this._b1; + info.b2 = this._b2; + info.numRows = 0; + } + _getPositionSolverInfo(info) { + info.b1 = this._b1; + info.b2 = this._b2; + info.numRows = 0; + } + _checkDestruction() { + let torqueSq = this._appliedTorqueX * this._appliedTorqueX + this._appliedTorqueY * this._appliedTorqueY + this._appliedTorqueZ * this._appliedTorqueZ; + if(this._breakForce > 0 && this._appliedForceX * this._appliedForceX + this._appliedForceY * this._appliedForceY + this._appliedForceZ * this._appliedForceZ > this._breakForce * this._breakForce) { + this._world.removeJoint(this); + return; + } + if(this._breakTorque > 0 && torqueSq > this._breakTorque * this._breakTorque) { + this._world.removeJoint(this); + return; + } + } + getRigidBody1() { + return this._b1; + } + getRigidBody2() { + return this._b2; + } + getType() { + return this._type; + } + getAnchor1() { + let v = new oimo.common.Vec3(); + v.x = this._anchor1X; + v.y = this._anchor1Y; + v.z = this._anchor1Z; + return v; + } + getAnchor2() { + let v = new oimo.common.Vec3(); + v.x = this._anchor2X; + v.y = this._anchor2Y; + v.z = this._anchor2Z; + return v; + } + getAnchor1To(anchor) { + anchor.x = this._anchor1X; + anchor.y = this._anchor1Y; + anchor.z = this._anchor1Z; + } + getAnchor2To(anchor) { + anchor.x = this._anchor2X; + anchor.y = this._anchor2Y; + anchor.z = this._anchor2Z; + } + getLocalAnchor1() { + let v = new oimo.common.Vec3(); + v.x = this._localAnchor1X; + v.y = this._localAnchor1Y; + v.z = this._localAnchor1Z; + return v; + } + getLocalAnchor2() { + let v = new oimo.common.Vec3(); + v.x = this._localAnchor2X; + v.y = this._localAnchor2Y; + v.z = this._localAnchor2Z; + return v; + } + getLocalAnchor1To(localAnchor) { + localAnchor.x = this._localAnchor1X; + localAnchor.y = this._localAnchor1Y; + localAnchor.z = this._localAnchor1Z; + } + getLocalAnchor2To(localAnchor) { + localAnchor.x = this._localAnchor2X; + localAnchor.y = this._localAnchor2Y; + localAnchor.z = this._localAnchor2Z; + } + getBasis1() { + let m = new oimo.common.Mat3(); + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX1X; + b01 = this._basisY1X; + b02 = this._basisZ1X; + b10 = this._basisX1Y; + b11 = this._basisY1Y; + b12 = this._basisZ1Y; + b20 = this._basisX1Z; + b21 = this._basisY1Z; + b22 = this._basisZ1Z; + m.e00 = b00; + m.e01 = b01; + m.e02 = b02; + m.e10 = b10; + m.e11 = b11; + m.e12 = b12; + m.e20 = b20; + m.e21 = b21; + m.e22 = b22; + return m; + } + getBasis2() { + let m = new oimo.common.Mat3(); + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX2X; + b01 = this._basisY2X; + b02 = this._basisZ2X; + b10 = this._basisX2Y; + b11 = this._basisY2Y; + b12 = this._basisZ2Y; + b20 = this._basisX2Z; + b21 = this._basisY2Z; + b22 = this._basisZ2Z; + m.e00 = b00; + m.e01 = b01; + m.e02 = b02; + m.e10 = b10; + m.e11 = b11; + m.e12 = b12; + m.e20 = b20; + m.e21 = b21; + m.e22 = b22; + return m; + } + getBasis1To(basis) { + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX1X; + b01 = this._basisY1X; + b02 = this._basisZ1X; + b10 = this._basisX1Y; + b11 = this._basisY1Y; + b12 = this._basisZ1Y; + b20 = this._basisX1Z; + b21 = this._basisY1Z; + b22 = this._basisZ1Z; + basis.e00 = b00; + basis.e01 = b01; + basis.e02 = b02; + basis.e10 = b10; + basis.e11 = b11; + basis.e12 = b12; + basis.e20 = b20; + basis.e21 = b21; + basis.e22 = b22; + } + getBasis2To(basis) { + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX2X; + b01 = this._basisY2X; + b02 = this._basisZ2X; + b10 = this._basisX2Y; + b11 = this._basisY2Y; + b12 = this._basisZ2Y; + b20 = this._basisX2Z; + b21 = this._basisY2Z; + b22 = this._basisZ2Z; + basis.e00 = b00; + basis.e01 = b01; + basis.e02 = b02; + basis.e10 = b10; + basis.e11 = b11; + basis.e12 = b12; + basis.e20 = b20; + basis.e21 = b21; + basis.e22 = b22; + } + getAllowCollision() { + return this._allowCollision; + } + setAllowCollision(allowCollision) { + this._allowCollision = allowCollision; + } + getBreakForce() { + return this._breakForce; + } + setBreakForce(breakForce) { + this._breakForce = breakForce; + } + getBreakTorque() { + return this._breakTorque; + } + setBreakTorque(breakTorque) { + this._breakTorque = breakTorque; + } + getPositionCorrectionAlgorithm() { + return this._positionCorrectionAlgorithm; + } + setPositionCorrectionAlgorithm(positionCorrectionAlgorithm) { + switch(positionCorrectionAlgorithm) { + case 0:case 1:case 2: + break; + default: + throw new Error("invalid position correction algorithm id: " + positionCorrectionAlgorithm); + } + this._positionCorrectionAlgorithm = positionCorrectionAlgorithm; + } + getAppliedForce() { + let v = new oimo.common.Vec3(); + v.x = this._appliedForceX; + v.y = this._appliedForceY; + v.z = this._appliedForceZ; + return v; + } + getAppliedForceTo(appliedForce) { + appliedForce.x = this._appliedForceX; + appliedForce.y = this._appliedForceY; + appliedForce.z = this._appliedForceZ; + } + getAppliedTorque() { + let v = new oimo.common.Vec3(); + v.x = this._appliedTorqueX; + v.y = this._appliedTorqueY; + v.z = this._appliedTorqueZ; + return v; + } + getAppliedTorqueTo(appliedTorque) { + appliedTorque.x = this._appliedTorqueX; + appliedTorque.y = this._appliedTorqueY; + appliedTorque.z = this._appliedTorqueZ; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.constraint.joint.CylindricalJoint = class oimo_dynamics_constraint_joint_CylindricalJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,2); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisX2X = v1.x; + this._localBasisX2Y = v1.y; + this._localBasisX2Z = v1.z; + this.buildLocalBasesFromX(); + this.angle = 0; + this.angularErrorY = 0; + this.angularErrorZ = 0; + this.translation = 0; + this.linearErrorY = 0; + this.linearErrorZ = 0; + this._basis = new oimo.dynamics.constraint.joint.BasisTracker(this); + this._translSd = config.translationalSpringDamper.clone(); + this._translLm = config.translationalLimitMotor.clone(); + this._rotSd = config.rotationalSpringDamper.clone(); + this._rotLm = config.rotationalLimitMotor.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linRhsY = this.linearErrorY * erp; + let linRhsZ = this.linearErrorZ * erp; + let angRhsY = this.angularErrorY * erp; + let angRhsZ = this.angularErrorZ * erp; + let j; + let translationalMotorMass = 1 / (this._b1._invMass + this._b2._invMass); + let rotationalMotorMass = this.computeEffectiveInertiaMoment(this._basis.xX,this._basis.xY,this._basis.xZ); + if(this._translSd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translation,this._translLm,translationalMotorMass,this._translSd,timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basis.xX; + j.lin1Y = this._basis.xY; + j.lin1Z = this._basis.xZ; + j.lin2X = this._basis.xX; + j.lin2Y = this._basis.xY; + j.lin2Z = this._basis.xZ; + j.ang1X = this._relativeAnchor1Y * this._basis.xZ - this._relativeAnchor1Z * this._basis.xY; + j.ang1Y = this._relativeAnchor1Z * this._basis.xX - this._relativeAnchor1X * this._basis.xZ; + j.ang1Z = this._relativeAnchor1X * this._basis.xY - this._relativeAnchor1Y * this._basis.xX; + j.ang2X = this._relativeAnchor2Y * this._basis.xZ - this._relativeAnchor2Z * this._basis.xY; + j.ang2Y = this._relativeAnchor2Z * this._basis.xX - this._relativeAnchor2X * this._basis.xZ; + j.ang2Z = this._relativeAnchor2X * this._basis.xY - this._relativeAnchor2Y * this._basis.xX; + } + let impulse = this._impulses[1]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linRhsY; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + j = row.jacobian; + j.lin1X = this._basis.yX; + j.lin1Y = this._basis.yY; + j.lin1Z = this._basis.yZ; + j.lin2X = this._basis.yX; + j.lin2Y = this._basis.yY; + j.lin2Z = this._basis.yZ; + j.ang1X = this._relativeAnchor1Y * this._basis.yZ - this._relativeAnchor1Z * this._basis.yY; + j.ang1Y = this._relativeAnchor1Z * this._basis.yX - this._relativeAnchor1X * this._basis.yZ; + j.ang1Z = this._relativeAnchor1X * this._basis.yY - this._relativeAnchor1Y * this._basis.yX; + j.ang2X = this._relativeAnchor2Y * this._basis.yZ - this._relativeAnchor2Z * this._basis.yY; + j.ang2Y = this._relativeAnchor2Z * this._basis.yX - this._relativeAnchor2X * this._basis.yZ; + j.ang2Z = this._relativeAnchor2X * this._basis.yY - this._relativeAnchor2Y * this._basis.yX; + let impulse1 = this._impulses[2]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linRhsZ; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = this._basis.zX; + j.lin1Y = this._basis.zY; + j.lin1Z = this._basis.zZ; + j.lin2X = this._basis.zX; + j.lin2Y = this._basis.zY; + j.lin2Z = this._basis.zZ; + j.ang1X = this._relativeAnchor1Y * this._basis.zZ - this._relativeAnchor1Z * this._basis.zY; + j.ang1Y = this._relativeAnchor1Z * this._basis.zX - this._relativeAnchor1X * this._basis.zZ; + j.ang1Z = this._relativeAnchor1X * this._basis.zY - this._relativeAnchor1Y * this._basis.zX; + j.ang2X = this._relativeAnchor2Y * this._basis.zZ - this._relativeAnchor2Z * this._basis.zY; + j.ang2Y = this._relativeAnchor2Z * this._basis.zX - this._relativeAnchor2X * this._basis.zZ; + j.ang2Z = this._relativeAnchor2X * this._basis.zY - this._relativeAnchor2Y * this._basis.zX; + if(this._rotSd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this.angle,this._rotLm,rotationalMotorMass,this._rotSd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._basis.xX; + j.ang1Y = this._basis.xY; + j.ang1Z = this._basis.xZ; + j.ang2X = this._basis.xX; + j.ang2Y = this._basis.xY; + j.ang2Z = this._basis.xZ; + } + let impulse2 = this._impulses[4]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = angRhsY; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.ang1X = this._basis.yX; + j.ang1Y = this._basis.yY; + j.ang1Z = this._basis.yZ; + j.ang2X = this._basis.yX; + j.ang2Y = this._basis.yY; + j.ang2Z = this._basis.yZ; + let impulse3 = this._impulses[5]; + let row3 = info.rows[info.numRows++]; + let _this3 = row3.jacobian; + _this3.lin1X = 0; + _this3.lin1Y = 0; + _this3.lin1Z = 0; + _this3.lin2X = 0; + _this3.lin2Y = 0; + _this3.lin2Z = 0; + _this3.ang1X = 0; + _this3.ang1Y = 0; + _this3.ang1Z = 0; + _this3.ang2X = 0; + _this3.ang2Y = 0; + _this3.ang2Z = 0; + row3.rhs = 0; + row3.cfm = 0; + row3.minImpulse = 0; + row3.maxImpulse = 0; + row3.motorSpeed = 0; + row3.motorMaxImpulse = 0; + row3.impulse = null; + row3.impulse = impulse3; + row3.rhs = angRhsZ; + row3.cfm = 0; + row3.minImpulse = -1e65536; + row3.maxImpulse = 1e65536; + j = row3.jacobian; + j.ang1X = this._basis.zX; + j.ang1Y = this._basis.zY; + j.ang1Z = this._basis.zZ; + j.ang2X = this._basis.zX; + j.ang2Y = this._basis.zY; + j.ang2Z = this._basis.zZ; + } + _syncAnchors() { + super._syncAnchors(); + let _this = this._basis; + let invM1 = _this.joint._b1._invMass; + let invM2 = _this.joint._b2._invMass; + let qX; + let qY; + let qZ; + let qW; + let idQX; + let idQY; + let idQZ; + let idQW; + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let newXX; + let newXY; + let newXZ; + let newYX; + let newYY; + let newYZ; + let newZX; + let newZY; + let newZZ; + let prevXX; + let prevXY; + let prevXZ; + let prevYX; + let prevYY; + let prevYZ; + let d = _this.joint._basisX1X * _this.joint._basisX2X + _this.joint._basisX1Y * _this.joint._basisX2Y + _this.joint._basisX1Z * _this.joint._basisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = _this.joint._basisX1X; + let y1 = _this.joint._basisX1Y; + let z1 = _this.joint._basisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + qX = vX; + qY = vY; + qZ = vZ; + qW = 0; + } else { + let cX; + let cY; + let cZ; + cX = _this.joint._basisX1Y * _this.joint._basisX2Z - _this.joint._basisX1Z * _this.joint._basisX2Y; + cY = _this.joint._basisX1Z * _this.joint._basisX2X - _this.joint._basisX1X * _this.joint._basisX2Z; + cZ = _this.joint._basisX1X * _this.joint._basisX2Y - _this.joint._basisX1Y * _this.joint._basisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + qX = cX; + qY = cY; + qZ = cZ; + qW = w; + } + idQX = 0; + idQY = 0; + idQZ = 0; + idQW = 1; + let q1X; + let q1Y; + let q1Z; + let q1W; + let q2X; + let q2Y; + let q2Z; + let q2W; + q1X = idQX; + q1Y = idQY; + q1Z = idQZ; + q1W = idQW; + q2X = qX; + q2Y = qY; + q2Z = qZ; + q2W = qW; + let d1 = q1X * q2X + q1Y * q2Y + q1Z * q2Z + q1W * q2W; + if(d1 < 0) { + d1 = -d1; + q2X = -q2X; + q2Y = -q2Y; + q2Z = -q2Z; + q2W = -q2W; + } + if(d1 > 0.999999) { + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = q2X - q1X; + dqY = q2Y - q1Y; + dqZ = q2Z - q1Z; + dqW = q2W - q1W; + q2X = q1X + dqX * (invM1 / (invM1 + invM2)); + q2Y = q1Y + dqY * (invM1 / (invM1 + invM2)); + q2Z = q1Z + dqZ * (invM1 / (invM1 + invM2)); + q2W = q1W + dqW * (invM1 / (invM1 + invM2)); + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + slerpQX = q2X * l; + slerpQY = q2Y * l; + slerpQZ = q2Z * l; + slerpQW = q2W * l; + } else { + let theta = invM1 / (invM1 + invM2) * Math.acos(d1); + q2X += q1X * -d1; + q2Y += q1Y * -d1; + q2Z += q1Z * -d1; + q2W += q1W * -d1; + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + q2X *= l; + q2Y *= l; + q2Z *= l; + q2W *= l; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + q1X *= cos; + q1Y *= cos; + q1Z *= cos; + q1W *= cos; + slerpQX = q1X + q2X * sin; + slerpQY = q1Y + q2Y * sin; + slerpQZ = q1Z + q2Z * sin; + slerpQW = q1W + q2W * sin; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * _this.joint._basisX1X + slerpM01 * _this.joint._basisX1Y + slerpM02 * _this.joint._basisX1Z; + __tmp__Y = slerpM10 * _this.joint._basisX1X + slerpM11 * _this.joint._basisX1Y + slerpM12 * _this.joint._basisX1Z; + __tmp__Z = slerpM20 * _this.joint._basisX1X + slerpM21 * _this.joint._basisX1Y + slerpM22 * _this.joint._basisX1Z; + newXX = __tmp__X; + newXY = __tmp__Y; + newXZ = __tmp__Z; + prevXX = _this.xX; + prevXY = _this.xY; + prevXZ = _this.xZ; + prevYX = _this.yX; + prevYY = _this.yY; + prevYZ = _this.yZ; + let d2 = prevXX * newXX + prevXY * newXY + prevXZ * newXZ; + if(d2 < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = prevXX; + let y1 = prevXY; + let z1 = prevXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = prevXY * newXZ - prevXZ * newXY; + cY = prevXZ * newXX - prevXX * newXZ; + cZ = prevXX * newXY - prevXY * newXX; + let w = Math.sqrt((1 + d2) * 0.5); + d2 = 0.5 / w; + cX *= d2; + cY *= d2; + cZ *= d2; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x1 = slerpQX; + let y1 = slerpQY; + let z1 = slerpQZ; + let w1 = slerpQW; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + slerpM00 = 1 - yy1 - zz1; + slerpM01 = xy1 - wz1; + slerpM02 = xz1 + wy1; + slerpM10 = xy1 + wz1; + slerpM11 = 1 - xx1 - zz1; + slerpM12 = yz1 - wx1; + slerpM20 = xz1 - wy1; + slerpM21 = yz1 + wx1; + slerpM22 = 1 - xx1 - yy1; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * prevYX + slerpM01 * prevYY + slerpM02 * prevYZ; + __tmp__Y1 = slerpM10 * prevYX + slerpM11 * prevYY + slerpM12 * prevYZ; + __tmp__Z1 = slerpM20 * prevYX + slerpM21 * prevYY + slerpM22 * prevYZ; + newYX = __tmp__X1; + newYY = __tmp__Y1; + newYZ = __tmp__Z1; + newZX = newXY * newYZ - newXZ * newYY; + newZY = newXZ * newYX - newXX * newYZ; + newZZ = newXX * newYY - newXY * newYX; + if(newZX * newZX + newZY * newZY + newZZ * newZZ > 1e-6) { + let l = newZX * newZX + newZY * newZY + newZZ * newZZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + newZX *= l; + newZY *= l; + newZZ *= l; + } else { + let x1 = newXX; + let y1 = newXY; + let z1 = newXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + newZX = 0; + newZY = z1 * d; + newZZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + newZX = -z1 * d; + newZY = 0; + newZZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } + newYX = newZY * newXZ - newZZ * newXY; + newYY = newZZ * newXX - newZX * newXZ; + newYZ = newZX * newXY - newZY * newXX; + _this.xX = newXX; + _this.xY = newXY; + _this.xZ = newXZ; + _this.yX = newYX; + _this.yY = newYY; + _this.yZ = newYZ; + _this.zX = newZX; + _this.zY = newZY; + _this.zZ = newZZ; + let angErrorX; + let angErrorY; + let angErrorZ; + angErrorX = this._basisX1Y * this._basisX2Z - this._basisX1Z * this._basisX2Y; + angErrorY = this._basisX1Z * this._basisX2X - this._basisX1X * this._basisX2Z; + angErrorZ = this._basisX1X * this._basisX2Y - this._basisX1Y * this._basisX2X; + let cos = this._basisX1X * this._basisX2X + this._basisX1Y * this._basisX2Y + this._basisX1Z * this._basisX2Z; + let theta = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + let l = angErrorX * angErrorX + angErrorY * angErrorY + angErrorZ * angErrorZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + angErrorX *= l; + angErrorY *= l; + angErrorZ *= l; + angErrorX *= theta; + angErrorY *= theta; + angErrorZ *= theta; + this.angularErrorY = angErrorX * this._basis.yX + angErrorY * this._basis.yY + angErrorZ * this._basis.yZ; + this.angularErrorZ = angErrorX * this._basis.zX + angErrorY * this._basis.zY + angErrorZ * this._basis.zZ; + let perpCrossX; + let perpCrossY; + let perpCrossZ; + perpCrossX = this._basisY1Y * this._basisY2Z - this._basisY1Z * this._basisY2Y; + perpCrossY = this._basisY1Z * this._basisY2X - this._basisY1X * this._basisY2Z; + perpCrossZ = this._basisY1X * this._basisY2Y - this._basisY1Y * this._basisY2X; + cos = this._basisY1X * this._basisY2X + this._basisY1Y * this._basisY2Y + this._basisY1Z * this._basisY2Z; + this.angle = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + if(perpCrossX * this._basis.xX + perpCrossY * this._basis.xY + perpCrossZ * this._basis.xZ < 0) { + this.angle = -this.angle; + } + let anchorDiffX; + let anchorDiffY; + let anchorDiffZ; + anchorDiffX = this._anchor2X - this._anchor1X; + anchorDiffY = this._anchor2Y - this._anchor1Y; + anchorDiffZ = this._anchor2Z - this._anchor1Z; + this.translation = anchorDiffX * this._basis.xX + anchorDiffY * this._basis.xY + anchorDiffZ * this._basis.xZ; + this.linearErrorY = anchorDiffX * this._basis.yX + anchorDiffY * this._basis.yY + anchorDiffZ * this._basis.yZ; + this.linearErrorZ = anchorDiffX * this._basis.zX + anchorDiffY * this._basis.zY + anchorDiffZ * this._basis.zZ; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getTranslationalSpringDamper() { + return this._translSd; + } + getRotationalSpringDamper() { + return this._rotSd; + } + getTranslationalLimitMotor() { + return this._translLm; + } + getRotationalLimitMotor() { + return this._rotLm; + } + getAngle() { + return this.angle; + } + getTranslation() { + return this.translation; + } +} +oimo.dynamics.constraint.joint.JointConfig = class oimo_dynamics_constraint_joint_JointConfig { + constructor() { + this.rigidBody1 = null; + this.rigidBody2 = null; + this.localAnchor1 = new oimo.common.Vec3(); + this.localAnchor2 = new oimo.common.Vec3(); + this.allowCollision = false; + this.solverType = oimo.common.Setting.defaultJointConstraintSolverType; + this.positionCorrectionAlgorithm = oimo.common.Setting.defaultJointPositionCorrectionAlgorithm; + this.breakForce = 0; + this.breakTorque = 0; + } + _init(rb1,rb2,worldAnchor) { + this.rigidBody1 = rb1; + this.rigidBody2 = rb2; + let _this = this.rigidBody1; + let localPoint = this.localAnchor1; + let vX; + let vY; + let vZ; + vX = worldAnchor.x; + vY = worldAnchor.y; + vZ = worldAnchor.z; + vX -= _this._transform._positionX; + vY -= _this._transform._positionY; + vZ -= _this._transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = _this._transform._rotation00 * vX + _this._transform._rotation10 * vY + _this._transform._rotation20 * vZ; + __tmp__Y = _this._transform._rotation01 * vX + _this._transform._rotation11 * vY + _this._transform._rotation21 * vZ; + __tmp__Z = _this._transform._rotation02 * vX + _this._transform._rotation12 * vY + _this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localPoint.x = vX; + localPoint.y = vY; + localPoint.z = vZ; + let _this1 = this.rigidBody2; + let localPoint1 = this.localAnchor2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAnchor.x; + vY1 = worldAnchor.y; + vZ1 = worldAnchor.z; + vX1 -= _this1._transform._positionX; + vY1 -= _this1._transform._positionY; + vZ1 -= _this1._transform._positionZ; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = _this1._transform._rotation00 * vX1 + _this1._transform._rotation10 * vY1 + _this1._transform._rotation20 * vZ1; + __tmp__Y1 = _this1._transform._rotation01 * vX1 + _this1._transform._rotation11 * vY1 + _this1._transform._rotation21 * vZ1; + __tmp__Z1 = _this1._transform._rotation02 * vX1 + _this1._transform._rotation12 * vY1 + _this1._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localPoint1.x = vX1; + localPoint1.y = vY1; + localPoint1.z = vZ1; + } +} +oimo.dynamics.constraint.joint.CylindricalJointConfig = class oimo_dynamics_constraint_joint_CylindricalJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.translationalLimitMotor = new oimo.dynamics.constraint.joint.TranslationalLimitMotor(); + this.translationalSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.rotationalLimitMotor = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.rotationalSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis.x; + vY = worldAxis.y; + vZ = worldAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis.x; + vY1 = worldAxis.y; + vZ1 = worldAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +oimo.dynamics.constraint.joint.GenericJoint = class oimo_dynamics_constraint_joint_GenericJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.GENERIC); + let tmp; + let _this = config.localBasis1; + if(!(_this.e00 * (_this.e11 * _this.e22 - _this.e12 * _this.e21) - _this.e01 * (_this.e10 * _this.e22 - _this.e12 * _this.e20) + _this.e02 * (_this.e10 * _this.e21 - _this.e11 * _this.e20) < 0)) { + let _this = config.localBasis2; + tmp = _this.e00 * (_this.e11 * _this.e22 - _this.e12 * _this.e21) - _this.e01 * (_this.e10 * _this.e22 - _this.e12 * _this.e20) + _this.e02 * (_this.e10 * _this.e21 - _this.e11 * _this.e20) < 0; + } else { + tmp = true; + } + if(tmp) { + console.log("src/oimo/dynamics/constraint/joint/GenericJoint.hx:50:","[warning] joint basis must be right handed"); + } + let lb100; + let lb101; + let lb102; + let lb110; + let lb111; + let lb112; + let lb120; + let lb121; + let lb122; + let lb200; + let lb201; + let lb202; + let lb210; + let lb211; + let lb212; + let lb220; + let lb221; + let lb222; + let m = config.localBasis1; + lb100 = m.e00; + lb101 = m.e01; + lb102 = m.e02; + lb110 = m.e10; + lb111 = m.e11; + lb112 = m.e12; + lb120 = m.e20; + lb121 = m.e21; + lb122 = m.e22; + let m1 = config.localBasis2; + lb200 = m1.e00; + lb201 = m1.e01; + lb202 = m1.e02; + lb210 = m1.e10; + lb211 = m1.e11; + lb212 = m1.e12; + lb220 = m1.e20; + lb221 = m1.e21; + lb222 = m1.e22; + this._localBasisX1X = lb100; + this._localBasisX1Y = lb110; + this._localBasisX1Z = lb120; + this._localBasisY1X = lb101; + this._localBasisY1Y = lb111; + this._localBasisY1Z = lb121; + this._localBasisZ1X = lb102; + this._localBasisZ1Y = lb112; + this._localBasisZ1Z = lb122; + this._localBasisX2X = lb200; + this._localBasisX2Y = lb210; + this._localBasisX2Z = lb220; + this._localBasisY2X = lb201; + this._localBasisY2Y = lb211; + this._localBasisY2Z = lb221; + this._localBasisZ2X = lb202; + this._localBasisZ2Y = lb212; + this._localBasisZ2Z = lb222; + this._angleX = 0; + this._angleY = 0; + this._angleZ = 0; + this.translationX = 0; + this.translationY = 0; + this.translationZ = 0; + this.xSingular = false; + this.ySingular = false; + this.zSingular = false; + this._translLms = new Array(3); + this._translSds = new Array(3); + this._rotLms = new Array(3); + this._rotSds = new Array(3); + this._translLms[0] = config.translationalLimitMotors[0].clone(); + this._translLms[1] = config.translationalLimitMotors[1].clone(); + this._translLms[2] = config.translationalLimitMotors[2].clone(); + this._translSds[0] = config.translationalSpringDampers[0].clone(); + this._translSds[1] = config.translationalSpringDampers[1].clone(); + this._translSds[2] = config.translationalSpringDampers[2].clone(); + this._rotLms[0] = config.rotationalLimitMotors[0].clone(); + this._rotLms[1] = config.rotationalLimitMotors[1].clone(); + this._rotLms[2] = config.rotationalLimitMotors[2].clone(); + this._rotSds[0] = config.rotationalSpringDampers[0].clone(); + this._rotSds[1] = config.rotationalSpringDampers[1].clone(); + this._rotSds[2] = config.rotationalSpringDampers[2].clone(); + } + getInfo(info,timeStep,isPositionPart) { + let j; + let translMotorMass = 1 / (this._b1._invMass + this._b2._invMass); + let motorMassX = this.computeEffectiveInertiaMoment(this._axisXX,this._axisXY,this._axisXZ); + let motorMassY = this.computeEffectiveInertiaMoment(this._axisYX,this._axisYY,this._axisYZ); + let motorMassZ = this.computeEffectiveInertiaMoment(this._axisZX,this._axisZY,this._axisZZ); + if(this._translSds[0].frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translationX,this._translLms[0],translMotorMass,this._translSds[0],timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basisX1X; + j.lin1Y = this._basisX1Y; + j.lin1Z = this._basisX1Z; + j.lin2X = this._basisX1X; + j.lin2Y = this._basisX1Y; + j.lin2Z = this._basisX1Z; + j.ang1X = this._relativeAnchor1Y * this._basisX1Z - this._relativeAnchor1Z * this._basisX1Y; + j.ang1Y = this._relativeAnchor1Z * this._basisX1X - this._relativeAnchor1X * this._basisX1Z; + j.ang1Z = this._relativeAnchor1X * this._basisX1Y - this._relativeAnchor1Y * this._basisX1X; + j.ang2X = this._relativeAnchor2Y * this._basisX1Z - this._relativeAnchor2Z * this._basisX1Y; + j.ang2Y = this._relativeAnchor2Z * this._basisX1X - this._relativeAnchor2X * this._basisX1Z; + j.ang2Z = this._relativeAnchor2X * this._basisX1Y - this._relativeAnchor2Y * this._basisX1X; + } + if(this._translSds[1].frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[1]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translationY,this._translLms[1],translMotorMass,this._translSds[1],timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basisY1X; + j.lin1Y = this._basisY1Y; + j.lin1Z = this._basisY1Z; + j.lin2X = this._basisY1X; + j.lin2Y = this._basisY1Y; + j.lin2Z = this._basisY1Z; + j.ang1X = this._relativeAnchor1Y * this._basisY1Z - this._relativeAnchor1Z * this._basisY1Y; + j.ang1Y = this._relativeAnchor1Z * this._basisY1X - this._relativeAnchor1X * this._basisY1Z; + j.ang1Z = this._relativeAnchor1X * this._basisY1Y - this._relativeAnchor1Y * this._basisY1X; + j.ang2X = this._relativeAnchor2Y * this._basisY1Z - this._relativeAnchor2Z * this._basisY1Y; + j.ang2Y = this._relativeAnchor2Z * this._basisY1X - this._relativeAnchor2X * this._basisY1Z; + j.ang2Z = this._relativeAnchor2X * this._basisY1Y - this._relativeAnchor2Y * this._basisY1X; + } + if(this._translSds[2].frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[2]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translationZ,this._translLms[2],translMotorMass,this._translSds[2],timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basisZ1X; + j.lin1Y = this._basisZ1Y; + j.lin1Z = this._basisZ1Z; + j.lin2X = this._basisZ1X; + j.lin2Y = this._basisZ1Y; + j.lin2Z = this._basisZ1Z; + j.ang1X = this._relativeAnchor1Y * this._basisZ1Z - this._relativeAnchor1Z * this._basisZ1Y; + j.ang1Y = this._relativeAnchor1Z * this._basisZ1X - this._relativeAnchor1X * this._basisZ1Z; + j.ang1Z = this._relativeAnchor1X * this._basisZ1Y - this._relativeAnchor1Y * this._basisZ1X; + j.ang2X = this._relativeAnchor2Y * this._basisZ1Z - this._relativeAnchor2Z * this._basisZ1Y; + j.ang2Y = this._relativeAnchor2Z * this._basisZ1X - this._relativeAnchor2X * this._basisZ1Z; + j.ang2Z = this._relativeAnchor2X * this._basisZ1Y - this._relativeAnchor2Y * this._basisZ1X; + } + if(!this.xSingular && (this._rotSds[0].frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleX,this._rotLms[0],motorMassX,this._rotSds[0],timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisXX; + j.ang1Y = this._axisXY; + j.ang1Z = this._axisXZ; + j.ang2X = this._axisXX; + j.ang2Y = this._axisXY; + j.ang2Z = this._axisXZ; + } + if(!this.ySingular && (this._rotSds[1].frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[4]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleY,this._rotLms[1],motorMassY,this._rotSds[1],timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisYX; + j.ang1Y = this._axisYY; + j.ang1Z = this._axisYZ; + j.ang2X = this._axisYX; + j.ang2Y = this._axisYY; + j.ang2Z = this._axisYZ; + } + if(!this.zSingular && (this._rotSds[2].frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[5]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleZ,this._rotLms[2],motorMassZ,this._rotSds[2],timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisZX; + j.ang1Y = this._axisZY; + j.ang1Z = this._axisZZ; + j.ang2X = this._axisZX; + j.ang2Y = this._axisZY; + j.ang2Z = this._axisZZ; + } + } + _syncAnchors() { + super._syncAnchors(); + let angleAxisXX; + let angleAxisXY; + let angleAxisXZ; + let angleAxisYX; + let angleAxisYY; + let angleAxisYZ; + let angleAxisZX; + let angleAxisZY; + let angleAxisZZ; + angleAxisXX = this._basisX1X; + angleAxisXY = this._basisX1Y; + angleAxisXZ = this._basisX1Z; + angleAxisZX = this._basisZ2X; + angleAxisZY = this._basisZ2Y; + angleAxisZZ = this._basisZ2Z; + angleAxisYX = angleAxisZY * angleAxisXZ - angleAxisZZ * angleAxisXY; + angleAxisYY = angleAxisZZ * angleAxisXX - angleAxisZX * angleAxisXZ; + angleAxisYZ = angleAxisZX * angleAxisXY - angleAxisZY * angleAxisXX; + this._axisXX = angleAxisYY * angleAxisZZ - angleAxisYZ * angleAxisZY; + this._axisXY = angleAxisYZ * angleAxisZX - angleAxisYX * angleAxisZZ; + this._axisXZ = angleAxisYX * angleAxisZY - angleAxisYY * angleAxisZX; + this._axisYX = angleAxisYX; + this._axisYY = angleAxisYY; + this._axisYZ = angleAxisYZ; + this._axisZX = angleAxisXY * angleAxisYZ - angleAxisXZ * angleAxisYY; + this._axisZY = angleAxisXZ * angleAxisYX - angleAxisXX * angleAxisYZ; + this._axisZZ = angleAxisXX * angleAxisYY - angleAxisXY * angleAxisYX; + let l = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._axisXX *= l; + this._axisXY *= l; + this._axisXZ *= l; + let l1 = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ; + if(l1 > 0) { + l1 = 1 / Math.sqrt(l1); + } + this._axisYX *= l1; + this._axisYY *= l1; + this._axisYZ *= l1; + let l2 = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ; + if(l2 > 0) { + l2 = 1 / Math.sqrt(l2); + } + this._axisZX *= l2; + this._axisZY *= l2; + this._axisZZ *= l2; + this.xSingular = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ == 0; + this.ySingular = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ == 0; + this.zSingular = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ == 0; + let rot100; + let rot101; + let rot102; + let rot110; + let rot111; + let rot112; + let rot120; + let rot121; + let rot122; + let rot200; + let rot201; + let rot202; + let rot210; + let rot211; + let rot212; + let rot220; + let rot221; + let rot222; + rot100 = this._basisX1X; + rot101 = this._basisY1X; + rot102 = this._basisZ1X; + rot110 = this._basisX1Y; + rot111 = this._basisY1Y; + rot112 = this._basisZ1Y; + rot120 = this._basisX1Z; + rot121 = this._basisY1Z; + rot122 = this._basisZ1Z; + rot200 = this._basisX2X; + rot201 = this._basisY2X; + rot202 = this._basisZ2X; + rot210 = this._basisX2Y; + rot211 = this._basisY2Y; + rot212 = this._basisZ2Y; + rot220 = this._basisX2Z; + rot221 = this._basisY2Z; + rot222 = this._basisZ2Z; + let relRot00; + let relRot01; + let relRot02; + let relRot11; + let relRot12; + let relRot21; + let relRot22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__11; + let __tmp__12; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot100 * rot200 + rot110 * rot210 + rot120 * rot220; + __tmp__01 = rot100 * rot201 + rot110 * rot211 + rot120 * rot221; + __tmp__02 = rot100 * rot202 + rot110 * rot212 + rot120 * rot222; + __tmp__11 = rot101 * rot201 + rot111 * rot211 + rot121 * rot221; + __tmp__12 = rot101 * rot202 + rot111 * rot212 + rot121 * rot222; + __tmp__21 = rot102 * rot201 + rot112 * rot211 + rot122 * rot221; + __tmp__22 = rot102 * rot202 + rot112 * rot212 + rot122 * rot222; + relRot00 = __tmp__00; + relRot01 = __tmp__01; + relRot02 = __tmp__02; + relRot11 = __tmp__11; + relRot12 = __tmp__12; + relRot21 = __tmp__21; + relRot22 = __tmp__22; + let anglesX; + let anglesY; + let anglesZ; + let sy = relRot02; + if(sy <= -1) { + let xSubZ = Math.atan2(relRot21,relRot11); + anglesX = xSubZ * 0.5; + anglesY = -1.570796326794895; + anglesZ = -xSubZ * 0.5; + } else if(sy >= 1) { + let xAddZ = Math.atan2(relRot21,relRot11); + anglesX = xAddZ * 0.5; + anglesY = 1.570796326794895; + anglesZ = xAddZ * 0.5; + } else { + anglesX = Math.atan2(-relRot12,relRot22); + anglesY = Math.asin(sy); + anglesZ = Math.atan2(-relRot01,relRot00); + } + this._angleX = anglesX; + this._angleY = anglesY; + this._angleZ = anglesZ; + let anchorDiffX; + let anchorDiffY; + let anchorDiffZ; + anchorDiffX = this._anchor2X - this._anchor1X; + anchorDiffY = this._anchor2Y - this._anchor1Y; + anchorDiffZ = this._anchor2Z - this._anchor1Z; + this.translationX = anchorDiffX * this._basisX1X + anchorDiffY * this._basisX1Y + anchorDiffZ * this._basisX1Z; + this.translationY = anchorDiffX * this._basisY1X + anchorDiffY * this._basisY1Y + anchorDiffZ * this._basisY1Z; + this.translationZ = anchorDiffX * this._basisZ1X + anchorDiffY * this._basisZ1Y + anchorDiffZ * this._basisZ1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxisX() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxisY() { + let v = new oimo.common.Vec3(); + v.x = this._axisYX; + v.y = this._axisYY; + v.z = this._axisYZ; + return v; + } + getAxisZ() { + let v = new oimo.common.Vec3(); + v.x = this._basisZ2X; + v.y = this._basisZ2Y; + v.z = this._basisZ2Z; + return v; + } + getTranslationalSpringDampers() { + return this._translSds.slice(0); + } + getRotationalSpringDampers() { + return this._translSds.slice(0); + } + getTranslationalLimitMotors() { + return this._translLms.slice(0); + } + getRotationalLimitMotors() { + return this._rotLms.slice(0); + } + getAngles() { + return new oimo.common.Vec3(this._angleX,this._angleY,this._angleZ); + } + getTranslations() { + return new oimo.common.Vec3(this.translationX,this.translationY,this.translationZ); + } +} +oimo.dynamics.constraint.joint.GenericJointConfig = class oimo_dynamics_constraint_joint_GenericJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localBasis1 = new oimo.common.Mat3(); + this.localBasis2 = new oimo.common.Mat3(); + let _g = []; + _g.push(new oimo.dynamics.constraint.joint.TranslationalLimitMotor().setLimits(0,0)); + _g.push(new oimo.dynamics.constraint.joint.TranslationalLimitMotor().setLimits(0,0)); + _g.push(new oimo.dynamics.constraint.joint.TranslationalLimitMotor().setLimits(0,0)); + this.translationalLimitMotors = _g; + let _g1 = []; + _g1.push(new oimo.dynamics.constraint.joint.RotationalLimitMotor().setLimits(0,0)); + _g1.push(new oimo.dynamics.constraint.joint.RotationalLimitMotor().setLimits(0,0)); + _g1.push(new oimo.dynamics.constraint.joint.RotationalLimitMotor().setLimits(0,0)); + this.rotationalLimitMotors = _g1; + this.translationalSpringDampers = [new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper()]; + this.rotationalSpringDampers = [new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper()]; + } + init(rigidBody1,rigidBody2,worldAnchor,worldBasis1,worldBasis2) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let tf1 = rigidBody1._transform; + let tf2 = rigidBody2._transform; + let wb100; + let wb101; + let wb102; + let wb110; + let wb111; + let wb112; + let wb120; + let wb121; + let wb122; + let wb200; + let wb201; + let wb202; + let wb210; + let wb211; + let wb212; + let wb220; + let wb221; + let wb222; + let lb100; + let lb101; + let lb102; + let lb110; + let lb111; + let lb112; + let lb120; + let lb121; + let lb122; + let lb200; + let lb201; + let lb202; + let lb210; + let lb211; + let lb212; + let lb220; + let lb221; + let lb222; + wb100 = worldBasis1.e00; + wb101 = worldBasis1.e01; + wb102 = worldBasis1.e02; + wb110 = worldBasis1.e10; + wb111 = worldBasis1.e11; + wb112 = worldBasis1.e12; + wb120 = worldBasis1.e20; + wb121 = worldBasis1.e21; + wb122 = worldBasis1.e22; + wb200 = worldBasis2.e00; + wb201 = worldBasis2.e01; + wb202 = worldBasis2.e02; + wb210 = worldBasis2.e10; + wb211 = worldBasis2.e11; + wb212 = worldBasis2.e12; + wb220 = worldBasis2.e20; + wb221 = worldBasis2.e21; + wb222 = worldBasis2.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * wb100 + tf1._rotation10 * wb110 + tf1._rotation20 * wb120; + __tmp__01 = tf1._rotation00 * wb101 + tf1._rotation10 * wb111 + tf1._rotation20 * wb121; + __tmp__02 = tf1._rotation00 * wb102 + tf1._rotation10 * wb112 + tf1._rotation20 * wb122; + __tmp__10 = tf1._rotation01 * wb100 + tf1._rotation11 * wb110 + tf1._rotation21 * wb120; + __tmp__11 = tf1._rotation01 * wb101 + tf1._rotation11 * wb111 + tf1._rotation21 * wb121; + __tmp__12 = tf1._rotation01 * wb102 + tf1._rotation11 * wb112 + tf1._rotation21 * wb122; + __tmp__20 = tf1._rotation02 * wb100 + tf1._rotation12 * wb110 + tf1._rotation22 * wb120; + __tmp__21 = tf1._rotation02 * wb101 + tf1._rotation12 * wb111 + tf1._rotation22 * wb121; + __tmp__22 = tf1._rotation02 * wb102 + tf1._rotation12 * wb112 + tf1._rotation22 * wb122; + lb100 = __tmp__00; + lb101 = __tmp__01; + lb102 = __tmp__02; + lb110 = __tmp__10; + lb111 = __tmp__11; + lb112 = __tmp__12; + lb120 = __tmp__20; + lb121 = __tmp__21; + lb122 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * wb200 + tf2._rotation10 * wb210 + tf2._rotation20 * wb220; + __tmp__011 = tf2._rotation00 * wb201 + tf2._rotation10 * wb211 + tf2._rotation20 * wb221; + __tmp__021 = tf2._rotation00 * wb202 + tf2._rotation10 * wb212 + tf2._rotation20 * wb222; + __tmp__101 = tf2._rotation01 * wb200 + tf2._rotation11 * wb210 + tf2._rotation21 * wb220; + __tmp__111 = tf2._rotation01 * wb201 + tf2._rotation11 * wb211 + tf2._rotation21 * wb221; + __tmp__121 = tf2._rotation01 * wb202 + tf2._rotation11 * wb212 + tf2._rotation21 * wb222; + __tmp__201 = tf2._rotation02 * wb200 + tf2._rotation12 * wb210 + tf2._rotation22 * wb220; + __tmp__211 = tf2._rotation02 * wb201 + tf2._rotation12 * wb211 + tf2._rotation22 * wb221; + __tmp__221 = tf2._rotation02 * wb202 + tf2._rotation12 * wb212 + tf2._rotation22 * wb222; + lb200 = __tmp__001; + lb201 = __tmp__011; + lb202 = __tmp__021; + lb210 = __tmp__101; + lb211 = __tmp__111; + lb212 = __tmp__121; + lb220 = __tmp__201; + lb221 = __tmp__211; + lb222 = __tmp__221; + let m = this.localBasis1; + m.e00 = lb100; + m.e01 = lb101; + m.e02 = lb102; + m.e10 = lb110; + m.e11 = lb111; + m.e12 = lb112; + m.e20 = lb120; + m.e21 = lb121; + m.e22 = lb122; + let m1 = this.localBasis2; + m1.e00 = lb200; + m1.e01 = lb201; + m1.e02 = lb202; + m1.e10 = lb210; + m1.e11 = lb211; + m1.e12 = lb212; + m1.e20 = lb220; + m1.e21 = lb221; + m1.e22 = lb222; + return this; + } +} +oimo.dynamics.constraint.joint.JointImpulse = class oimo_dynamics_constraint_joint_JointImpulse { + constructor() { + this.impulse = 0; + this.impulseM = 0; + this.impulseP = 0; + } +} +oimo.dynamics.constraint.joint.JointLink = class oimo_dynamics_constraint_joint_JointLink { + constructor(joint) { + this._joint = joint; + } + getContact() { + return this._joint; + } + getOther() { + return this._other; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.constraint.joint.JointMacro = class oimo_dynamics_constraint_joint_JointMacro { +} +oimo.dynamics.constraint.joint.JointType = class oimo_dynamics_constraint_joint_JointType { +} +oimo.dynamics.constraint.joint.PrismaticJoint = class oimo_dynamics_constraint_joint_PrismaticJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.PRISMATIC); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisX2X = v1.x; + this._localBasisX2Y = v1.y; + this._localBasisX2Z = v1.z; + this.buildLocalBasesFromX(); + this._basis = new oimo.dynamics.constraint.joint.BasisTracker(this); + this.translation = 0; + this.linearErrorY = 0; + this.linearErrorZ = 0; + this.angularErrorX = 0; + this.angularErrorY = 0; + this.angularErrorZ = 0; + this._sd = config.springDamper.clone(); + this._lm = config.limitMotor.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linRhsY = this.linearErrorY * erp; + let linRhsZ = this.linearErrorZ * erp; + let angRhsX = this.angularErrorX * erp; + let angRhsY = this.angularErrorY * erp; + let angRhsZ = this.angularErrorZ * erp; + let j; + if(this._sd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translation,this._lm,1 / (this._b1._invMass + this._b2._invMass),this._sd,timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basis.xX; + j.lin1Y = this._basis.xY; + j.lin1Z = this._basis.xZ; + j.lin2X = this._basis.xX; + j.lin2Y = this._basis.xY; + j.lin2Z = this._basis.xZ; + j.ang1X = this._relativeAnchor1Y * this._basis.xZ - this._relativeAnchor1Z * this._basis.xY; + j.ang1Y = this._relativeAnchor1Z * this._basis.xX - this._relativeAnchor1X * this._basis.xZ; + j.ang1Z = this._relativeAnchor1X * this._basis.xY - this._relativeAnchor1Y * this._basis.xX; + j.ang2X = this._relativeAnchor2Y * this._basis.xZ - this._relativeAnchor2Z * this._basis.xY; + j.ang2Y = this._relativeAnchor2Z * this._basis.xX - this._relativeAnchor2X * this._basis.xZ; + j.ang2Z = this._relativeAnchor2X * this._basis.xY - this._relativeAnchor2Y * this._basis.xX; + } + let impulse = this._impulses[1]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linRhsY; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + j = row.jacobian; + j.lin1X = this._basis.yX; + j.lin1Y = this._basis.yY; + j.lin1Z = this._basis.yZ; + j.lin2X = this._basis.yX; + j.lin2Y = this._basis.yY; + j.lin2Z = this._basis.yZ; + j.ang1X = this._relativeAnchor1Y * this._basis.yZ - this._relativeAnchor1Z * this._basis.yY; + j.ang1Y = this._relativeAnchor1Z * this._basis.yX - this._relativeAnchor1X * this._basis.yZ; + j.ang1Z = this._relativeAnchor1X * this._basis.yY - this._relativeAnchor1Y * this._basis.yX; + j.ang2X = this._relativeAnchor2Y * this._basis.yZ - this._relativeAnchor2Z * this._basis.yY; + j.ang2Y = this._relativeAnchor2Z * this._basis.yX - this._relativeAnchor2X * this._basis.yZ; + j.ang2Z = this._relativeAnchor2X * this._basis.yY - this._relativeAnchor2Y * this._basis.yX; + let impulse1 = this._impulses[2]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linRhsZ; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = this._basis.zX; + j.lin1Y = this._basis.zY; + j.lin1Z = this._basis.zZ; + j.lin2X = this._basis.zX; + j.lin2Y = this._basis.zY; + j.lin2Z = this._basis.zZ; + j.ang1X = this._relativeAnchor1Y * this._basis.zZ - this._relativeAnchor1Z * this._basis.zY; + j.ang1Y = this._relativeAnchor1Z * this._basis.zX - this._relativeAnchor1X * this._basis.zZ; + j.ang1Z = this._relativeAnchor1X * this._basis.zY - this._relativeAnchor1Y * this._basis.zX; + j.ang2X = this._relativeAnchor2Y * this._basis.zZ - this._relativeAnchor2Z * this._basis.zY; + j.ang2Y = this._relativeAnchor2Z * this._basis.zX - this._relativeAnchor2X * this._basis.zZ; + j.ang2Z = this._relativeAnchor2X * this._basis.zY - this._relativeAnchor2Y * this._basis.zX; + let impulse2 = this._impulses[3]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = angRhsX; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.ang1X = 1; + j.ang1Y = 0; + j.ang1Z = 0; + j.ang2X = 1; + j.ang2Y = 0; + j.ang2Z = 0; + let impulse3 = this._impulses[4]; + let row3 = info.rows[info.numRows++]; + let _this3 = row3.jacobian; + _this3.lin1X = 0; + _this3.lin1Y = 0; + _this3.lin1Z = 0; + _this3.lin2X = 0; + _this3.lin2Y = 0; + _this3.lin2Z = 0; + _this3.ang1X = 0; + _this3.ang1Y = 0; + _this3.ang1Z = 0; + _this3.ang2X = 0; + _this3.ang2Y = 0; + _this3.ang2Z = 0; + row3.rhs = 0; + row3.cfm = 0; + row3.minImpulse = 0; + row3.maxImpulse = 0; + row3.motorSpeed = 0; + row3.motorMaxImpulse = 0; + row3.impulse = null; + row3.impulse = impulse3; + row3.rhs = angRhsY; + row3.cfm = 0; + row3.minImpulse = -1e65536; + row3.maxImpulse = 1e65536; + j = row3.jacobian; + j.ang1X = 0; + j.ang1Y = 1; + j.ang1Z = 0; + j.ang2X = 0; + j.ang2Y = 1; + j.ang2Z = 0; + let impulse4 = this._impulses[5]; + let row4 = info.rows[info.numRows++]; + let _this4 = row4.jacobian; + _this4.lin1X = 0; + _this4.lin1Y = 0; + _this4.lin1Z = 0; + _this4.lin2X = 0; + _this4.lin2Y = 0; + _this4.lin2Z = 0; + _this4.ang1X = 0; + _this4.ang1Y = 0; + _this4.ang1Z = 0; + _this4.ang2X = 0; + _this4.ang2Y = 0; + _this4.ang2Z = 0; + row4.rhs = 0; + row4.cfm = 0; + row4.minImpulse = 0; + row4.maxImpulse = 0; + row4.motorSpeed = 0; + row4.motorMaxImpulse = 0; + row4.impulse = null; + row4.impulse = impulse4; + row4.rhs = angRhsZ; + row4.cfm = 0; + row4.minImpulse = -1e65536; + row4.maxImpulse = 1e65536; + j = row4.jacobian; + j.ang1X = 0; + j.ang1Y = 0; + j.ang1Z = 1; + j.ang2X = 0; + j.ang2Y = 0; + j.ang2Z = 1; + } + _syncAnchors() { + super._syncAnchors(); + let _this = this._basis; + let invM1 = _this.joint._b1._invMass; + let invM2 = _this.joint._b2._invMass; + let qX; + let qY; + let qZ; + let qW; + let idQX; + let idQY; + let idQZ; + let idQW; + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let newXX; + let newXY; + let newXZ; + let newYX; + let newYY; + let newYZ; + let newZX; + let newZY; + let newZZ; + let prevXX; + let prevXY; + let prevXZ; + let prevYX; + let prevYY; + let prevYZ; + let d = _this.joint._basisX1X * _this.joint._basisX2X + _this.joint._basisX1Y * _this.joint._basisX2Y + _this.joint._basisX1Z * _this.joint._basisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = _this.joint._basisX1X; + let y1 = _this.joint._basisX1Y; + let z1 = _this.joint._basisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + qX = vX; + qY = vY; + qZ = vZ; + qW = 0; + } else { + let cX; + let cY; + let cZ; + cX = _this.joint._basisX1Y * _this.joint._basisX2Z - _this.joint._basisX1Z * _this.joint._basisX2Y; + cY = _this.joint._basisX1Z * _this.joint._basisX2X - _this.joint._basisX1X * _this.joint._basisX2Z; + cZ = _this.joint._basisX1X * _this.joint._basisX2Y - _this.joint._basisX1Y * _this.joint._basisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + qX = cX; + qY = cY; + qZ = cZ; + qW = w; + } + idQX = 0; + idQY = 0; + idQZ = 0; + idQW = 1; + let q1X; + let q1Y; + let q1Z; + let q1W; + let q2X; + let q2Y; + let q2Z; + let q2W; + q1X = idQX; + q1Y = idQY; + q1Z = idQZ; + q1W = idQW; + q2X = qX; + q2Y = qY; + q2Z = qZ; + q2W = qW; + let d1 = q1X * q2X + q1Y * q2Y + q1Z * q2Z + q1W * q2W; + if(d1 < 0) { + d1 = -d1; + q2X = -q2X; + q2Y = -q2Y; + q2Z = -q2Z; + q2W = -q2W; + } + if(d1 > 0.999999) { + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = q2X - q1X; + dqY = q2Y - q1Y; + dqZ = q2Z - q1Z; + dqW = q2W - q1W; + q2X = q1X + dqX * (invM1 / (invM1 + invM2)); + q2Y = q1Y + dqY * (invM1 / (invM1 + invM2)); + q2Z = q1Z + dqZ * (invM1 / (invM1 + invM2)); + q2W = q1W + dqW * (invM1 / (invM1 + invM2)); + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + slerpQX = q2X * l; + slerpQY = q2Y * l; + slerpQZ = q2Z * l; + slerpQW = q2W * l; + } else { + let theta = invM1 / (invM1 + invM2) * Math.acos(d1); + q2X += q1X * -d1; + q2Y += q1Y * -d1; + q2Z += q1Z * -d1; + q2W += q1W * -d1; + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + q2X *= l; + q2Y *= l; + q2Z *= l; + q2W *= l; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + q1X *= cos; + q1Y *= cos; + q1Z *= cos; + q1W *= cos; + slerpQX = q1X + q2X * sin; + slerpQY = q1Y + q2Y * sin; + slerpQZ = q1Z + q2Z * sin; + slerpQW = q1W + q2W * sin; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * _this.joint._basisX1X + slerpM01 * _this.joint._basisX1Y + slerpM02 * _this.joint._basisX1Z; + __tmp__Y = slerpM10 * _this.joint._basisX1X + slerpM11 * _this.joint._basisX1Y + slerpM12 * _this.joint._basisX1Z; + __tmp__Z = slerpM20 * _this.joint._basisX1X + slerpM21 * _this.joint._basisX1Y + slerpM22 * _this.joint._basisX1Z; + newXX = __tmp__X; + newXY = __tmp__Y; + newXZ = __tmp__Z; + prevXX = _this.xX; + prevXY = _this.xY; + prevXZ = _this.xZ; + prevYX = _this.yX; + prevYY = _this.yY; + prevYZ = _this.yZ; + let d2 = prevXX * newXX + prevXY * newXY + prevXZ * newXZ; + if(d2 < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = prevXX; + let y1 = prevXY; + let z1 = prevXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = prevXY * newXZ - prevXZ * newXY; + cY = prevXZ * newXX - prevXX * newXZ; + cZ = prevXX * newXY - prevXY * newXX; + let w = Math.sqrt((1 + d2) * 0.5); + d2 = 0.5 / w; + cX *= d2; + cY *= d2; + cZ *= d2; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x1 = slerpQX; + let y1 = slerpQY; + let z1 = slerpQZ; + let w1 = slerpQW; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + slerpM00 = 1 - yy1 - zz1; + slerpM01 = xy1 - wz1; + slerpM02 = xz1 + wy1; + slerpM10 = xy1 + wz1; + slerpM11 = 1 - xx1 - zz1; + slerpM12 = yz1 - wx1; + slerpM20 = xz1 - wy1; + slerpM21 = yz1 + wx1; + slerpM22 = 1 - xx1 - yy1; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * prevYX + slerpM01 * prevYY + slerpM02 * prevYZ; + __tmp__Y1 = slerpM10 * prevYX + slerpM11 * prevYY + slerpM12 * prevYZ; + __tmp__Z1 = slerpM20 * prevYX + slerpM21 * prevYY + slerpM22 * prevYZ; + newYX = __tmp__X1; + newYY = __tmp__Y1; + newYZ = __tmp__Z1; + newZX = newXY * newYZ - newXZ * newYY; + newZY = newXZ * newYX - newXX * newYZ; + newZZ = newXX * newYY - newXY * newYX; + if(newZX * newZX + newZY * newZY + newZZ * newZZ > 1e-6) { + let l = newZX * newZX + newZY * newZY + newZZ * newZZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + newZX *= l; + newZY *= l; + newZZ *= l; + } else { + let x1 = newXX; + let y1 = newXY; + let z1 = newXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + newZX = 0; + newZY = z1 * d; + newZZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + newZX = -z1 * d; + newZY = 0; + newZZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } + newYX = newZY * newXZ - newZZ * newXY; + newYY = newZZ * newXX - newZX * newXZ; + newYZ = newZX * newXY - newZY * newXX; + _this.xX = newXX; + _this.xY = newXY; + _this.xZ = newXZ; + _this.yX = newYX; + _this.yY = newYY; + _this.yZ = newYZ; + _this.zX = newZX; + _this.zY = newZY; + _this.zZ = newZZ; + let rot100; + let rot101; + let rot102; + let rot110; + let rot111; + let rot112; + let rot120; + let rot121; + let rot122; + let rot200; + let rot201; + let rot202; + let rot210; + let rot211; + let rot212; + let rot220; + let rot221; + let rot222; + rot100 = this._basisX1X; + rot101 = this._basisY1X; + rot102 = this._basisZ1X; + rot110 = this._basisX1Y; + rot111 = this._basisY1Y; + rot112 = this._basisZ1Y; + rot120 = this._basisX1Z; + rot121 = this._basisY1Z; + rot122 = this._basisZ1Z; + rot200 = this._basisX2X; + rot201 = this._basisY2X; + rot202 = this._basisZ2X; + rot210 = this._basisX2Y; + rot211 = this._basisY2Y; + rot212 = this._basisZ2Y; + rot220 = this._basisX2Z; + rot221 = this._basisY2Z; + rot222 = this._basisZ2Z; + let relRot00; + let relRot01; + let relRot02; + let relRot10; + let relRot11; + let relRot12; + let relRot20; + let relRot21; + let relRot22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot200 * rot100 + rot201 * rot101 + rot202 * rot102; + __tmp__01 = rot200 * rot110 + rot201 * rot111 + rot202 * rot112; + __tmp__02 = rot200 * rot120 + rot201 * rot121 + rot202 * rot122; + __tmp__10 = rot210 * rot100 + rot211 * rot101 + rot212 * rot102; + __tmp__11 = rot210 * rot110 + rot211 * rot111 + rot212 * rot112; + __tmp__12 = rot210 * rot120 + rot211 * rot121 + rot212 * rot122; + __tmp__20 = rot220 * rot100 + rot221 * rot101 + rot222 * rot102; + __tmp__21 = rot220 * rot110 + rot221 * rot111 + rot222 * rot112; + __tmp__22 = rot220 * rot120 + rot221 * rot121 + rot222 * rot122; + relRot00 = __tmp__00; + relRot01 = __tmp__01; + relRot02 = __tmp__02; + relRot10 = __tmp__10; + relRot11 = __tmp__11; + relRot12 = __tmp__12; + relRot20 = __tmp__20; + relRot21 = __tmp__21; + relRot22 = __tmp__22; + let relQX; + let relQY; + let relQZ; + let relQW; + let e00 = relRot00; + let e11 = relRot11; + let e22 = relRot22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + relQW = 0.5 * s; + s = 0.5 / s; + relQX = (relRot21 - relRot12) * s; + relQY = (relRot02 - relRot20) * s; + relQZ = (relRot10 - relRot01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + relQX = 0.5 * s; + s = 0.5 / s; + relQY = (relRot01 + relRot10) * s; + relQZ = (relRot02 + relRot20) * s; + relQW = (relRot21 - relRot12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + relQZ = 0.5 * s; + s = 0.5 / s; + relQX = (relRot02 + relRot20) * s; + relQY = (relRot12 + relRot21) * s; + relQW = (relRot10 - relRot01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + relQY = 0.5 * s; + s = 0.5 / s; + relQX = (relRot01 + relRot10) * s; + relQZ = (relRot12 + relRot21) * s; + relQW = (relRot02 - relRot20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + relQZ = 0.5 * s; + s = 0.5 / s; + relQX = (relRot02 + relRot20) * s; + relQY = (relRot12 + relRot21) * s; + relQW = (relRot10 - relRot01) * s; + } + let cosHalfTheta = relQW; + let theta = (cosHalfTheta <= -1 ? 3.14159265358979 : cosHalfTheta >= 1 ? 0 : Math.acos(cosHalfTheta)) * 2; + this.angularErrorX = relQX; + this.angularErrorY = relQY; + this.angularErrorZ = relQZ; + let l = this.angularErrorX * this.angularErrorX + this.angularErrorY * this.angularErrorY + this.angularErrorZ * this.angularErrorZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this.angularErrorX *= l; + this.angularErrorY *= l; + this.angularErrorZ *= l; + this.angularErrorX *= theta; + this.angularErrorY *= theta; + this.angularErrorZ *= theta; + let anchorDiffX; + let anchorDiffY; + let anchorDiffZ; + anchorDiffX = this._anchor2X - this._anchor1X; + anchorDiffY = this._anchor2Y - this._anchor1Y; + anchorDiffZ = this._anchor2Z - this._anchor1Z; + this.translation = anchorDiffX * this._basis.xX + anchorDiffY * this._basis.xY + anchorDiffZ * this._basis.xZ; + this.linearErrorY = anchorDiffX * this._basis.yX + anchorDiffY * this._basis.yY + anchorDiffZ * this._basis.yZ; + this.linearErrorZ = anchorDiffX * this._basis.zX + anchorDiffY * this._basis.zY + anchorDiffZ * this._basis.zZ; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getSpringDamper() { + return this._sd; + } + getLimitMotor() { + return this._lm; + } + getTranslation() { + return this.translation; + } +} +oimo.dynamics.constraint.joint.PrismaticJointConfig = class oimo_dynamics_constraint_joint_PrismaticJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.limitMotor = new oimo.dynamics.constraint.joint.TranslationalLimitMotor(); + this.springDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis.x; + vY = worldAxis.y; + vZ = worldAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis.x; + vY1 = worldAxis.y; + vZ1 = worldAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +oimo.dynamics.constraint.joint.RagdollJoint = class oimo_dynamics_constraint_joint_RagdollJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.RAGDOLL); + let v = config.localTwistAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localSwingAxis1; + this._localBasisY1X = v1.x; + this._localBasisY1Y = v1.y; + this._localBasisY1Z = v1.z; + let v2 = config.localTwistAxis2; + this._localBasisX2X = v2.x; + this._localBasisX2Y = v2.y; + this._localBasisX2Z = v2.z; + this.buildLocalBasesFromXY1X2(); + this._twistSd = config.twistSpringDamper.clone(); + this._twistLm = config.twistLimitMotor.clone(); + this._swingSd = config.swingSpringDamper.clone(); + this._maxSwingAngle1 = config.maxSwingAngle1; + this._maxSwingAngle2 = config.maxSwingAngle2; + if(this._maxSwingAngle1 < oimo.common.Setting.minRagdollMaxSwingAngle) { + this._maxSwingAngle1 = oimo.common.Setting.minRagdollMaxSwingAngle; + } + if(this._maxSwingAngle2 < oimo.common.Setting.minRagdollMaxSwingAngle) { + this._maxSwingAngle2 = oimo.common.Setting.minRagdollMaxSwingAngle; + } + this.dummySwingLm = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.dummySwingLm.lowerLimit = -1; + this.dummySwingLm.upperLimit = 0; + this._swingAngle = 0; + this._twistAngle = 0; + this.swingError = 0; + this.swingAxisX = 0; + this.swingAxisY = 0; + this.swingAxisZ = 0; + this.twistAxisX = 0; + this.twistAxisY = 0; + this.twistAxisZ = 0; + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = this.linearErrorX * erp; + linearRhsY = this.linearErrorY * erp; + linearRhsZ = this.linearErrorZ * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let swingMass = this.computeEffectiveInertiaMoment(this.swingAxisX,this.swingAxisY,this.swingAxisZ); + let twistMass = this.computeEffectiveInertiaMoment(this._basisX2X,this._basisX2Y,this._basisX2Z); + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + if(this.swingError > 0 && (this._swingSd.frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this.swingError,this.dummySwingLm,swingMass,this._swingSd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this.swingAxisX; + j.ang1Y = this.swingAxisY; + j.ang1Z = this.swingAxisZ; + j.ang2X = this.swingAxisX; + j.ang2Y = this.swingAxisY; + j.ang2Z = this.swingAxisZ; + } + if(this._twistSd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[4]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._twistAngle,this._twistLm,twistMass,this._twistSd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this.twistAxisX; + j.ang1Y = this.twistAxisY; + j.ang1Z = this.twistAxisZ; + j.ang2X = this.twistAxisX; + j.ang2Y = this.twistAxisY; + j.ang2Z = this.twistAxisZ; + } + } + _syncAnchors() { + super._syncAnchors(); + let axis1X; + let axis1Y; + let axis1Z; + let axis2X; + let axis2Y; + let axis2Z; + axis1X = this._basisX1X; + axis1Y = this._basisX1Y; + axis1Z = this._basisX1Z; + axis2X = this._basisX2X; + axis2Y = this._basisX2Y; + axis2Z = this._basisX2Z; + let basis1Mat00; + let basis1Mat01; + let basis1Mat02; + let basis1Mat10; + let basis1Mat11; + let basis1Mat12; + let basis1Mat20; + let basis1Mat21; + let basis1Mat22; + basis1Mat00 = this._basisX1X; + basis1Mat01 = this._basisY1X; + basis1Mat02 = this._basisZ1X; + basis1Mat10 = this._basisX1Y; + basis1Mat11 = this._basisY1Y; + basis1Mat12 = this._basisZ1Y; + basis1Mat20 = this._basisX1Z; + basis1Mat21 = this._basisY1Z; + basis1Mat22 = this._basisZ1Z; + let swingQX; + let swingQY; + let swingQZ; + let swingQW; + let swingM00; + let swingM01; + let swingM02; + let swingM10; + let swingM11; + let swingM12; + let swingM20; + let swingM21; + let swingM22; + let swingVX; + let swingVY; + let swingVZ; + let d = axis1X * axis2X + axis1Y * axis2Y + axis1Z * axis2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = axis1X; + let y1 = axis1Y; + let z1 = axis1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + swingQX = vX; + swingQY = vY; + swingQZ = vZ; + swingQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = axis1Y * axis2Z - axis1Z * axis2Y; + cY = axis1Z * axis2X - axis1X * axis2Z; + cZ = axis1X * axis2Y - axis1Y * axis2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + swingQX = cX; + swingQY = cY; + swingQZ = cZ; + swingQW = w; + } + let x = swingQX; + let y = swingQY; + let z = swingQZ; + let w = swingQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + swingM00 = 1 - yy - zz; + swingM01 = xy - wz; + swingM02 = xz + wy; + swingM10 = xy + wz; + swingM11 = 1 - xx - zz; + swingM12 = yz - wx; + swingM20 = xz - wy; + swingM21 = yz + wx; + swingM22 = 1 - xx - yy; + this._swingAngle = (swingQW <= -1 ? 3.14159265358979 : swingQW >= 1 ? 0 : Math.acos(swingQW)) * 2; + swingVX = swingQX; + swingVY = swingQY; + swingVZ = swingQZ; + let basisY2In1X; + let basisY2In1Y; + let basisY2In1Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = swingM00 * this._basisY2X + swingM10 * this._basisY2Y + swingM20 * this._basisY2Z; + __tmp__Y = swingM01 * this._basisY2X + swingM11 * this._basisY2Y + swingM21 * this._basisY2Z; + __tmp__Z = swingM02 * this._basisY2X + swingM12 * this._basisY2Y + swingM22 * this._basisY2Z; + basisY2In1X = __tmp__X; + basisY2In1Y = __tmp__Y; + basisY2In1Z = __tmp__Z; + this._twistAngle = Math.atan2(this._basisZ1X * basisY2In1X + this._basisZ1Y * basisY2In1Y + this._basisZ1Z * basisY2In1Z,this._basisY1X * basisY2In1X + this._basisY1Y * basisY2In1Y + this._basisY1Z * basisY2In1Z); + this.twistAxisX = this._basisX1X + this._basisX2X; + this.twistAxisY = this._basisX1Y + this._basisX2Y; + this.twistAxisZ = this._basisX1Z + this._basisX2Z; + let l = this.twistAxisX * this.twistAxisX + this.twistAxisY * this.twistAxisY + this.twistAxisZ * this.twistAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this.twistAxisX *= l; + this.twistAxisY *= l; + this.twistAxisZ *= l; + let invLen = Math.sqrt(swingVX * swingVX + swingVY * swingVY + swingVZ * swingVZ); + if(invLen > 0) { + invLen = 1 / invLen; + } + swingVX *= invLen * this._swingAngle; + swingVY *= invLen * this._swingAngle; + swingVZ *= invLen * this._swingAngle; + + let __tmp__Y1; + let __tmp__Z1; + + __tmp__Y1 = basis1Mat01 * swingVX + basis1Mat11 * swingVY + basis1Mat21 * swingVZ; + __tmp__Z1 = basis1Mat02 * swingVX + basis1Mat12 * swingVY + basis1Mat22 * swingVZ; + + swingVY = __tmp__Y1; + swingVZ = __tmp__Z1; + let x1 = swingVY; + let y1 = swingVZ; + let a = this._maxSwingAngle1; + let b = this._maxSwingAngle2; + let invA2 = 1 / (a * a); + let invB2 = 1 / (b * b); + let w1 = x1 * x1 * invA2 + y1 * y1 * invB2; + if(w1 == 0) { + this.swingAxisX = 0; + this.swingAxisY = 0; + this.swingAxisZ = 0; + this.swingError = 0; + } else { + let t = Math.sqrt(1 / w1); + let x0 = x1 * t; + let y0 = y1 * t; + let nx = x0 * invA2; + let ny = y0 * invB2; + invLen = 1 / Math.sqrt(nx * nx + ny * ny); + nx *= invLen; + ny *= invLen; + let depth = (x1 - x0) * nx + (y1 - y0) * ny; + if(depth > 0) { + this.swingError = depth; + this.swingAxisX = 0; + this.swingAxisY = nx; + this.swingAxisZ = ny; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = basis1Mat00 * this.swingAxisX + basis1Mat01 * this.swingAxisY + basis1Mat02 * this.swingAxisZ; + __tmp__Y = basis1Mat10 * this.swingAxisX + basis1Mat11 * this.swingAxisY + basis1Mat12 * this.swingAxisZ; + __tmp__Z = basis1Mat20 * this.swingAxisX + basis1Mat21 * this.swingAxisY + basis1Mat22 * this.swingAxisZ; + this.swingAxisX = __tmp__X; + this.swingAxisY = __tmp__Y; + this.swingAxisZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = swingM00 * this.swingAxisX + swingM01 * this.swingAxisY + swingM02 * this.swingAxisZ; + __tmp__Y1 = swingM10 * this.swingAxisX + swingM11 * this.swingAxisY + swingM12 * this.swingAxisZ; + __tmp__Z1 = swingM20 * this.swingAxisX + swingM21 * this.swingAxisY + swingM22 * this.swingAxisZ; + this.swingAxisX = __tmp__X1; + this.swingAxisY = __tmp__Y1; + this.swingAxisZ = __tmp__Z1; + } else { + this.swingError = 0; + } + } + this.linearErrorX = this._anchor2X - this._anchor1X; + this.linearErrorY = this._anchor2Y - this._anchor1Y; + this.linearErrorZ = this._anchor2Z - this._anchor1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getTwistSpringDamper() { + return this._twistSd; + } + getTwistLimitMotor() { + return this._twistLm; + } + getSwingSpringDamper() { + return this._swingSd; + } + getSwingAxis() { + let v = new oimo.common.Vec3(); + v.x = this.swingAxisX; + v.y = this.swingAxisY; + v.z = this.swingAxisZ; + return v; + } + getSwingAxisTo(axis) { + axis.x = this.swingAxisX; + axis.y = this.swingAxisY; + axis.z = this.swingAxisZ; + } + getSwingAngle() { + return this._swingAngle; + } + getTwistAngle() { + return this._twistAngle; + } +} +oimo.dynamics.constraint.joint.RagdollJointConfig = class oimo_dynamics_constraint_joint_RagdollJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localTwistAxis1 = new oimo.common.Vec3(1,0,0); + this.localTwistAxis2 = new oimo.common.Vec3(1,0,0); + this.localSwingAxis1 = new oimo.common.Vec3(0,1,0); + this.twistSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.swingSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.twistLimitMotor = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.maxSwingAngle1 = 3.14159265358979; + this.maxSwingAngle2 = 3.14159265358979; + } + init(rigidBody1,rigidBody2,worldAnchor,worldTwistAxis,worldSwingAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localTwistAxis1; + let vX; + let vY; + let vZ; + vX = worldTwistAxis.x; + vY = worldTwistAxis.y; + vZ = worldTwistAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localTwistAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldTwistAxis.x; + vY1 = worldTwistAxis.y; + vZ1 = worldTwistAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + let localVector2 = this.localSwingAxis1; + let vX2; + let vY2; + let vZ2; + vX2 = worldSwingAxis.x; + vY2 = worldSwingAxis.y; + vZ2 = worldSwingAxis.z; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = rigidBody1._transform._rotation00 * vX2 + rigidBody1._transform._rotation10 * vY2 + rigidBody1._transform._rotation20 * vZ2; + __tmp__Y2 = rigidBody1._transform._rotation01 * vX2 + rigidBody1._transform._rotation11 * vY2 + rigidBody1._transform._rotation21 * vZ2; + __tmp__Z2 = rigidBody1._transform._rotation02 * vX2 + rigidBody1._transform._rotation12 * vY2 + rigidBody1._transform._rotation22 * vZ2; + vX2 = __tmp__X2; + vY2 = __tmp__Y2; + vZ2 = __tmp__Z2; + localVector2.x = vX2; + localVector2.y = vY2; + localVector2.z = vZ2; + return this; + } +} +oimo.dynamics.constraint.joint.RevoluteJoint = class oimo_dynamics_constraint_joint_RevoluteJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,1); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisX2X = v1.x; + this._localBasisX2Y = v1.y; + this._localBasisX2Z = v1.z; + this.buildLocalBasesFromX(); + this.angle = 0; + this.angularErrorY = 0; + this.angularErrorZ = 0; + this._basis = new oimo.dynamics.constraint.joint.BasisTracker(this); + this._sd = config.springDamper.clone(); + this._lm = config.limitMotor.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = this.linearErrorX * erp; + linearRhsY = this.linearErrorY * erp; + linearRhsZ = this.linearErrorZ * erp; + let angRhsY = this.angularErrorY * erp; + let angRhsZ = this.angularErrorZ * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let motorMass = this.computeEffectiveInertiaMoment(this._basis.xX,this._basis.xY,this._basis.xZ); + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + if(this._sd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this.angle,this._lm,motorMass,this._sd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._basis.xX; + j.ang1Y = this._basis.xY; + j.ang1Z = this._basis.xZ; + j.ang2X = this._basis.xX; + j.ang2Y = this._basis.xY; + j.ang2Z = this._basis.xZ; + } + let impulse3 = this._impulses[4]; + let row3 = info.rows[info.numRows++]; + let _this3 = row3.jacobian; + _this3.lin1X = 0; + _this3.lin1Y = 0; + _this3.lin1Z = 0; + _this3.lin2X = 0; + _this3.lin2Y = 0; + _this3.lin2Z = 0; + _this3.ang1X = 0; + _this3.ang1Y = 0; + _this3.ang1Z = 0; + _this3.ang2X = 0; + _this3.ang2Y = 0; + _this3.ang2Z = 0; + row3.rhs = 0; + row3.cfm = 0; + row3.minImpulse = 0; + row3.maxImpulse = 0; + row3.motorSpeed = 0; + row3.motorMaxImpulse = 0; + row3.impulse = null; + row3.impulse = impulse3; + row3.rhs = angRhsY; + row3.cfm = 0; + row3.minImpulse = -1e65536; + row3.maxImpulse = 1e65536; + j = row3.jacobian; + j.ang1X = this._basis.yX; + j.ang1Y = this._basis.yY; + j.ang1Z = this._basis.yZ; + j.ang2X = this._basis.yX; + j.ang2Y = this._basis.yY; + j.ang2Z = this._basis.yZ; + let impulse4 = this._impulses[5]; + let row4 = info.rows[info.numRows++]; + let _this4 = row4.jacobian; + _this4.lin1X = 0; + _this4.lin1Y = 0; + _this4.lin1Z = 0; + _this4.lin2X = 0; + _this4.lin2Y = 0; + _this4.lin2Z = 0; + _this4.ang1X = 0; + _this4.ang1Y = 0; + _this4.ang1Z = 0; + _this4.ang2X = 0; + _this4.ang2Y = 0; + _this4.ang2Z = 0; + row4.rhs = 0; + row4.cfm = 0; + row4.minImpulse = 0; + row4.maxImpulse = 0; + row4.motorSpeed = 0; + row4.motorMaxImpulse = 0; + row4.impulse = null; + row4.impulse = impulse4; + row4.rhs = angRhsZ; + row4.cfm = 0; + row4.minImpulse = -1e65536; + row4.maxImpulse = 1e65536; + j = row4.jacobian; + j.ang1X = this._basis.zX; + j.ang1Y = this._basis.zY; + j.ang1Z = this._basis.zZ; + j.ang2X = this._basis.zX; + j.ang2Y = this._basis.zY; + j.ang2Z = this._basis.zZ; + } + _syncAnchors() { + super._syncAnchors(); + let _this = this._basis; + let invM1 = _this.joint._b1._invMass; + let invM2 = _this.joint._b2._invMass; + let qX; + let qY; + let qZ; + let qW; + let idQX; + let idQY; + let idQZ; + let idQW; + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let newXX; + let newXY; + let newXZ; + let newYX; + let newYY; + let newYZ; + let newZX; + let newZY; + let newZZ; + let prevXX; + let prevXY; + let prevXZ; + let prevYX; + let prevYY; + let prevYZ; + let d = _this.joint._basisX1X * _this.joint._basisX2X + _this.joint._basisX1Y * _this.joint._basisX2Y + _this.joint._basisX1Z * _this.joint._basisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = _this.joint._basisX1X; + let y1 = _this.joint._basisX1Y; + let z1 = _this.joint._basisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + qX = vX; + qY = vY; + qZ = vZ; + qW = 0; + } else { + let cX; + let cY; + let cZ; + cX = _this.joint._basisX1Y * _this.joint._basisX2Z - _this.joint._basisX1Z * _this.joint._basisX2Y; + cY = _this.joint._basisX1Z * _this.joint._basisX2X - _this.joint._basisX1X * _this.joint._basisX2Z; + cZ = _this.joint._basisX1X * _this.joint._basisX2Y - _this.joint._basisX1Y * _this.joint._basisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + qX = cX; + qY = cY; + qZ = cZ; + qW = w; + } + idQX = 0; + idQY = 0; + idQZ = 0; + idQW = 1; + let q1X; + let q1Y; + let q1Z; + let q1W; + let q2X; + let q2Y; + let q2Z; + let q2W; + q1X = idQX; + q1Y = idQY; + q1Z = idQZ; + q1W = idQW; + q2X = qX; + q2Y = qY; + q2Z = qZ; + q2W = qW; + let d1 = q1X * q2X + q1Y * q2Y + q1Z * q2Z + q1W * q2W; + if(d1 < 0) { + d1 = -d1; + q2X = -q2X; + q2Y = -q2Y; + q2Z = -q2Z; + q2W = -q2W; + } + if(d1 > 0.999999) { + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = q2X - q1X; + dqY = q2Y - q1Y; + dqZ = q2Z - q1Z; + dqW = q2W - q1W; + q2X = q1X + dqX * (invM1 / (invM1 + invM2)); + q2Y = q1Y + dqY * (invM1 / (invM1 + invM2)); + q2Z = q1Z + dqZ * (invM1 / (invM1 + invM2)); + q2W = q1W + dqW * (invM1 / (invM1 + invM2)); + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + slerpQX = q2X * l; + slerpQY = q2Y * l; + slerpQZ = q2Z * l; + slerpQW = q2W * l; + } else { + let theta = invM1 / (invM1 + invM2) * Math.acos(d1); + q2X += q1X * -d1; + q2Y += q1Y * -d1; + q2Z += q1Z * -d1; + q2W += q1W * -d1; + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + q2X *= l; + q2Y *= l; + q2Z *= l; + q2W *= l; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + q1X *= cos; + q1Y *= cos; + q1Z *= cos; + q1W *= cos; + slerpQX = q1X + q2X * sin; + slerpQY = q1Y + q2Y * sin; + slerpQZ = q1Z + q2Z * sin; + slerpQW = q1W + q2W * sin; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * _this.joint._basisX1X + slerpM01 * _this.joint._basisX1Y + slerpM02 * _this.joint._basisX1Z; + __tmp__Y = slerpM10 * _this.joint._basisX1X + slerpM11 * _this.joint._basisX1Y + slerpM12 * _this.joint._basisX1Z; + __tmp__Z = slerpM20 * _this.joint._basisX1X + slerpM21 * _this.joint._basisX1Y + slerpM22 * _this.joint._basisX1Z; + newXX = __tmp__X; + newXY = __tmp__Y; + newXZ = __tmp__Z; + prevXX = _this.xX; + prevXY = _this.xY; + prevXZ = _this.xZ; + prevYX = _this.yX; + prevYY = _this.yY; + prevYZ = _this.yZ; + let d2 = prevXX * newXX + prevXY * newXY + prevXZ * newXZ; + if(d2 < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = prevXX; + let y1 = prevXY; + let z1 = prevXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = prevXY * newXZ - prevXZ * newXY; + cY = prevXZ * newXX - prevXX * newXZ; + cZ = prevXX * newXY - prevXY * newXX; + let w = Math.sqrt((1 + d2) * 0.5); + d2 = 0.5 / w; + cX *= d2; + cY *= d2; + cZ *= d2; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x1 = slerpQX; + let y1 = slerpQY; + let z1 = slerpQZ; + let w1 = slerpQW; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + slerpM00 = 1 - yy1 - zz1; + slerpM01 = xy1 - wz1; + slerpM02 = xz1 + wy1; + slerpM10 = xy1 + wz1; + slerpM11 = 1 - xx1 - zz1; + slerpM12 = yz1 - wx1; + slerpM20 = xz1 - wy1; + slerpM21 = yz1 + wx1; + slerpM22 = 1 - xx1 - yy1; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * prevYX + slerpM01 * prevYY + slerpM02 * prevYZ; + __tmp__Y1 = slerpM10 * prevYX + slerpM11 * prevYY + slerpM12 * prevYZ; + __tmp__Z1 = slerpM20 * prevYX + slerpM21 * prevYY + slerpM22 * prevYZ; + newYX = __tmp__X1; + newYY = __tmp__Y1; + newYZ = __tmp__Z1; + newZX = newXY * newYZ - newXZ * newYY; + newZY = newXZ * newYX - newXX * newYZ; + newZZ = newXX * newYY - newXY * newYX; + if(newZX * newZX + newZY * newZY + newZZ * newZZ > 1e-6) { + let l = newZX * newZX + newZY * newZY + newZZ * newZZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + newZX *= l; + newZY *= l; + newZZ *= l; + } else { + let x1 = newXX; + let y1 = newXY; + let z1 = newXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + newZX = 0; + newZY = z1 * d; + newZZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + newZX = -z1 * d; + newZY = 0; + newZZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } + newYX = newZY * newXZ - newZZ * newXY; + newYY = newZZ * newXX - newZX * newXZ; + newYZ = newZX * newXY - newZY * newXX; + _this.xX = newXX; + _this.xY = newXY; + _this.xZ = newXZ; + _this.yX = newYX; + _this.yY = newYY; + _this.yZ = newYZ; + _this.zX = newZX; + _this.zY = newZY; + _this.zZ = newZZ; + let angErrorX; + let angErrorY; + let angErrorZ; + angErrorX = this._basisX1Y * this._basisX2Z - this._basisX1Z * this._basisX2Y; + angErrorY = this._basisX1Z * this._basisX2X - this._basisX1X * this._basisX2Z; + angErrorZ = this._basisX1X * this._basisX2Y - this._basisX1Y * this._basisX2X; + let cos = this._basisX1X * this._basisX2X + this._basisX1Y * this._basisX2Y + this._basisX1Z * this._basisX2Z; + let theta = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + let l = angErrorX * angErrorX + angErrorY * angErrorY + angErrorZ * angErrorZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + angErrorX *= l; + angErrorY *= l; + angErrorZ *= l; + angErrorX *= theta; + angErrorY *= theta; + angErrorZ *= theta; + this.angularErrorY = angErrorX * this._basis.yX + angErrorY * this._basis.yY + angErrorZ * this._basis.yZ; + this.angularErrorZ = angErrorX * this._basis.zX + angErrorY * this._basis.zY + angErrorZ * this._basis.zZ; + let perpCrossX; + let perpCrossY; + let perpCrossZ; + perpCrossX = this._basisY1Y * this._basisY2Z - this._basisY1Z * this._basisY2Y; + perpCrossY = this._basisY1Z * this._basisY2X - this._basisY1X * this._basisY2Z; + perpCrossZ = this._basisY1X * this._basisY2Y - this._basisY1Y * this._basisY2X; + cos = this._basisY1X * this._basisY2X + this._basisY1Y * this._basisY2Y + this._basisY1Z * this._basisY2Z; + this.angle = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + if(perpCrossX * this._basis.xX + perpCrossY * this._basis.xY + perpCrossZ * this._basis.xZ < 0) { + this.angle = -this.angle; + } + this.linearErrorX = this._anchor2X - this._anchor1X; + this.linearErrorY = this._anchor2Y - this._anchor1Y; + this.linearErrorZ = this._anchor2Z - this._anchor1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getSpringDamper() { + return this._sd; + } + getLimitMotor() { + return this._lm; + } + getAngle() { + return this.angle; + } +} +oimo.dynamics.constraint.joint.RevoluteJointConfig = class oimo_dynamics_constraint_joint_RevoluteJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.springDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.limitMotor = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis.x; + vY = worldAxis.y; + vZ = worldAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis.x; + vY1 = worldAxis.y; + vZ1 = worldAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +oimo.dynamics.constraint.joint.RotationalLimitMotor = class oimo_dynamics_constraint_joint_RotationalLimitMotor { + constructor() { + this.lowerLimit = 1; + this.upperLimit = 0; + this.motorTorque = 0; + } + setLimits(lower,upper) { + this.lowerLimit = lower; + this.upperLimit = upper; + return this; + } + setMotor(speed,torque) { + this.motorSpeed = speed; + this.motorTorque = torque; + return this; + } + clone() { + let lm = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + lm.lowerLimit = this.lowerLimit; + lm.upperLimit = this.upperLimit; + lm.motorSpeed = this.motorSpeed; + lm.motorTorque = this.motorTorque; + return lm; + } +} +oimo.dynamics.constraint.joint.SphericalJoint = class oimo_dynamics_constraint_joint_SphericalJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,0); + this._sd = config.springDamper.clone(); + } + getInfo(info,timeStep,isPositionPart) { + if(this._sd.frequency > 0 && isPositionPart) { + return; + } + let errorX; + let errorY; + let errorZ; + errorX = this._anchor2X - this._anchor1X; + errorY = this._anchor2Y - this._anchor1Y; + errorZ = this._anchor2Z - this._anchor1Z; + let cfm; + let erp; + if(this._sd.frequency > 0) { + let omega = 6.28318530717958 * this._sd.frequency; + let zeta = this._sd.dampingRatio; + if(zeta < oimo.common.Setting.minSpringDamperDampingRatio) { + zeta = oimo.common.Setting.minSpringDamperDampingRatio; + } + let h = timeStep.dt; + let c = 2 * zeta * omega; + let k = omega * omega; + if(this._sd.useSymplecticEuler) { + cfm = 1 / (h * c); + erp = k / c; + } else { + cfm = 1 / (h * (h * k + c)); + erp = k / (h * k + c); + } + cfm *= this._b1._invMass + this._b2._invMass; + } else { + cfm = 0; + erp = this.getErp(timeStep,isPositionPart); + } + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = errorX * erp; + linearRhsY = errorY * erp; + linearRhsZ = errorZ * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = cfm; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = cfm; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = cfm; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getSpringDamper() { + return this._sd; + } +} +oimo.dynamics.constraint.joint.SphericalJointConfig = class oimo_dynamics_constraint_joint_SphericalJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.springDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + } + init(rigidBody1,rigidBody2,worldAnchor) { + this._init(rigidBody1,rigidBody2,worldAnchor); + return this; + } +} +oimo.dynamics.constraint.joint.SpringDamper = class oimo_dynamics_constraint_joint_SpringDamper { + constructor() { + this.frequency = 0; + this.dampingRatio = 0; + this.useSymplecticEuler = false; + } + setSpring(frequency,dampingRatio) { + this.frequency = frequency; + this.dampingRatio = dampingRatio; + return this; + } + setSymplecticEuler(useSymplecticEuler) { + this.useSymplecticEuler = useSymplecticEuler; + return this; + } + clone() { + let sd = new oimo.dynamics.constraint.joint.SpringDamper(); + sd.frequency = this.frequency; + sd.dampingRatio = this.dampingRatio; + sd.useSymplecticEuler = this.useSymplecticEuler; + return sd; + } +} +oimo.dynamics.constraint.joint.TranslationalLimitMotor = class oimo_dynamics_constraint_joint_TranslationalLimitMotor { + constructor() { + this.lowerLimit = 1; + this.upperLimit = 0; + this.motorForce = 0; + } + setLimits(lower,upper) { + this.lowerLimit = lower; + this.upperLimit = upper; + return this; + } + setMotor(speed,force) { + this.motorSpeed = speed; + this.motorForce = force; + return this; + } + clone() { + let lm = new oimo.dynamics.constraint.joint.TranslationalLimitMotor(); + lm.lowerLimit = this.lowerLimit; + lm.upperLimit = this.upperLimit; + lm.motorSpeed = this.motorSpeed; + lm.motorForce = this.motorForce; + return lm; + } +} +oimo.dynamics.constraint.joint.UniversalJoint = class oimo_dynamics_constraint_joint_UniversalJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.UNIVERSAL); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisZ2X = v1.x; + this._localBasisZ2Y = v1.y; + this._localBasisZ2Z = v1.z; + this.buildLocalBasesFromX1Z2(); + this._angleX = 0; + this._angleY = 0; + this._angleZ = 0; + this.xSingular = false; + this.ySingular = false; + this.zSingular = false; + this._sd1 = config.springDamper1.clone(); + this._sd2 = config.springDamper2.clone(); + this._lm1 = config.limitMotor1.clone(); + this._lm2 = config.limitMotor2.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = this.linearErrorX * erp; + linearRhsY = this.linearErrorY * erp; + linearRhsZ = this.linearErrorZ * erp; + let angRhsY = this._angleY * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let motorMassX = this.computeEffectiveInertiaMoment(this._axisXX,this._axisXY,this._axisXZ); + let motorMassZ = this.computeEffectiveInertiaMoment(this._axisZX,this._axisZY,this._axisZZ); + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + if(!this.xSingular && (this._sd1.frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleX,this._lm1,motorMassX,this._sd1,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisXX; + j.ang1Y = this._axisXY; + j.ang1Z = this._axisXZ; + j.ang2X = this._axisXX; + j.ang2Y = this._axisXY; + j.ang2Z = this._axisXZ; + } + if(!this.ySingular) { + let impulse = this._impulses[4]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = angRhsY; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + j = row.jacobian; + j.ang1X = this._axisYX; + j.ang1Y = this._axisYY; + j.ang1Z = this._axisYZ; + j.ang2X = this._axisYX; + j.ang2Y = this._axisYY; + j.ang2Z = this._axisYZ; + } + if(!this.zSingular && (this._sd2.frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[5]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleZ,this._lm2,motorMassZ,this._sd2,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisZX; + j.ang1Y = this._axisZY; + j.ang1Z = this._axisZZ; + j.ang2X = this._axisZX; + j.ang2Y = this._axisZY; + j.ang2Z = this._axisZZ; + } + } + _syncAnchors() { + super._syncAnchors(); + let angleAxisXX; + let angleAxisXY; + let angleAxisXZ; + let angleAxisYX; + let angleAxisYY; + let angleAxisYZ; + let angleAxisZX; + let angleAxisZY; + let angleAxisZZ; + angleAxisXX = this._basisX1X; + angleAxisXY = this._basisX1Y; + angleAxisXZ = this._basisX1Z; + angleAxisZX = this._basisZ2X; + angleAxisZY = this._basisZ2Y; + angleAxisZZ = this._basisZ2Z; + angleAxisYX = angleAxisZY * angleAxisXZ - angleAxisZZ * angleAxisXY; + angleAxisYY = angleAxisZZ * angleAxisXX - angleAxisZX * angleAxisXZ; + angleAxisYZ = angleAxisZX * angleAxisXY - angleAxisZY * angleAxisXX; + this._axisXX = angleAxisYY * angleAxisZZ - angleAxisYZ * angleAxisZY; + this._axisXY = angleAxisYZ * angleAxisZX - angleAxisYX * angleAxisZZ; + this._axisXZ = angleAxisYX * angleAxisZY - angleAxisYY * angleAxisZX; + this._axisYX = angleAxisYX; + this._axisYY = angleAxisYY; + this._axisYZ = angleAxisYZ; + this._axisZX = angleAxisXY * angleAxisYZ - angleAxisXZ * angleAxisYY; + this._axisZY = angleAxisXZ * angleAxisYX - angleAxisXX * angleAxisYZ; + this._axisZZ = angleAxisXX * angleAxisYY - angleAxisXY * angleAxisYX; + let l = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._axisXX *= l; + this._axisXY *= l; + this._axisXZ *= l; + let l1 = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ; + if(l1 > 0) { + l1 = 1 / Math.sqrt(l1); + } + this._axisYX *= l1; + this._axisYY *= l1; + this._axisYZ *= l1; + let l2 = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ; + if(l2 > 0) { + l2 = 1 / Math.sqrt(l2); + } + this._axisZX *= l2; + this._axisZY *= l2; + this._axisZZ *= l2; + this.xSingular = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ == 0; + this.ySingular = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ == 0; + this.zSingular = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ == 0; + let rot100; + let rot101; + let rot102; + let rot110; + let rot111; + let rot112; + let rot120; + let rot121; + let rot122; + let rot200; + let rot201; + let rot202; + let rot210; + let rot211; + let rot212; + let rot220; + let rot221; + let rot222; + rot100 = this._basisX1X; + rot101 = this._basisY1X; + rot102 = this._basisZ1X; + rot110 = this._basisX1Y; + rot111 = this._basisY1Y; + rot112 = this._basisZ1Y; + rot120 = this._basisX1Z; + rot121 = this._basisY1Z; + rot122 = this._basisZ1Z; + rot200 = this._basisX2X; + rot201 = this._basisY2X; + rot202 = this._basisZ2X; + rot210 = this._basisX2Y; + rot211 = this._basisY2Y; + rot212 = this._basisZ2Y; + rot220 = this._basisX2Z; + rot221 = this._basisY2Z; + rot222 = this._basisZ2Z; + let relRot00; + let relRot01; + let relRot02; + let relRot11; + let relRot12; + let relRot21; + let relRot22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__11; + let __tmp__12; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot100 * rot200 + rot110 * rot210 + rot120 * rot220; + __tmp__01 = rot100 * rot201 + rot110 * rot211 + rot120 * rot221; + __tmp__02 = rot100 * rot202 + rot110 * rot212 + rot120 * rot222; + __tmp__11 = rot101 * rot201 + rot111 * rot211 + rot121 * rot221; + __tmp__12 = rot101 * rot202 + rot111 * rot212 + rot121 * rot222; + __tmp__21 = rot102 * rot201 + rot112 * rot211 + rot122 * rot221; + __tmp__22 = rot102 * rot202 + rot112 * rot212 + rot122 * rot222; + relRot00 = __tmp__00; + relRot01 = __tmp__01; + relRot02 = __tmp__02; + relRot11 = __tmp__11; + relRot12 = __tmp__12; + relRot21 = __tmp__21; + relRot22 = __tmp__22; + let anglesX; + let anglesY; + let anglesZ; + let sy = relRot02; + if(sy <= -1) { + let xSubZ = Math.atan2(relRot21,relRot11); + anglesX = xSubZ * 0.5; + anglesY = -1.570796326794895; + anglesZ = -xSubZ * 0.5; + } else if(sy >= 1) { + let xAddZ = Math.atan2(relRot21,relRot11); + anglesX = xAddZ * 0.5; + anglesY = 1.570796326794895; + anglesZ = xAddZ * 0.5; + } else { + anglesX = Math.atan2(-relRot12,relRot22); + anglesY = Math.asin(sy); + anglesZ = Math.atan2(-relRot01,relRot00); + } + this._angleX = anglesX; + this._angleY = anglesY; + this._angleZ = anglesZ; + this.linearErrorX = this._anchor2X - this._anchor1X; + this.linearErrorY = this._anchor2Y - this._anchor1Y; + this.linearErrorZ = this._anchor2Z - this._anchor1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisZ2X; + v.y = this._basisZ2Y; + v.z = this._basisZ2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisZ2X; + axis.y = this._basisZ2Y; + axis.z = this._basisZ2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisZ2X; + v.y = this._localBasisZ2Y; + v.z = this._localBasisZ2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisZ2X; + axis.y = this._localBasisZ2Y; + axis.z = this._localBasisZ2Z; + } + getSpringDamper1() { + return this._sd1; + } + getSpringDamper2() { + return this._sd2; + } + getLimitMotor1() { + return this._lm1; + } + getLimitMotor2() { + return this._lm2; + } + getAngle1() { + return this._angleX; + } + getAngle2() { + return this._angleZ; + } +} +oimo.dynamics.constraint.joint.UniversalJointConfig = class oimo_dynamics_constraint_joint_UniversalJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.springDamper1 = new oimo.dynamics.constraint.joint.SpringDamper(); + this.springDamper2 = new oimo.dynamics.constraint.joint.SpringDamper(); + this.limitMotor1 = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.limitMotor2 = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis1,worldAxis2) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis1.x; + vY = worldAxis1.y; + vZ = worldAxis1.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis2.x; + vY1 = worldAxis2.y; + vZ1 = worldAxis2.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +if(!oimo.dynamics.constraint.solver) oimo.dynamics.constraint.solver = {}; +oimo.dynamics.constraint.solver.ConstraintSolverType = class oimo_dynamics_constraint_solver_ConstraintSolverType { +} +if(!oimo.dynamics.constraint.solver.common) oimo.dynamics.constraint.solver.common = {}; +oimo.dynamics.constraint.solver.common.ContactSolverMassDataRow = class oimo_dynamics_constraint_solver_common_ContactSolverMassDataRow { + constructor() { + this.invMLinN1X = 0; + this.invMLinN1Y = 0; + this.invMLinN1Z = 0; + this.invMLinN2X = 0; + this.invMLinN2Y = 0; + this.invMLinN2Z = 0; + this.invMAngN1X = 0; + this.invMAngN1Y = 0; + this.invMAngN1Z = 0; + this.invMAngN2X = 0; + this.invMAngN2Y = 0; + this.invMAngN2Z = 0; + this.invMLinT1X = 0; + this.invMLinT1Y = 0; + this.invMLinT1Z = 0; + this.invMLinT2X = 0; + this.invMLinT2Y = 0; + this.invMLinT2Z = 0; + this.invMAngT1X = 0; + this.invMAngT1Y = 0; + this.invMAngT1Z = 0; + this.invMAngT2X = 0; + this.invMAngT2Y = 0; + this.invMAngT2Z = 0; + this.invMLinB1X = 0; + this.invMLinB1Y = 0; + this.invMLinB1Z = 0; + this.invMLinB2X = 0; + this.invMLinB2Y = 0; + this.invMLinB2Z = 0; + this.invMAngB1X = 0; + this.invMAngB1Y = 0; + this.invMAngB1Z = 0; + this.invMAngB2X = 0; + this.invMAngB2Y = 0; + this.invMAngB2Z = 0; + this.massN = 0; + this.massTB00 = 0; + this.massTB01 = 0; + this.massTB10 = 0; + this.massTB11 = 0; + } +} +oimo.dynamics.constraint.solver.common.JointSolverMassDataRow = class oimo_dynamics_constraint_solver_common_JointSolverMassDataRow { + constructor() { + this.invMLin1X = 0; + this.invMLin1Y = 0; + this.invMLin1Z = 0; + this.invMLin2X = 0; + this.invMLin2Y = 0; + this.invMLin2Z = 0; + this.invMAng1X = 0; + this.invMAng1Y = 0; + this.invMAng1Z = 0; + this.invMAng2X = 0; + this.invMAng2Y = 0; + this.invMAng2Z = 0; + this.mass = 0; + this.massWithoutCfm = 0; + } +} +if(!oimo.dynamics.constraint.solver.direct) oimo.dynamics.constraint.solver.direct = {}; +oimo.dynamics.constraint.solver.direct.Boundary = class oimo_dynamics_constraint_solver_direct_Boundary { + constructor(maxRows) { + this.iBounded = new Array(maxRows); + this.iUnbounded = new Array(maxRows); + this.signs = new Array(maxRows); + this.b = new Array(maxRows); + this.numBounded = 0; + this.numUnbounded = 0; + this.matrixId = 0; + } + init(buildInfo) { + this.numBounded = buildInfo.numBounded; + let _g = 0; + let _g1 = this.numBounded; + while(_g < _g1) { + let i = _g++; + this.iBounded[i] = buildInfo.iBounded[i]; + this.signs[i] = buildInfo.signs[i]; + } + this.numUnbounded = buildInfo.numUnbounded; + this.matrixId = 0; + let _g2 = 0; + let _g3 = this.numUnbounded; + while(_g2 < _g3) { + let i = _g2++; + let idx = buildInfo.iUnbounded[i]; + this.iUnbounded[i] = idx; + this.matrixId |= 1 << idx; + } + } + computeImpulses(info,mass,relVels,impulses,dImpulses,impulseFactor,noCheck) { + let _g = 0; + let _g1 = this.numUnbounded; + while(_g < _g1) { + let idx = this.iUnbounded[_g++]; + let row = info.rows[idx]; + this.b[idx] = row.rhs * impulseFactor - relVels[idx] - row.cfm * impulses[idx]; + } + let invMassWithoutCfm = mass._invMassWithoutCfm; + let _g2 = 0; + let _g3 = this.numBounded; + while(_g2 < _g3) { + let i = _g2++; + let idx = this.iBounded[i]; + let sign = this.signs[i]; + let row = info.rows[idx]; + let dImpulse = (sign < 0 ? row.minImpulse : sign > 0 ? row.maxImpulse : 0) - impulses[idx]; + dImpulses[idx] = dImpulse; + if(dImpulse != 0) { + let _g = 0; + let _g1 = this.numUnbounded; + while(_g < _g1) { + let idx2 = this.iUnbounded[_g++]; + this.b[idx2] -= invMassWithoutCfm[idx][idx2] * dImpulse; + } + } + } + let indices = this.iUnbounded; + let n = this.numUnbounded; + let id = 0; + let _g4 = 0; + while(_g4 < n) id |= 1 << indices[_g4++]; + let massMatrix; + if(mass._cacheComputed[id]) { + massMatrix = mass._cachedSubmatrices[id]; + } else { + mass.computeSubmatrix(id,indices,n); + mass._cacheComputed[id] = true; + massMatrix = mass._cachedSubmatrices[id]; + } + let ok = true; + let _g5 = 0; + let _g6 = this.numUnbounded; + while(_g5 < _g6) { + let i = _g5++; + let idx = this.iUnbounded[i]; + let row = info.rows[idx]; + let oldImpulse = impulses[idx]; + let impulse = oldImpulse; + let _g = 0; + let _g1 = this.numUnbounded; + while(_g < _g1) { + let j = _g++; + impulse += this.b[this.iUnbounded[j]] * massMatrix[i][j]; + } + if(impulse < row.minImpulse - oimo.common.Setting.directMlcpSolverEps || impulse > row.maxImpulse + oimo.common.Setting.directMlcpSolverEps) { + ok = false; + break; + } + dImpulses[idx] = impulse - oldImpulse; + } + if(noCheck) { + return true; + } + if(!ok) { + return false; + } + let _g7 = 0; + let _g8 = this.numBounded; + while(_g7 < _g8) { + let i = _g7++; + let idx = this.iBounded[i]; + let row = info.rows[idx]; + let sign = this.signs[i]; + let error = 0; + let newImpulse = impulses[idx] + dImpulses[idx]; + let relVel = relVels[idx]; + let _g = 0; + let _g1 = info.numRows; + while(_g < _g1) { + let j = _g++; + relVel += invMassWithoutCfm[idx][j] * dImpulses[j]; + } + error = row.rhs * impulseFactor - relVel - row.cfm * newImpulse; + if(sign < 0 && error > oimo.common.Setting.directMlcpSolverEps || sign > 0 && error < -oimo.common.Setting.directMlcpSolverEps) { + ok = false; + break; + } + } + return ok; + } +} +oimo.dynamics.constraint.solver.direct.BoundaryBuildInfo = class oimo_dynamics_constraint_solver_direct_BoundaryBuildInfo { + constructor(size) { + this.size = size; + this.numBounded = 0; + this.iBounded = new Array(size); + this.signs = new Array(size); + this.numUnbounded = 0; + this.iUnbounded = new Array(size); + } +} +oimo.dynamics.constraint.solver.direct.BoundaryBuilder = class oimo_dynamics_constraint_solver_direct_BoundaryBuilder { + constructor(maxRows) { + this.maxRows = maxRows; + this.numBoundaries = 0; + this.boundaries = new Array(1 << maxRows); + this.bbInfo = new oimo.dynamics.constraint.solver.direct.BoundaryBuildInfo(maxRows); + } + buildBoundariesRecursive(info,i) { + if(i == info.numRows) { + if(this.boundaries[this.numBoundaries] == null) { + this.boundaries[this.numBoundaries] = new oimo.dynamics.constraint.solver.direct.Boundary(this.maxRows); + } + this.boundaries[this.numBoundaries++].init(this.bbInfo); + return; + } + let row = info.rows[i]; + let lowerLimitEnabled = row.minImpulse > -1e65536; + let upperLimitEnabled = row.maxImpulse < 1e65536; + if(row.minImpulse == 0 && row.maxImpulse == 0) { + let _this = this.bbInfo; + _this.iBounded[_this.numBounded] = i; + _this.signs[_this.numBounded] = 0; + _this.numBounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numBounded--; + return; + } + let _this = this.bbInfo; + _this.iUnbounded[_this.numUnbounded] = i; + _this.numUnbounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numUnbounded--; + if(lowerLimitEnabled) { + let _this = this.bbInfo; + _this.iBounded[_this.numBounded] = i; + _this.signs[_this.numBounded] = -1; + _this.numBounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numBounded--; + } + if(upperLimitEnabled) { + let _this = this.bbInfo; + _this.iBounded[_this.numBounded] = i; + _this.signs[_this.numBounded] = 1; + _this.numBounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numBounded--; + } + } + buildBoundaries(info) { + this.numBoundaries = 0; + let _this = this.bbInfo; + _this.numBounded = 0; + _this.numUnbounded = 0; + this.buildBoundariesRecursive(info,0); + } +} +oimo.dynamics.constraint.solver.direct.BoundarySelector = class oimo_dynamics_constraint_solver_direct_BoundarySelector { + constructor(n) { + this.n = n; + this.indices = new Array(n); + this.tmpIndices = new Array(n); + let _g = 0; + while(_g < n) { + let i = _g++; + this.indices[i] = i; + } + } + getIndex(i) { + return this.indices[i]; + } + select(index) { + let i = 0; + while(this.indices[i] != index) ++i; + while(i > 0) { + let tmp = this.indices[i]; + this.indices[i] = this.indices[i - 1]; + this.indices[i - 1] = tmp; + --i; + } + } + setSize(size) { + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = this.n; + while(_g < _g1) { + let idx = this.indices[_g++]; + if(idx < size) { + this.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + this.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = this.indices; + this.indices = this.tmpIndices; + this.tmpIndices = tmp; + } +} +oimo.dynamics.constraint.solver.direct.DirectJointConstraintSolver = class oimo_dynamics_constraint_solver_direct_DirectJointConstraintSolver extends oimo.dynamics.constraint.ConstraintSolver { + constructor(joint) { + super(); + this.joint = joint; + this.info = new oimo.dynamics.constraint.info.joint.JointSolverInfo(); + let maxRows = oimo.common.Setting.maxJacobianRows; + this.massMatrix = new oimo.dynamics.constraint.solver.direct.MassMatrix(maxRows); + this.boundaryBuilder = new oimo.dynamics.constraint.solver.direct.BoundaryBuilder(maxRows); + this.massData = new Array(maxRows); + let _g = 0; + let _g1 = this.massData.length; + while(_g < _g1) this.massData[_g++] = new oimo.dynamics.constraint.solver.common.JointSolverMassDataRow(); + let numMaxBoundaries = this.boundaryBuilder.boundaries.length; + this.velBoundarySelector = new oimo.dynamics.constraint.solver.direct.BoundarySelector(numMaxBoundaries); + this.posBoundarySelector = new oimo.dynamics.constraint.solver.direct.BoundarySelector(numMaxBoundaries); + this.relVels = new Array(maxRows); + this.impulses = new Array(maxRows); + this.dImpulses = new Array(maxRows); + this.dTotalImpulses = new Array(maxRows); + let _g2 = 0; + while(_g2 < maxRows) { + let i = _g2++; + this.relVels[i] = 0; + this.impulses[i] = 0; + this.dImpulses[i] = 0; + this.dTotalImpulses[i] = 0; + } + } + preSolveVelocity(timeStep) { + this.joint._syncAnchors(); + this.joint._getVelocitySolverInfo(timeStep,this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + this.massMatrix.computeInvMass(this.info,this.massData); + let _this = this.boundaryBuilder; + _this.numBoundaries = 0; + let _this1 = _this.bbInfo; + _this1.numBounded = 0; + _this1.numUnbounded = 0; + _this.buildBoundariesRecursive(this.info,0); + let _this2 = this.velBoundarySelector; + let size = this.boundaryBuilder.numBoundaries; + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = _this2.n; + while(_g < _g1) { + let idx = _this2.indices[_g++]; + if(idx < size) { + _this2.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + _this2.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = _this2.indices; + _this2.indices = _this2.tmpIndices; + _this2.tmpIndices = tmp; + } + warmStart(timeStep) { + let factor = this.joint._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE ? oimo.common.Setting.jointWarmStartingFactorForBaungarte : oimo.common.Setting.jointWarmStartingFactor; + factor *= timeStep.dtRatio; + if(factor <= 0) { + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let _this = this.info.rows[_g++].impulse; + _this.impulse = 0; + _this.impulseM = 0; + _this.impulseP = 0; + } + return; + } + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let imp = row.impulse; + let impulse = imp.impulse * factor; + if(impulse < row.minImpulse) { + impulse = row.minImpulse; + } else if(impulse > row.maxImpulse) { + impulse = row.maxImpulse; + } + imp.impulse = impulse; + if(row.motorMaxImpulse > 0) { + let impulseM = imp.impulseM * factor; + let max = row.motorMaxImpulse; + if(impulseM < -max) { + impulseM = -max; + } else if(impulseM > max) { + impulseM = max; + } + imp.impulseM = impulseM; + } else { + imp.impulseM = 0; + } + this.dImpulses[i] = imp.impulse + imp.impulseM; + } + let impulses = this.dImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + } + if(angularSet) { + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + } + solveVelocity() { + let numRows = this.info.numRows; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + while(_g < numRows) { + let i = _g++; + let row = this.info.rows[i]; + let j = row.jacobian; + let relVel = 0; + relVel += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + relVel -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + relVel += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + relVel -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + this.relVels[i] = relVel; + this.impulses[i] = row.impulse.impulse; + this.dTotalImpulses[i] = 0; + } + let invMass = this.massMatrix._invMassWithoutCfm; + let _g1 = 0; + while(_g1 < numRows) { + let i = _g1++; + let row = this.info.rows[i]; + let imp = row.impulse; + if(row.motorMaxImpulse > 0) { + let oldImpulseM = imp.impulseM; + let impulseM = oldImpulseM + this.massData[i].massWithoutCfm * (-row.motorSpeed - this.relVels[i]); + let maxImpulseM = row.motorMaxImpulse; + if(impulseM < -maxImpulseM) { + impulseM = -maxImpulseM; + } else if(impulseM > maxImpulseM) { + impulseM = maxImpulseM; + } + imp.impulseM = impulseM; + let dImpulseM = impulseM - oldImpulseM; + this.dTotalImpulses[i] = dImpulseM; + let _g = 0; + while(_g < numRows) { + let j = _g++; + this.relVels[j] += dImpulseM * invMass[i][j]; + } + } + } + let solved = false; + let _g2 = 0; + let _g3 = this.boundaryBuilder.numBoundaries; + while(_g2 < _g3) { + let idx = this.velBoundarySelector.indices[_g2++]; + if(this.boundaryBuilder.boundaries[idx].computeImpulses(this.info,this.massMatrix,this.relVels,this.impulses,this.dImpulses,1,false)) { + let _g = 0; + while(_g < numRows) { + let j = _g++; + let dimp = this.dImpulses[j]; + this.info.rows[j].impulse.impulse += dimp; + this.dTotalImpulses[j] += dimp; + } + let impulses = this.dTotalImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g1 = 0; + let _g2 = this.info.numRows; + while(_g1 < _g2) { + let i = _g1++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + } + if(angularSet) { + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + let _this = this.velBoundarySelector; + let i = 0; + while(_this.indices[i] != idx) ++i; + while(i > 0) { + let tmp = _this.indices[i]; + _this.indices[i] = _this.indices[i - 1]; + _this.indices[i - 1] = tmp; + --i; + } + solved = true; + break; + } + } + if(!solved) { + console.log("src/oimo/dynamics/constraint/solver/direct/DirectJointConstraintSolver.hx:335:","could not find solution. (velocity)"); + return; + } + } + postSolveVelocity(timeStep) { + let linX; + let linY; + let linZ; + let angX; + let angY; + let angZ; + linX = 0; + linY = 0; + linZ = 0; + angX = 0; + angY = 0; + angZ = 0; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let row = this.info.rows[_g++]; + let imp = row.impulse; + let j = row.jacobian; + if((j.flag & 1) != 0) { + linX += j.lin1X * imp.impulse; + linY += j.lin1Y * imp.impulse; + linZ += j.lin1Z * imp.impulse; + } else if((j.flag & 2) != 0) { + angX += j.ang1X * imp.impulse; + angY += j.ang1Y * imp.impulse; + angZ += j.ang1Z * imp.impulse; + } + } + this.joint._appliedForceX = linX * timeStep.invDt; + this.joint._appliedForceY = linY * timeStep.invDt; + this.joint._appliedForceZ = linZ * timeStep.invDt; + this.joint._appliedTorqueX = angX * timeStep.invDt; + this.joint._appliedTorqueY = angY * timeStep.invDt; + this.joint._appliedTorqueZ = angZ * timeStep.invDt; + } + preSolvePosition(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + this.massMatrix.computeInvMass(this.info,this.massData); + let _this = this.boundaryBuilder; + _this.numBoundaries = 0; + let _this1 = _this.bbInfo; + _this1.numBounded = 0; + _this1.numUnbounded = 0; + _this.buildBoundariesRecursive(this.info,0); + let _this2 = this.posBoundarySelector; + let size = this.boundaryBuilder.numBoundaries; + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = _this2.n; + while(_g < _g1) { + let idx = _this2.indices[_g++]; + if(idx < size) { + _this2.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + _this2.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = _this2.indices; + _this2.indices = _this2.tmpIndices; + _this2.tmpIndices = tmp; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) this.info.rows[_g2++].impulse.impulseP = 0; + } + solvePositionSplitImpulse() { + let numRows = this.info.numRows; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g = 0; + while(_g < numRows) { + let i = _g++; + let row = this.info.rows[i]; + let j = row.jacobian; + let relVel = 0; + relVel += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + relVel -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + relVel += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + relVel -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + this.relVels[i] = relVel; + this.impulses[i] = row.impulse.impulseP; + } + let solved = false; + let _g1 = 0; + let _g2 = this.boundaryBuilder.numBoundaries; + while(_g1 < _g2) { + let idx = this.posBoundarySelector.indices[_g1++]; + if(this.boundaryBuilder.boundaries[idx].computeImpulses(this.info,this.massMatrix,this.relVels,this.impulses,this.dImpulses,oimo.common.Setting.positionSplitImpulseBaumgarte,false)) { + let _g = 0; + while(_g < numRows) { + let j = _g++; + this.info.rows[j].impulse.impulseP += this.dImpulses[j]; + } + let impulses = this.dImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g1 = 0; + let _g2 = this.info.numRows; + while(_g1 < _g2) { + let i = _g1++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + this._b1._pseudoVelX = lv1X; + this._b1._pseudoVelY = lv1Y; + this._b1._pseudoVelZ = lv1Z; + this._b2._pseudoVelX = lv2X; + this._b2._pseudoVelY = lv2Y; + this._b2._pseudoVelZ = lv2Z; + } + if(angularSet) { + this._b1._angPseudoVelX = av1X; + this._b1._angPseudoVelY = av1Y; + this._b1._angPseudoVelZ = av1Z; + this._b2._angPseudoVelX = av2X; + this._b2._angPseudoVelY = av2Y; + this._b2._angPseudoVelZ = av2Z; + } + let _this = this.posBoundarySelector; + let i = 0; + while(_this.indices[i] != idx) ++i; + while(i > 0) { + let tmp = _this.indices[i]; + _this.indices[i] = _this.indices[i - 1]; + _this.indices[i - 1] = tmp; + --i; + } + solved = true; + break; + } + } + if(!solved) { + console.log("src/oimo/dynamics/constraint/solver/direct/DirectJointConstraintSolver.hx:450:","could not find solution. (split impulse)"); + return; + } + } + solvePositionNgs(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + this.massMatrix.computeInvMass(this.info,this.massData); + let _this = this.boundaryBuilder; + _this.numBoundaries = 0; + let _this1 = _this.bbInfo; + _this1.numBounded = 0; + _this1.numUnbounded = 0; + _this.buildBoundariesRecursive(this.info,0); + let _this2 = this.posBoundarySelector; + let size = this.boundaryBuilder.numBoundaries; + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = _this2.n; + while(_g < _g1) { + let idx = _this2.indices[_g++]; + if(idx < size) { + _this2.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + _this2.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = _this2.indices; + _this2.indices = _this2.tmpIndices; + _this2.tmpIndices = tmp; + let numRows = this.info.numRows; + let _g2 = 0; + while(_g2 < numRows) { + let i = _g2++; + let imp = this.info.rows[i].impulse; + this.relVels[i] = 0; + this.impulses[i] = imp.impulseP; + } + let solved = false; + let _g3 = 0; + let _g4 = this.boundaryBuilder.numBoundaries; + while(_g3 < _g4) { + let idx = this.posBoundarySelector.indices[_g3++]; + if(this.boundaryBuilder.boundaries[idx].computeImpulses(this.info,this.massMatrix,this.relVels,this.impulses,this.dImpulses,oimo.common.Setting.positionNgsBaumgarte,false)) { + let _g = 0; + while(_g < numRows) { + let j = _g++; + this.info.rows[j].impulse.impulseP += this.dImpulses[j]; + } + let impulses = this.dImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = 0; + lv1Y = 0; + lv1Z = 0; + lv2X = 0; + lv2Y = 0; + lv2Z = 0; + av1X = 0; + av1Y = 0; + av1Z = 0; + av2X = 0; + av2Y = 0; + av2Z = 0; + let _g1 = 0; + let _g2 = this.info.numRows; + while(_g1 < _g2) { + let i = _g1++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + let _this = this._b1; + _this._transform._positionX += lv1X; + _this._transform._positionY += lv1Y; + _this._transform._positionZ += lv1Z; + let _this1 = this._b2; + _this1._transform._positionX += lv2X; + _this1._transform._positionY += lv2Y; + _this1._transform._positionZ += lv2Z; + } + if(angularSet) { + let _this = this._b1; + let theta = Math.sqrt(av1X * av1X + av1Y * av1Y + av1Z * av1Z); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = av1X * rotationToSinAxisFactor; + sinAxisY = av1Y * rotationToSinAxisFactor; + sinAxisZ = av1Z * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = _this._transform._rotation00; + let e11 = _this._transform._rotation11; + let e22 = _this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation21 - _this._transform._rotation12) * s; + qY = (_this._transform._rotation02 - _this._transform._rotation20) * s; + qZ = (_this._transform._rotation10 - _this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (_this._transform._rotation01 + _this._transform._rotation10) * s; + qZ = (_this._transform._rotation02 + _this._transform._rotation20) * s; + qW = (_this._transform._rotation21 - _this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation02 + _this._transform._rotation20) * s; + qY = (_this._transform._rotation12 + _this._transform._rotation21) * s; + qW = (_this._transform._rotation10 - _this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation01 + _this._transform._rotation10) * s; + qZ = (_this._transform._rotation12 + _this._transform._rotation21) * s; + qW = (_this._transform._rotation02 - _this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation02 + _this._transform._rotation20) * s; + qY = (_this._transform._rotation12 + _this._transform._rotation21) * s; + qW = (_this._transform._rotation10 - _this._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this._transform._rotation00 = 1 - yy - zz; + _this._transform._rotation01 = xy - wz; + _this._transform._rotation02 = xz + wy; + _this._transform._rotation10 = xy + wz; + _this._transform._rotation11 = 1 - xx - zz; + _this._transform._rotation12 = yz - wx; + _this._transform._rotation20 = xz - wy; + _this._transform._rotation21 = yz + wx; + _this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = _this._transform._rotation00 * _this._invLocalInertia00 + _this._transform._rotation01 * _this._invLocalInertia10 + _this._transform._rotation02 * _this._invLocalInertia20; + __tmp__01 = _this._transform._rotation00 * _this._invLocalInertia01 + _this._transform._rotation01 * _this._invLocalInertia11 + _this._transform._rotation02 * _this._invLocalInertia21; + __tmp__02 = _this._transform._rotation00 * _this._invLocalInertia02 + _this._transform._rotation01 * _this._invLocalInertia12 + _this._transform._rotation02 * _this._invLocalInertia22; + __tmp__10 = _this._transform._rotation10 * _this._invLocalInertia00 + _this._transform._rotation11 * _this._invLocalInertia10 + _this._transform._rotation12 * _this._invLocalInertia20; + __tmp__11 = _this._transform._rotation10 * _this._invLocalInertia01 + _this._transform._rotation11 * _this._invLocalInertia11 + _this._transform._rotation12 * _this._invLocalInertia21; + __tmp__12 = _this._transform._rotation10 * _this._invLocalInertia02 + _this._transform._rotation11 * _this._invLocalInertia12 + _this._transform._rotation12 * _this._invLocalInertia22; + __tmp__20 = _this._transform._rotation20 * _this._invLocalInertia00 + _this._transform._rotation21 * _this._invLocalInertia10 + _this._transform._rotation22 * _this._invLocalInertia20; + __tmp__21 = _this._transform._rotation20 * _this._invLocalInertia01 + _this._transform._rotation21 * _this._invLocalInertia11 + _this._transform._rotation22 * _this._invLocalInertia21; + __tmp__22 = _this._transform._rotation20 * _this._invLocalInertia02 + _this._transform._rotation21 * _this._invLocalInertia12 + _this._transform._rotation22 * _this._invLocalInertia22; + _this._invInertia00 = __tmp__00; + _this._invInertia01 = __tmp__01; + _this._invInertia02 = __tmp__02; + _this._invInertia10 = __tmp__10; + _this._invInertia11 = __tmp__11; + _this._invInertia12 = __tmp__12; + _this._invInertia20 = __tmp__20; + _this._invInertia21 = __tmp__21; + _this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = _this._invInertia00 * _this._transform._rotation00 + _this._invInertia01 * _this._transform._rotation01 + _this._invInertia02 * _this._transform._rotation02; + __tmp__011 = _this._invInertia00 * _this._transform._rotation10 + _this._invInertia01 * _this._transform._rotation11 + _this._invInertia02 * _this._transform._rotation12; + __tmp__021 = _this._invInertia00 * _this._transform._rotation20 + _this._invInertia01 * _this._transform._rotation21 + _this._invInertia02 * _this._transform._rotation22; + __tmp__101 = _this._invInertia10 * _this._transform._rotation00 + _this._invInertia11 * _this._transform._rotation01 + _this._invInertia12 * _this._transform._rotation02; + __tmp__111 = _this._invInertia10 * _this._transform._rotation10 + _this._invInertia11 * _this._transform._rotation11 + _this._invInertia12 * _this._transform._rotation12; + __tmp__121 = _this._invInertia10 * _this._transform._rotation20 + _this._invInertia11 * _this._transform._rotation21 + _this._invInertia12 * _this._transform._rotation22; + __tmp__201 = _this._invInertia20 * _this._transform._rotation00 + _this._invInertia21 * _this._transform._rotation01 + _this._invInertia22 * _this._transform._rotation02; + __tmp__211 = _this._invInertia20 * _this._transform._rotation10 + _this._invInertia21 * _this._transform._rotation11 + _this._invInertia22 * _this._transform._rotation12; + __tmp__221 = _this._invInertia20 * _this._transform._rotation20 + _this._invInertia21 * _this._transform._rotation21 + _this._invInertia22 * _this._transform._rotation22; + _this._invInertia00 = __tmp__001; + _this._invInertia01 = __tmp__011; + _this._invInertia02 = __tmp__021; + _this._invInertia10 = __tmp__101; + _this._invInertia11 = __tmp__111; + _this._invInertia12 = __tmp__121; + _this._invInertia20 = __tmp__201; + _this._invInertia21 = __tmp__211; + _this._invInertia22 = __tmp__221; + _this._invInertia00 *= _this._rotFactor.x; + _this._invInertia01 *= _this._rotFactor.x; + _this._invInertia02 *= _this._rotFactor.x; + _this._invInertia10 *= _this._rotFactor.y; + _this._invInertia11 *= _this._rotFactor.y; + _this._invInertia12 *= _this._rotFactor.y; + _this._invInertia20 *= _this._rotFactor.z; + _this._invInertia21 *= _this._rotFactor.z; + _this._invInertia22 *= _this._rotFactor.z; + let _this1 = this._b2; + let theta1 = Math.sqrt(av2X * av2X + av2Y * av2Y + av2Z * av2Z); + let halfTheta1 = theta1 * 0.5; + let rotationToSinAxisFactor1; + let cosHalfTheta1; + if(halfTheta1 < 0.5) { + let ht2 = halfTheta1 * halfTheta1; + rotationToSinAxisFactor1 = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta1 = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor1 = Math.sin(halfTheta1) / theta1; + cosHalfTheta1 = Math.cos(halfTheta1); + } + let sinAxisX1; + let sinAxisY1; + let sinAxisZ1; + sinAxisX1 = av2X * rotationToSinAxisFactor1; + sinAxisY1 = av2Y * rotationToSinAxisFactor1; + sinAxisZ1 = av2Z * rotationToSinAxisFactor1; + let dqX1; + let dqY1; + let dqZ1; + let dqW1; + dqX1 = sinAxisX1; + dqY1 = sinAxisY1; + dqZ1 = sinAxisZ1; + dqW1 = cosHalfTheta1; + let qX1; + let qY1; + let qZ1; + let qW1; + let e001 = _this1._transform._rotation00; + let e111 = _this1._transform._rotation11; + let e221 = _this1._transform._rotation22; + let t1 = e001 + e111 + e221; + let s1; + if(t1 > 0) { + s1 = Math.sqrt(t1 + 1); + qW1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation21 - _this1._transform._rotation12) * s1; + qY1 = (_this1._transform._rotation02 - _this1._transform._rotation20) * s1; + qZ1 = (_this1._transform._rotation10 - _this1._transform._rotation01) * s1; + } else if(e001 > e111) { + if(e001 > e221) { + s1 = Math.sqrt(e001 - e111 - e221 + 1); + qX1 = 0.5 * s1; + s1 = 0.5 / s1; + qY1 = (_this1._transform._rotation01 + _this1._transform._rotation10) * s1; + qZ1 = (_this1._transform._rotation02 + _this1._transform._rotation20) * s1; + qW1 = (_this1._transform._rotation21 - _this1._transform._rotation12) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation02 + _this1._transform._rotation20) * s1; + qY1 = (_this1._transform._rotation12 + _this1._transform._rotation21) * s1; + qW1 = (_this1._transform._rotation10 - _this1._transform._rotation01) * s1; + } + } else if(e111 > e221) { + s1 = Math.sqrt(e111 - e221 - e001 + 1); + qY1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation01 + _this1._transform._rotation10) * s1; + qZ1 = (_this1._transform._rotation12 + _this1._transform._rotation21) * s1; + qW1 = (_this1._transform._rotation02 - _this1._transform._rotation20) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation02 + _this1._transform._rotation20) * s1; + qY1 = (_this1._transform._rotation12 + _this1._transform._rotation21) * s1; + qW1 = (_this1._transform._rotation10 - _this1._transform._rotation01) * s1; + } + qX1 = dqW1 * qX1 + dqX1 * qW1 + dqY1 * qZ1 - dqZ1 * qY1; + qY1 = dqW1 * qY1 - dqX1 * qZ1 + dqY1 * qW1 + dqZ1 * qX1; + qZ1 = dqW1 * qZ1 + dqX1 * qY1 - dqY1 * qX1 + dqZ1 * qW1; + qW1 = dqW1 * qW1 - dqX1 * qX1 - dqY1 * qY1 - dqZ1 * qZ1; + let l1 = qX1 * qX1 + qY1 * qY1 + qZ1 * qZ1 + qW1 * qW1; + if(l1 > 1e-32) { + l1 = 1 / Math.sqrt(l1); + } + qX1 *= l1; + qY1 *= l1; + qZ1 *= l1; + qW1 *= l1; + let x1 = qX1; + let y1 = qY1; + let z1 = qZ1; + let w1 = qW1; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + _this1._transform._rotation00 = 1 - yy1 - zz1; + _this1._transform._rotation01 = xy1 - wz1; + _this1._transform._rotation02 = xz1 + wy1; + _this1._transform._rotation10 = xy1 + wz1; + _this1._transform._rotation11 = 1 - xx1 - zz1; + _this1._transform._rotation12 = yz1 - wx1; + _this1._transform._rotation20 = xz1 - wy1; + _this1._transform._rotation21 = yz1 + wx1; + _this1._transform._rotation22 = 1 - xx1 - yy1; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = _this1._transform._rotation00 * _this1._invLocalInertia00 + _this1._transform._rotation01 * _this1._invLocalInertia10 + _this1._transform._rotation02 * _this1._invLocalInertia20; + __tmp__012 = _this1._transform._rotation00 * _this1._invLocalInertia01 + _this1._transform._rotation01 * _this1._invLocalInertia11 + _this1._transform._rotation02 * _this1._invLocalInertia21; + __tmp__022 = _this1._transform._rotation00 * _this1._invLocalInertia02 + _this1._transform._rotation01 * _this1._invLocalInertia12 + _this1._transform._rotation02 * _this1._invLocalInertia22; + __tmp__102 = _this1._transform._rotation10 * _this1._invLocalInertia00 + _this1._transform._rotation11 * _this1._invLocalInertia10 + _this1._transform._rotation12 * _this1._invLocalInertia20; + __tmp__112 = _this1._transform._rotation10 * _this1._invLocalInertia01 + _this1._transform._rotation11 * _this1._invLocalInertia11 + _this1._transform._rotation12 * _this1._invLocalInertia21; + __tmp__122 = _this1._transform._rotation10 * _this1._invLocalInertia02 + _this1._transform._rotation11 * _this1._invLocalInertia12 + _this1._transform._rotation12 * _this1._invLocalInertia22; + __tmp__202 = _this1._transform._rotation20 * _this1._invLocalInertia00 + _this1._transform._rotation21 * _this1._invLocalInertia10 + _this1._transform._rotation22 * _this1._invLocalInertia20; + __tmp__212 = _this1._transform._rotation20 * _this1._invLocalInertia01 + _this1._transform._rotation21 * _this1._invLocalInertia11 + _this1._transform._rotation22 * _this1._invLocalInertia21; + __tmp__222 = _this1._transform._rotation20 * _this1._invLocalInertia02 + _this1._transform._rotation21 * _this1._invLocalInertia12 + _this1._transform._rotation22 * _this1._invLocalInertia22; + _this1._invInertia00 = __tmp__002; + _this1._invInertia01 = __tmp__012; + _this1._invInertia02 = __tmp__022; + _this1._invInertia10 = __tmp__102; + _this1._invInertia11 = __tmp__112; + _this1._invInertia12 = __tmp__122; + _this1._invInertia20 = __tmp__202; + _this1._invInertia21 = __tmp__212; + _this1._invInertia22 = __tmp__222; + let __tmp__003; + let __tmp__013; + let __tmp__023; + let __tmp__103; + let __tmp__113; + let __tmp__123; + let __tmp__203; + let __tmp__213; + let __tmp__223; + __tmp__003 = _this1._invInertia00 * _this1._transform._rotation00 + _this1._invInertia01 * _this1._transform._rotation01 + _this1._invInertia02 * _this1._transform._rotation02; + __tmp__013 = _this1._invInertia00 * _this1._transform._rotation10 + _this1._invInertia01 * _this1._transform._rotation11 + _this1._invInertia02 * _this1._transform._rotation12; + __tmp__023 = _this1._invInertia00 * _this1._transform._rotation20 + _this1._invInertia01 * _this1._transform._rotation21 + _this1._invInertia02 * _this1._transform._rotation22; + __tmp__103 = _this1._invInertia10 * _this1._transform._rotation00 + _this1._invInertia11 * _this1._transform._rotation01 + _this1._invInertia12 * _this1._transform._rotation02; + __tmp__113 = _this1._invInertia10 * _this1._transform._rotation10 + _this1._invInertia11 * _this1._transform._rotation11 + _this1._invInertia12 * _this1._transform._rotation12; + __tmp__123 = _this1._invInertia10 * _this1._transform._rotation20 + _this1._invInertia11 * _this1._transform._rotation21 + _this1._invInertia12 * _this1._transform._rotation22; + __tmp__203 = _this1._invInertia20 * _this1._transform._rotation00 + _this1._invInertia21 * _this1._transform._rotation01 + _this1._invInertia22 * _this1._transform._rotation02; + __tmp__213 = _this1._invInertia20 * _this1._transform._rotation10 + _this1._invInertia21 * _this1._transform._rotation11 + _this1._invInertia22 * _this1._transform._rotation12; + __tmp__223 = _this1._invInertia20 * _this1._transform._rotation20 + _this1._invInertia21 * _this1._transform._rotation21 + _this1._invInertia22 * _this1._transform._rotation22; + _this1._invInertia00 = __tmp__003; + _this1._invInertia01 = __tmp__013; + _this1._invInertia02 = __tmp__023; + _this1._invInertia10 = __tmp__103; + _this1._invInertia11 = __tmp__113; + _this1._invInertia12 = __tmp__123; + _this1._invInertia20 = __tmp__203; + _this1._invInertia21 = __tmp__213; + _this1._invInertia22 = __tmp__223; + _this1._invInertia00 *= _this1._rotFactor.x; + _this1._invInertia01 *= _this1._rotFactor.x; + _this1._invInertia02 *= _this1._rotFactor.x; + _this1._invInertia10 *= _this1._rotFactor.y; + _this1._invInertia11 *= _this1._rotFactor.y; + _this1._invInertia12 *= _this1._rotFactor.y; + _this1._invInertia20 *= _this1._rotFactor.z; + _this1._invInertia21 *= _this1._rotFactor.z; + _this1._invInertia22 *= _this1._rotFactor.z; + } + let _this = this.posBoundarySelector; + let i = 0; + while(_this.indices[i] != idx) ++i; + while(i > 0) { + let tmp = _this.indices[i]; + _this.indices[i] = _this.indices[i - 1]; + _this.indices[i - 1] = tmp; + --i; + } + solved = true; + break; + } + } + if(!solved) { + console.log("src/oimo/dynamics/constraint/solver/direct/DirectJointConstraintSolver.hx:502:","could not find solution. (NGS)"); + return; + } + } + postSolve() { + this.joint._syncAnchors(); + this.joint._checkDestruction(); + } +} +oimo.dynamics.constraint.solver.direct.MassMatrix = class oimo_dynamics_constraint_solver_direct_MassMatrix { + constructor(size) { + this._size = size; + this.tmpMatrix = new Array(this._size); + this._invMass = new Array(this._size); + this._invMassWithoutCfm = new Array(this._size); + let _g = 0; + let _g1 = this._size; + while(_g < _g1) { + let i = _g++; + this.tmpMatrix[i] = new Array(this._size); + this._invMass[i] = new Array(this._size); + this._invMassWithoutCfm[i] = new Array(this._size); + let _g1 = 0; + let _g2 = this._size; + while(_g1 < _g2) { + let j = _g1++; + this.tmpMatrix[i][j] = 0; + this._invMass[i][j] = 0; + this._invMassWithoutCfm[i][j] = 0; + } + } + this._maxSubmatrixId = 1 << this._size; + this._cacheComputed = new Array(this._maxSubmatrixId); + this._cachedSubmatrices = new Array(this._maxSubmatrixId); + let _g2 = 0; + let _g3 = this._maxSubmatrixId; + while(_g2 < _g3) { + let i = _g2++; + let t; + t = (i & 85) + (i >> 1 & 85); + t = (t & 51) + (t >> 2 & 51); + t = (t & 15) + (t >> 4 & 15); + let matrixSize = t; + let subMatrix = new Array(matrixSize); + let _g = 0; + while(_g < matrixSize) { + let j = _g++; + subMatrix[j] = new Array(matrixSize); + let _g1 = 0; + while(_g1 < matrixSize) subMatrix[j][_g1++] = 0; + } + this._cacheComputed[i] = false; + this._cachedSubmatrices[i] = subMatrix; + } + } + computeSubmatrix(id,indices,size) { + let _g = 0; + while(_g < size) { + let i = _g++; + let ii = indices[i]; + let _g1 = 0; + while(_g1 < size) { + let j = _g1++; + this.tmpMatrix[i][j] = this._invMass[ii][indices[j]]; + } + } + let src = this.tmpMatrix; + let dst = this._cachedSubmatrices[id]; + let srci; + let dsti; + let srcj; + let dstj; + let diag; + switch(size) { + case 4: + srci = src[0]; + dsti = dst[0]; + diag = 1 / srci[0]; + dsti[0] = diag; + srci[1] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srcj = src[1]; + dstj = dst[1]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srci = src[1]; + dsti = dst[1]; + diag = 1 / srci[1]; + dsti[1] = diag; + dsti[0] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srci = src[2]; + dsti = dst[2]; + diag = 1 / srci[2]; + dsti[2] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + srci[3] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srci = src[3]; + dsti = dst[3]; + diag = 1 / srci[3]; + dsti[3] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[3]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dsti = dst[1]; + dst[0][1] = dsti[0]; + dsti = dst[2]; + dst[0][2] = dsti[0]; + dst[1][2] = dsti[1]; + dsti = dst[3]; + dst[0][3] = dsti[0]; + dst[1][3] = dsti[1]; + dst[2][3] = dsti[2]; + break; + case 5: + srci = src[0]; + dsti = dst[0]; + diag = 1 / srci[0]; + dsti[0] = diag; + srci[1] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srcj = src[1]; + dstj = dst[1]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srci = src[1]; + dsti = dst[1]; + diag = 1 / srci[1]; + dsti[1] = diag; + dsti[0] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srci = src[2]; + dsti = dst[2]; + diag = 1 / srci[2]; + dsti[2] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srci = src[3]; + dsti = dst[3]; + diag = 1 / srci[3]; + dsti[3] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + srci[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dstj[3] = -diag * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srci = src[4]; + dsti = dst[4]; + diag = 1 / srci[4]; + dsti[4] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + dsti[3] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[4]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + dstj[3] -= dsti[3] * srcj[4]; + dsti = dst[1]; + dst[0][1] = dsti[0]; + dsti = dst[2]; + dst[0][2] = dsti[0]; + dst[1][2] = dsti[1]; + dsti = dst[3]; + dst[0][3] = dsti[0]; + dst[1][3] = dsti[1]; + dst[2][3] = dsti[2]; + dsti = dst[4]; + dst[0][4] = dsti[0]; + dst[1][4] = dsti[1]; + dst[2][4] = dsti[2]; + dst[3][4] = dsti[3]; + break; + case 6: + srci = src[0]; + dsti = dst[0]; + diag = 1 / srci[0]; + dsti[0] = diag; + srci[1] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[1]; + dstj = dst[1]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srci = src[1]; + dsti = dst[1]; + diag = 1 / srci[1]; + dsti[1] = diag; + dsti[0] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srci = src[2]; + dsti = dst[2]; + diag = 1 / srci[2]; + dsti[2] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srci = src[3]; + dsti = dst[3]; + diag = 1 / srci[3]; + dsti[3] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dstj[3] = -diag * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dstj[3] = -diag * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srci = src[4]; + dsti = dst[4]; + diag = 1 / srci[4]; + dsti[4] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + dsti[3] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + dstj[3] -= dsti[3] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + dstj[3] -= dsti[3] * srcj[4]; + dstj[4] = -diag * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srci = src[5]; + dsti = dst[5]; + diag = 1 / srci[5]; + dsti[5] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + dsti[3] *= diag; + dsti[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[5]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + dstj[2] -= dsti[2] * srcj[5]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + dstj[2] -= dsti[2] * srcj[5]; + dstj[3] -= dsti[3] * srcj[5]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + dstj[2] -= dsti[2] * srcj[5]; + dstj[3] -= dsti[3] * srcj[5]; + dstj[4] -= dsti[4] * srcj[5]; + dsti = dst[1]; + dst[0][1] = dsti[0]; + dsti = dst[2]; + dst[0][2] = dsti[0]; + dst[1][2] = dsti[1]; + dsti = dst[3]; + dst[0][3] = dsti[0]; + dst[1][3] = dsti[1]; + dst[2][3] = dsti[2]; + dsti = dst[4]; + dst[0][4] = dsti[0]; + dst[1][4] = dsti[1]; + dst[2][4] = dsti[2]; + dst[3][4] = dsti[3]; + dsti = dst[5]; + dst[0][5] = dsti[0]; + dst[1][5] = dsti[1]; + dst[2][5] = dsti[2]; + dst[3][5] = dsti[3]; + dst[4][5] = dsti[4]; + break; + default: + let _g1 = 0; + while(_g1 < size) { + let i = _g1++; + srci = src[i]; + dsti = dst[i]; + let diag = 1 / srci[i]; + dsti[i] = diag; + let _g = 0; + while(_g < i) dsti[_g++] *= diag; + let _g2 = i + 1; + while(_g2 < size) srci[_g2++] *= diag; + let _g3 = 0; + while(_g3 < i) { + let j = _g3++; + srcj = src[j]; + dstj = dst[j]; + let _g = 0; + let _g1 = j + 1; + while(_g < _g1) { + let k = _g++; + dstj[k] -= dsti[k] * srcj[i]; + } + let _g2 = i + 1; + while(_g2 < size) { + let k = _g2++; + srcj[k] -= srci[k] * srcj[i]; + } + } + let _g4 = i + 1; + while(_g4 < size) { + let j = _g4++; + srcj = src[j]; + dstj = dst[j]; + let _g = 0; + while(_g < i) { + let k = _g++; + dstj[k] -= dsti[k] * srcj[i]; + } + dstj[i] = -diag * srcj[i]; + let _g1 = i + 1; + while(_g1 < size) { + let k = _g1++; + srcj[k] -= srci[k] * srcj[i]; + } + } + } + let _g2 = 1; + while(_g2 < size) { + let i = _g2++; + dsti = dst[i]; + let _g = 0; + while(_g < i) { + let j = _g++; + dst[j][i] = dsti[j]; + } + } + } + } + computeInvMass(info,massData) { + let invMass = this._invMass; + let invMassWithoutCfm = this._invMassWithoutCfm; + let numRows = info.numRows; + let b1 = info.b1; + let b2 = info.b2; + let invM1 = b1._invMass; + let invM2 = b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = b1._invInertia00; + invI101 = b1._invInertia01; + invI102 = b1._invInertia02; + invI110 = b1._invInertia10; + invI111 = b1._invInertia11; + invI112 = b1._invInertia12; + invI120 = b1._invInertia20; + invI121 = b1._invInertia21; + invI122 = b1._invInertia22; + invI200 = b2._invInertia00; + invI201 = b2._invInertia01; + invI202 = b2._invInertia02; + invI210 = b2._invInertia10; + invI211 = b2._invInertia11; + invI212 = b2._invInertia12; + invI220 = b2._invInertia20; + invI221 = b2._invInertia21; + invI222 = b2._invInertia22; + let _g = 0; + while(_g < numRows) { + let i = _g++; + let j = info.rows[i].jacobian; + let md = massData[i]; + j.updateSparsity(); + if((j.flag & 1) != 0) { + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + } else { + md.invMLin1X = 0; + md.invMLin1Y = 0; + md.invMLin1Z = 0; + md.invMLin2X = 0; + md.invMLin2Y = 0; + md.invMLin2Z = 0; + } + if((j.flag & 2) != 0) { + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + } else { + md.invMAng1X = 0; + md.invMAng1Y = 0; + md.invMAng1Z = 0; + md.invMAng2X = 0; + md.invMAng2Y = 0; + md.invMAng2Z = 0; + } + } + let _g1 = 0; + while(_g1 < numRows) { + let i = _g1++; + let j1 = info.rows[i].jacobian; + let _g = i; + while(_g < numRows) { + let j = _g++; + let md2 = massData[j]; + let val = j1.lin1X * md2.invMLin1X + j1.lin1Y * md2.invMLin1Y + j1.lin1Z * md2.invMLin1Z + (j1.ang1X * md2.invMAng1X + j1.ang1Y * md2.invMAng1Y + j1.ang1Z * md2.invMAng1Z) + (j1.lin2X * md2.invMLin2X + j1.lin2Y * md2.invMLin2Y + j1.lin2Z * md2.invMLin2Z) + (j1.ang2X * md2.invMAng2X + j1.ang2Y * md2.invMAng2Y + j1.ang2Z * md2.invMAng2Z); + if(i == j) { + invMass[i][j] = val + info.rows[i].cfm; + invMassWithoutCfm[i][j] = val; + md2.mass = val + info.rows[i].cfm; + md2.massWithoutCfm = val; + if(md2.mass != 0) { + md2.mass = 1 / md2.mass; + } + if(md2.massWithoutCfm != 0) { + md2.massWithoutCfm = 1 / md2.massWithoutCfm; + } + } else { + invMass[i][j] = val; + invMass[j][i] = val; + invMassWithoutCfm[i][j] = val; + invMassWithoutCfm[j][i] = val; + } + } + } + let _g2 = 0; + let _g3 = this._maxSubmatrixId; + while(_g2 < _g3) this._cacheComputed[_g2++] = false; + } +} +if(!oimo.dynamics.constraint.solver.pgs) oimo.dynamics.constraint.solver.pgs = {}; +oimo.dynamics.constraint.solver.pgs.PgsContactConstraintSolver = class oimo_dynamics_constraint_solver_pgs_PgsContactConstraintSolver extends oimo.dynamics.constraint.ConstraintSolver { + constructor(constraint) { + super(); + this.constraint = constraint; + this.info = new oimo.dynamics.constraint.info.contact.ContactSolverInfo(); + this.massData = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = this.massData.length; + while(_g < _g1) this.massData[_g++] = new oimo.dynamics.constraint.solver.common.ContactSolverMassDataRow(); + } + preSolveVelocity(timeStep) { + this.constraint._getVelocitySolverInfo(timeStep,this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let j = row.jacobianN; + md.invMLinN1X = j.lin1X * invM1; + md.invMLinN1Y = j.lin1Y * invM1; + md.invMLinN1Z = j.lin1Z * invM1; + md.invMLinN2X = j.lin2X * invM2; + md.invMLinN2Y = j.lin2Y * invM2; + md.invMLinN2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAngN1X = __tmp__X; + md.invMAngN1Y = __tmp__Y; + md.invMAngN1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAngN2X = __tmp__X1; + md.invMAngN2Y = __tmp__Y1; + md.invMAngN2Z = __tmp__Z1; + md.massN = invM1 + invM2 + (md.invMAngN1X * j.ang1X + md.invMAngN1Y * j.ang1Y + md.invMAngN1Z * j.ang1Z) + (md.invMAngN2X * j.ang2X + md.invMAngN2Y * j.ang2Y + md.invMAngN2Z * j.ang2Z); + if(md.massN != 0) { + md.massN = 1 / md.massN; + } + let jt = row.jacobianT; + let jb = row.jacobianB; + md.invMLinT1X = jt.lin1X * invM1; + md.invMLinT1Y = jt.lin1Y * invM1; + md.invMLinT1Z = jt.lin1Z * invM1; + md.invMLinT2X = jt.lin2X * invM2; + md.invMLinT2Y = jt.lin2Y * invM2; + md.invMLinT2Z = jt.lin2Z * invM2; + md.invMLinB1X = jb.lin1X * invM1; + md.invMLinB1Y = jb.lin1Y * invM1; + md.invMLinB1Z = jb.lin1Z * invM1; + md.invMLinB2X = jb.lin2X * invM2; + md.invMLinB2Y = jb.lin2Y * invM2; + md.invMLinB2Z = jb.lin2Z * invM2; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = invI100 * jt.ang1X + invI101 * jt.ang1Y + invI102 * jt.ang1Z; + __tmp__Y2 = invI110 * jt.ang1X + invI111 * jt.ang1Y + invI112 * jt.ang1Z; + __tmp__Z2 = invI120 * jt.ang1X + invI121 * jt.ang1Y + invI122 * jt.ang1Z; + md.invMAngT1X = __tmp__X2; + md.invMAngT1Y = __tmp__Y2; + md.invMAngT1Z = __tmp__Z2; + let __tmp__X3; + let __tmp__Y3; + let __tmp__Z3; + __tmp__X3 = invI200 * jt.ang2X + invI201 * jt.ang2Y + invI202 * jt.ang2Z; + __tmp__Y3 = invI210 * jt.ang2X + invI211 * jt.ang2Y + invI212 * jt.ang2Z; + __tmp__Z3 = invI220 * jt.ang2X + invI221 * jt.ang2Y + invI222 * jt.ang2Z; + md.invMAngT2X = __tmp__X3; + md.invMAngT2Y = __tmp__Y3; + md.invMAngT2Z = __tmp__Z3; + let __tmp__X4; + let __tmp__Y4; + let __tmp__Z4; + __tmp__X4 = invI100 * jb.ang1X + invI101 * jb.ang1Y + invI102 * jb.ang1Z; + __tmp__Y4 = invI110 * jb.ang1X + invI111 * jb.ang1Y + invI112 * jb.ang1Z; + __tmp__Z4 = invI120 * jb.ang1X + invI121 * jb.ang1Y + invI122 * jb.ang1Z; + md.invMAngB1X = __tmp__X4; + md.invMAngB1Y = __tmp__Y4; + md.invMAngB1Z = __tmp__Z4; + let __tmp__X5; + let __tmp__Y5; + let __tmp__Z5; + __tmp__X5 = invI200 * jb.ang2X + invI201 * jb.ang2Y + invI202 * jb.ang2Z; + __tmp__Y5 = invI210 * jb.ang2X + invI211 * jb.ang2Y + invI212 * jb.ang2Z; + __tmp__Z5 = invI220 * jb.ang2X + invI221 * jb.ang2Y + invI222 * jb.ang2Z; + md.invMAngB2X = __tmp__X5; + md.invMAngB2Y = __tmp__Y5; + md.invMAngB2Z = __tmp__Z5; + let invMassTB00 = invM1 + invM2 + (md.invMAngT1X * jt.ang1X + md.invMAngT1Y * jt.ang1Y + md.invMAngT1Z * jt.ang1Z) + (md.invMAngT2X * jt.ang2X + md.invMAngT2Y * jt.ang2Y + md.invMAngT2Z * jt.ang2Z); + let invMassTB01 = md.invMAngT1X * jb.ang1X + md.invMAngT1Y * jb.ang1Y + md.invMAngT1Z * jb.ang1Z + (md.invMAngT2X * jb.ang2X + md.invMAngT2Y * jb.ang2Y + md.invMAngT2Z * jb.ang2Z); + let invMassTB11 = invM1 + invM2 + (md.invMAngB1X * jb.ang1X + md.invMAngB1Y * jb.ang1Y + md.invMAngB1Z * jb.ang1Z) + (md.invMAngB2X * jb.ang2X + md.invMAngB2Y * jb.ang2Y + md.invMAngB2Z * jb.ang2Z); + let invDet = invMassTB00 * invMassTB11 - invMassTB01 * invMassTB01; + if(invDet != 0) { + invDet = 1 / invDet; + } + md.massTB00 = invMassTB11 * invDet; + md.massTB01 = -invMassTB01 * invDet; + md.massTB10 = -invMassTB01 * invDet; + md.massTB11 = invMassTB00 * invDet; + } + } + warmStart(timeStep) { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let imp = row.impulse; + let md = this.massData[i]; + let jt = row.jacobianT; + let jb = row.jacobianB; + let impulseN = imp.impulseN; + let impulseT = imp.impulseLX * jt.lin1X + imp.impulseLY * jt.lin1Y + imp.impulseLZ * jt.lin1Z; + let impulseB = imp.impulseLX * jb.lin1X + imp.impulseLY * jb.lin1Y + imp.impulseLZ * jb.lin1Z; + imp.impulseT = impulseT; + imp.impulseB = impulseB; + imp.impulseN *= timeStep.dtRatio; + imp.impulseT *= timeStep.dtRatio; + imp.impulseB *= timeStep.dtRatio; + lv1X += md.invMLinN1X * impulseN; + lv1Y += md.invMLinN1Y * impulseN; + lv1Z += md.invMLinN1Z * impulseN; + lv1X += md.invMLinT1X * impulseT; + lv1Y += md.invMLinT1Y * impulseT; + lv1Z += md.invMLinT1Z * impulseT; + lv1X += md.invMLinB1X * impulseB; + lv1Y += md.invMLinB1Y * impulseB; + lv1Z += md.invMLinB1Z * impulseB; + lv2X += md.invMLinN2X * -impulseN; + lv2Y += md.invMLinN2Y * -impulseN; + lv2Z += md.invMLinN2Z * -impulseN; + lv2X += md.invMLinT2X * -impulseT; + lv2Y += md.invMLinT2Y * -impulseT; + lv2Z += md.invMLinT2Z * -impulseT; + lv2X += md.invMLinB2X * -impulseB; + lv2Y += md.invMLinB2Y * -impulseB; + lv2Z += md.invMLinB2Z * -impulseB; + av1X += md.invMAngN1X * impulseN; + av1Y += md.invMAngN1Y * impulseN; + av1Z += md.invMAngN1Z * impulseN; + av1X += md.invMAngT1X * impulseT; + av1Y += md.invMAngT1Y * impulseT; + av1Z += md.invMAngT1Z * impulseT; + av1X += md.invMAngB1X * impulseB; + av1Y += md.invMAngB1Y * impulseB; + av1Z += md.invMAngB1Z * impulseB; + av2X += md.invMAngN2X * -impulseN; + av2Y += md.invMAngN2Y * -impulseN; + av2Z += md.invMAngN2Z * -impulseN; + av2X += md.invMAngT2X * -impulseT; + av2Y += md.invMAngT2Y * -impulseT; + av2Z += md.invMAngT2Z * -impulseT; + av2X += md.invMAngB2X * -impulseB; + av2Y += md.invMAngB2Y * -impulseB; + av2Z += md.invMAngB2Z * -impulseB; + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + solveVelocity() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let rvt = 0; + let j = row.jacobianT; + rvt += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvt -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvt += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvt -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let rvb = 0; + j = row.jacobianB; + rvb += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvb -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvb += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvb -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseT = -(rvt * md.massTB00 + rvb * md.massTB01); + let impulseB = -(rvt * md.massTB10 + rvb * md.massTB11); + let oldImpulseT = imp.impulseT; + let oldImpulseB = imp.impulseB; + imp.impulseT += impulseT; + imp.impulseB += impulseB; + let maxImpulse = row.friction * imp.impulseN; + if(maxImpulse == 0) { + imp.impulseT = 0; + imp.impulseB = 0; + } else { + let impulseLengthSq = imp.impulseT * imp.impulseT + imp.impulseB * imp.impulseB; + if(impulseLengthSq > maxImpulse * maxImpulse) { + let invL = maxImpulse / Math.sqrt(impulseLengthSq); + imp.impulseT *= invL; + imp.impulseB *= invL; + } + } + impulseT = imp.impulseT - oldImpulseT; + impulseB = imp.impulseB - oldImpulseB; + lv1X += md.invMLinT1X * impulseT; + lv1Y += md.invMLinT1Y * impulseT; + lv1Z += md.invMLinT1Z * impulseT; + lv1X += md.invMLinB1X * impulseB; + lv1Y += md.invMLinB1Y * impulseB; + lv1Z += md.invMLinB1Z * impulseB; + lv2X += md.invMLinT2X * -impulseT; + lv2Y += md.invMLinT2Y * -impulseT; + lv2Z += md.invMLinT2Z * -impulseT; + lv2X += md.invMLinB2X * -impulseB; + lv2Y += md.invMLinB2Y * -impulseB; + lv2Z += md.invMLinB2Z * -impulseB; + av1X += md.invMAngT1X * impulseT; + av1Y += md.invMAngT1Y * impulseT; + av1Z += md.invMAngT1Z * impulseT; + av1X += md.invMAngB1X * impulseB; + av1Y += md.invMAngB1Y * impulseB; + av1Z += md.invMAngB1Z * impulseB; + av2X += md.invMAngT2X * -impulseT; + av2Y += md.invMAngT2Y * -impulseT; + av2Z += md.invMAngT2Z * -impulseT; + av2X += md.invMAngB2X * -impulseB; + av2Y += md.invMAngB2Y * -impulseB; + av2Z += md.invMAngB2Z * -impulseB; + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let rvn = 0; + let j = row.jacobianN; + rvn += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvn -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvn += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvn -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseN = (row.rhs - rvn) * md.massN; + let oldImpulseN = imp.impulseN; + imp.impulseN += impulseN; + if(imp.impulseN < 0) { + imp.impulseN = 0; + } + impulseN = imp.impulseN - oldImpulseN; + lv1X += md.invMLinN1X * impulseN; + lv1Y += md.invMLinN1Y * impulseN; + lv1Z += md.invMLinN1Z * impulseN; + lv2X += md.invMLinN2X * -impulseN; + lv2Y += md.invMLinN2Y * -impulseN; + lv2Z += md.invMLinN2Z * -impulseN; + av1X += md.invMAngN1X * impulseN; + av1Y += md.invMAngN1Y * impulseN; + av1Z += md.invMAngN1Z * impulseN; + av2X += md.invMAngN2X * -impulseN; + av2Y += md.invMAngN2Y * -impulseN; + av2Z += md.invMAngN2Z * -impulseN; + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + preSolvePosition(timeStep) { + this.constraint._syncManifold(); + this.constraint._getPositionSolverInfo(this.info); + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobianN; + md.invMLinN1X = j.lin1X * invM1; + md.invMLinN1Y = j.lin1Y * invM1; + md.invMLinN1Z = j.lin1Z * invM1; + md.invMLinN2X = j.lin2X * invM2; + md.invMLinN2Y = j.lin2Y * invM2; + md.invMLinN2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAngN1X = __tmp__X; + md.invMAngN1Y = __tmp__Y; + md.invMAngN1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAngN2X = __tmp__X1; + md.invMAngN2Y = __tmp__Y1; + md.invMAngN2Z = __tmp__Z1; + md.massN = invM1 + invM2 + (md.invMAngN1X * j.ang1X + md.invMAngN1Y * j.ang1Y + md.invMAngN1Z * j.ang1Z) + (md.invMAngN2X * j.ang2X + md.invMAngN2Y * j.ang2Y + md.invMAngN2Z * j.ang2Z); + if(md.massN != 0) { + md.massN = 1 / md.massN; + } + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) this.info.rows[_g2++].impulse.impulseP = 0; + } + solvePositionSplitImpulse() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobianN; + let rvn = 0; + rvn += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvn -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvn += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvn -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs - rvn) * md.massN * oimo.common.Setting.positionSplitImpulseBaumgarte; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < 0) { + imp.impulseP = 0; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLinN1X * impulseP; + lv1Y += md.invMLinN1Y * impulseP; + lv1Z += md.invMLinN1Z * impulseP; + lv2X += md.invMLinN2X * -impulseP; + lv2Y += md.invMLinN2Y * -impulseP; + lv2Z += md.invMLinN2Z * -impulseP; + av1X += md.invMAngN1X * impulseP; + av1Y += md.invMAngN1Y * impulseP; + av1Z += md.invMAngN1Z * impulseP; + av2X += md.invMAngN2X * -impulseP; + av2Y += md.invMAngN2Y * -impulseP; + av2Z += md.invMAngN2Z * -impulseP; + } + this._b1._pseudoVelX = lv1X; + this._b1._pseudoVelY = lv1Y; + this._b1._pseudoVelZ = lv1Z; + this._b2._pseudoVelX = lv2X; + this._b2._pseudoVelY = lv2Y; + this._b2._pseudoVelZ = lv2Z; + this._b1._angPseudoVelX = av1X; + this._b1._angPseudoVelY = av1Y; + this._b1._angPseudoVelZ = av1Z; + this._b2._angPseudoVelX = av2X; + this._b2._angPseudoVelY = av2Y; + this._b2._angPseudoVelZ = av2Z; + } + solvePositionNgs(timeStep) { + this.constraint._syncManifold(); + this.constraint._getPositionSolverInfo(this.info); + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobianN; + md.invMLinN1X = j.lin1X * invM1; + md.invMLinN1Y = j.lin1Y * invM1; + md.invMLinN1Z = j.lin1Z * invM1; + md.invMLinN2X = j.lin2X * invM2; + md.invMLinN2Y = j.lin2Y * invM2; + md.invMLinN2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAngN1X = __tmp__X; + md.invMAngN1Y = __tmp__Y; + md.invMAngN1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAngN2X = __tmp__X1; + md.invMAngN2Y = __tmp__Y1; + md.invMAngN2Z = __tmp__Z1; + md.massN = invM1 + invM2 + (md.invMAngN1X * j.ang1X + md.invMAngN1Y * j.ang1Y + md.invMAngN1Z * j.ang1Z) + (md.invMAngN2X * j.ang2X + md.invMAngN2Y * j.ang2Y + md.invMAngN2Z * j.ang2Z); + if(md.massN != 0) { + md.massN = 1 / md.massN; + } + } + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = 0; + lv1Y = 0; + lv1Z = 0; + lv2X = 0; + lv2Y = 0; + lv2Z = 0; + av1X = 0; + av1Y = 0; + av1Z = 0; + av2X = 0; + av2Y = 0; + av2Z = 0; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobianN; + let rvn = 0; + rvn += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvn -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvn += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvn -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs - rvn) * md.massN * oimo.common.Setting.positionNgsBaumgarte; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < 0) { + imp.impulseP = 0; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLinN1X * impulseP; + lv1Y += md.invMLinN1Y * impulseP; + lv1Z += md.invMLinN1Z * impulseP; + lv2X += md.invMLinN2X * -impulseP; + lv2Y += md.invMLinN2Y * -impulseP; + lv2Z += md.invMLinN2Z * -impulseP; + av1X += md.invMAngN1X * impulseP; + av1Y += md.invMAngN1Y * impulseP; + av1Z += md.invMAngN1Z * impulseP; + av2X += md.invMAngN2X * -impulseP; + av2Y += md.invMAngN2Y * -impulseP; + av2Z += md.invMAngN2Z * -impulseP; + } + let _this = this._b1; + _this._transform._positionX += lv1X; + _this._transform._positionY += lv1Y; + _this._transform._positionZ += lv1Z; + let _this1 = this._b2; + _this1._transform._positionX += lv2X; + _this1._transform._positionY += lv2Y; + _this1._transform._positionZ += lv2Z; + let _this2 = this._b1; + let theta = Math.sqrt(av1X * av1X + av1Y * av1Y + av1Z * av1Z); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = av1X * rotationToSinAxisFactor; + sinAxisY = av1Y * rotationToSinAxisFactor; + sinAxisZ = av1Z * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = _this2._transform._rotation00; + let e11 = _this2._transform._rotation11; + let e22 = _this2._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + qY = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + qZ = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qW = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this2._transform._rotation00 = 1 - yy - zz; + _this2._transform._rotation01 = xy - wz; + _this2._transform._rotation02 = xz + wy; + _this2._transform._rotation10 = xy + wz; + _this2._transform._rotation11 = 1 - xx - zz; + _this2._transform._rotation12 = yz - wx; + _this2._transform._rotation20 = xz - wy; + _this2._transform._rotation21 = yz + wx; + _this2._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = _this2._transform._rotation00 * _this2._invLocalInertia00 + _this2._transform._rotation01 * _this2._invLocalInertia10 + _this2._transform._rotation02 * _this2._invLocalInertia20; + __tmp__01 = _this2._transform._rotation00 * _this2._invLocalInertia01 + _this2._transform._rotation01 * _this2._invLocalInertia11 + _this2._transform._rotation02 * _this2._invLocalInertia21; + __tmp__02 = _this2._transform._rotation00 * _this2._invLocalInertia02 + _this2._transform._rotation01 * _this2._invLocalInertia12 + _this2._transform._rotation02 * _this2._invLocalInertia22; + __tmp__10 = _this2._transform._rotation10 * _this2._invLocalInertia00 + _this2._transform._rotation11 * _this2._invLocalInertia10 + _this2._transform._rotation12 * _this2._invLocalInertia20; + __tmp__11 = _this2._transform._rotation10 * _this2._invLocalInertia01 + _this2._transform._rotation11 * _this2._invLocalInertia11 + _this2._transform._rotation12 * _this2._invLocalInertia21; + __tmp__12 = _this2._transform._rotation10 * _this2._invLocalInertia02 + _this2._transform._rotation11 * _this2._invLocalInertia12 + _this2._transform._rotation12 * _this2._invLocalInertia22; + __tmp__20 = _this2._transform._rotation20 * _this2._invLocalInertia00 + _this2._transform._rotation21 * _this2._invLocalInertia10 + _this2._transform._rotation22 * _this2._invLocalInertia20; + __tmp__21 = _this2._transform._rotation20 * _this2._invLocalInertia01 + _this2._transform._rotation21 * _this2._invLocalInertia11 + _this2._transform._rotation22 * _this2._invLocalInertia21; + __tmp__22 = _this2._transform._rotation20 * _this2._invLocalInertia02 + _this2._transform._rotation21 * _this2._invLocalInertia12 + _this2._transform._rotation22 * _this2._invLocalInertia22; + _this2._invInertia00 = __tmp__00; + _this2._invInertia01 = __tmp__01; + _this2._invInertia02 = __tmp__02; + _this2._invInertia10 = __tmp__10; + _this2._invInertia11 = __tmp__11; + _this2._invInertia12 = __tmp__12; + _this2._invInertia20 = __tmp__20; + _this2._invInertia21 = __tmp__21; + _this2._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = _this2._invInertia00 * _this2._transform._rotation00 + _this2._invInertia01 * _this2._transform._rotation01 + _this2._invInertia02 * _this2._transform._rotation02; + __tmp__011 = _this2._invInertia00 * _this2._transform._rotation10 + _this2._invInertia01 * _this2._transform._rotation11 + _this2._invInertia02 * _this2._transform._rotation12; + __tmp__021 = _this2._invInertia00 * _this2._transform._rotation20 + _this2._invInertia01 * _this2._transform._rotation21 + _this2._invInertia02 * _this2._transform._rotation22; + __tmp__101 = _this2._invInertia10 * _this2._transform._rotation00 + _this2._invInertia11 * _this2._transform._rotation01 + _this2._invInertia12 * _this2._transform._rotation02; + __tmp__111 = _this2._invInertia10 * _this2._transform._rotation10 + _this2._invInertia11 * _this2._transform._rotation11 + _this2._invInertia12 * _this2._transform._rotation12; + __tmp__121 = _this2._invInertia10 * _this2._transform._rotation20 + _this2._invInertia11 * _this2._transform._rotation21 + _this2._invInertia12 * _this2._transform._rotation22; + __tmp__201 = _this2._invInertia20 * _this2._transform._rotation00 + _this2._invInertia21 * _this2._transform._rotation01 + _this2._invInertia22 * _this2._transform._rotation02; + __tmp__211 = _this2._invInertia20 * _this2._transform._rotation10 + _this2._invInertia21 * _this2._transform._rotation11 + _this2._invInertia22 * _this2._transform._rotation12; + __tmp__221 = _this2._invInertia20 * _this2._transform._rotation20 + _this2._invInertia21 * _this2._transform._rotation21 + _this2._invInertia22 * _this2._transform._rotation22; + _this2._invInertia00 = __tmp__001; + _this2._invInertia01 = __tmp__011; + _this2._invInertia02 = __tmp__021; + _this2._invInertia10 = __tmp__101; + _this2._invInertia11 = __tmp__111; + _this2._invInertia12 = __tmp__121; + _this2._invInertia20 = __tmp__201; + _this2._invInertia21 = __tmp__211; + _this2._invInertia22 = __tmp__221; + _this2._invInertia00 *= _this2._rotFactor.x; + _this2._invInertia01 *= _this2._rotFactor.x; + _this2._invInertia02 *= _this2._rotFactor.x; + _this2._invInertia10 *= _this2._rotFactor.y; + _this2._invInertia11 *= _this2._rotFactor.y; + _this2._invInertia12 *= _this2._rotFactor.y; + _this2._invInertia20 *= _this2._rotFactor.z; + _this2._invInertia21 *= _this2._rotFactor.z; + _this2._invInertia22 *= _this2._rotFactor.z; + let _this3 = this._b2; + let theta1 = Math.sqrt(av2X * av2X + av2Y * av2Y + av2Z * av2Z); + let halfTheta1 = theta1 * 0.5; + let rotationToSinAxisFactor1; + let cosHalfTheta1; + if(halfTheta1 < 0.5) { + let ht2 = halfTheta1 * halfTheta1; + rotationToSinAxisFactor1 = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta1 = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor1 = Math.sin(halfTheta1) / theta1; + cosHalfTheta1 = Math.cos(halfTheta1); + } + let sinAxisX1; + let sinAxisY1; + let sinAxisZ1; + sinAxisX1 = av2X * rotationToSinAxisFactor1; + sinAxisY1 = av2Y * rotationToSinAxisFactor1; + sinAxisZ1 = av2Z * rotationToSinAxisFactor1; + let dqX1; + let dqY1; + let dqZ1; + let dqW1; + dqX1 = sinAxisX1; + dqY1 = sinAxisY1; + dqZ1 = sinAxisZ1; + dqW1 = cosHalfTheta1; + let qX1; + let qY1; + let qZ1; + let qW1; + let e001 = _this3._transform._rotation00; + let e111 = _this3._transform._rotation11; + let e221 = _this3._transform._rotation22; + let t1 = e001 + e111 + e221; + let s1; + if(t1 > 0) { + s1 = Math.sqrt(t1 + 1); + qW1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + qY1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + qZ1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } else if(e001 > e111) { + if(e001 > e221) { + s1 = Math.sqrt(e001 - e111 - e221 + 1); + qX1 = 0.5 * s1; + s1 = 0.5 / s1; + qY1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qW1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + } else if(e111 > e221) { + s1 = Math.sqrt(e111 - e221 - e001 + 1); + qY1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + qX1 = dqW1 * qX1 + dqX1 * qW1 + dqY1 * qZ1 - dqZ1 * qY1; + qY1 = dqW1 * qY1 - dqX1 * qZ1 + dqY1 * qW1 + dqZ1 * qX1; + qZ1 = dqW1 * qZ1 + dqX1 * qY1 - dqY1 * qX1 + dqZ1 * qW1; + qW1 = dqW1 * qW1 - dqX1 * qX1 - dqY1 * qY1 - dqZ1 * qZ1; + let l1 = qX1 * qX1 + qY1 * qY1 + qZ1 * qZ1 + qW1 * qW1; + if(l1 > 1e-32) { + l1 = 1 / Math.sqrt(l1); + } + qX1 *= l1; + qY1 *= l1; + qZ1 *= l1; + qW1 *= l1; + let x1 = qX1; + let y1 = qY1; + let z1 = qZ1; + let w1 = qW1; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + _this3._transform._rotation00 = 1 - yy1 - zz1; + _this3._transform._rotation01 = xy1 - wz1; + _this3._transform._rotation02 = xz1 + wy1; + _this3._transform._rotation10 = xy1 + wz1; + _this3._transform._rotation11 = 1 - xx1 - zz1; + _this3._transform._rotation12 = yz1 - wx1; + _this3._transform._rotation20 = xz1 - wy1; + _this3._transform._rotation21 = yz1 + wx1; + _this3._transform._rotation22 = 1 - xx1 - yy1; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = _this3._transform._rotation00 * _this3._invLocalInertia00 + _this3._transform._rotation01 * _this3._invLocalInertia10 + _this3._transform._rotation02 * _this3._invLocalInertia20; + __tmp__012 = _this3._transform._rotation00 * _this3._invLocalInertia01 + _this3._transform._rotation01 * _this3._invLocalInertia11 + _this3._transform._rotation02 * _this3._invLocalInertia21; + __tmp__022 = _this3._transform._rotation00 * _this3._invLocalInertia02 + _this3._transform._rotation01 * _this3._invLocalInertia12 + _this3._transform._rotation02 * _this3._invLocalInertia22; + __tmp__102 = _this3._transform._rotation10 * _this3._invLocalInertia00 + _this3._transform._rotation11 * _this3._invLocalInertia10 + _this3._transform._rotation12 * _this3._invLocalInertia20; + __tmp__112 = _this3._transform._rotation10 * _this3._invLocalInertia01 + _this3._transform._rotation11 * _this3._invLocalInertia11 + _this3._transform._rotation12 * _this3._invLocalInertia21; + __tmp__122 = _this3._transform._rotation10 * _this3._invLocalInertia02 + _this3._transform._rotation11 * _this3._invLocalInertia12 + _this3._transform._rotation12 * _this3._invLocalInertia22; + __tmp__202 = _this3._transform._rotation20 * _this3._invLocalInertia00 + _this3._transform._rotation21 * _this3._invLocalInertia10 + _this3._transform._rotation22 * _this3._invLocalInertia20; + __tmp__212 = _this3._transform._rotation20 * _this3._invLocalInertia01 + _this3._transform._rotation21 * _this3._invLocalInertia11 + _this3._transform._rotation22 * _this3._invLocalInertia21; + __tmp__222 = _this3._transform._rotation20 * _this3._invLocalInertia02 + _this3._transform._rotation21 * _this3._invLocalInertia12 + _this3._transform._rotation22 * _this3._invLocalInertia22; + _this3._invInertia00 = __tmp__002; + _this3._invInertia01 = __tmp__012; + _this3._invInertia02 = __tmp__022; + _this3._invInertia10 = __tmp__102; + _this3._invInertia11 = __tmp__112; + _this3._invInertia12 = __tmp__122; + _this3._invInertia20 = __tmp__202; + _this3._invInertia21 = __tmp__212; + _this3._invInertia22 = __tmp__222; + let __tmp__003; + let __tmp__013; + let __tmp__023; + let __tmp__103; + let __tmp__113; + let __tmp__123; + let __tmp__203; + let __tmp__213; + let __tmp__223; + __tmp__003 = _this3._invInertia00 * _this3._transform._rotation00 + _this3._invInertia01 * _this3._transform._rotation01 + _this3._invInertia02 * _this3._transform._rotation02; + __tmp__013 = _this3._invInertia00 * _this3._transform._rotation10 + _this3._invInertia01 * _this3._transform._rotation11 + _this3._invInertia02 * _this3._transform._rotation12; + __tmp__023 = _this3._invInertia00 * _this3._transform._rotation20 + _this3._invInertia01 * _this3._transform._rotation21 + _this3._invInertia02 * _this3._transform._rotation22; + __tmp__103 = _this3._invInertia10 * _this3._transform._rotation00 + _this3._invInertia11 * _this3._transform._rotation01 + _this3._invInertia12 * _this3._transform._rotation02; + __tmp__113 = _this3._invInertia10 * _this3._transform._rotation10 + _this3._invInertia11 * _this3._transform._rotation11 + _this3._invInertia12 * _this3._transform._rotation12; + __tmp__123 = _this3._invInertia10 * _this3._transform._rotation20 + _this3._invInertia11 * _this3._transform._rotation21 + _this3._invInertia12 * _this3._transform._rotation22; + __tmp__203 = _this3._invInertia20 * _this3._transform._rotation00 + _this3._invInertia21 * _this3._transform._rotation01 + _this3._invInertia22 * _this3._transform._rotation02; + __tmp__213 = _this3._invInertia20 * _this3._transform._rotation10 + _this3._invInertia21 * _this3._transform._rotation11 + _this3._invInertia22 * _this3._transform._rotation12; + __tmp__223 = _this3._invInertia20 * _this3._transform._rotation20 + _this3._invInertia21 * _this3._transform._rotation21 + _this3._invInertia22 * _this3._transform._rotation22; + _this3._invInertia00 = __tmp__003; + _this3._invInertia01 = __tmp__013; + _this3._invInertia02 = __tmp__023; + _this3._invInertia10 = __tmp__103; + _this3._invInertia11 = __tmp__113; + _this3._invInertia12 = __tmp__123; + _this3._invInertia20 = __tmp__203; + _this3._invInertia21 = __tmp__213; + _this3._invInertia22 = __tmp__223; + _this3._invInertia00 *= _this3._rotFactor.x; + _this3._invInertia01 *= _this3._rotFactor.x; + _this3._invInertia02 *= _this3._rotFactor.x; + _this3._invInertia10 *= _this3._rotFactor.y; + _this3._invInertia11 *= _this3._rotFactor.y; + _this3._invInertia12 *= _this3._rotFactor.y; + _this3._invInertia20 *= _this3._rotFactor.z; + _this3._invInertia21 *= _this3._rotFactor.z; + _this3._invInertia22 *= _this3._rotFactor.z; + } + postSolve() { + let lin1X; + let lin1Y; + let lin1Z; + let ang1X; + let ang1Y; + let ang1Z; + let ang2X; + let ang2Y; + let ang2Z; + lin1X = 0; + lin1Y = 0; + lin1Z = 0; + ang1X = 0; + ang1Y = 0; + ang1Z = 0; + ang2X = 0; + ang2Y = 0; + ang2Z = 0; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let row = this.info.rows[_g++]; + let imp = row.impulse; + let jn = row.jacobianN; + let jt = row.jacobianT; + let jb = row.jacobianB; + let impN = imp.impulseN; + let impT = imp.impulseT; + let impB = imp.impulseB; + let impulseLX; + let impulseLY; + let impulseLZ; + impulseLX = 0; + impulseLY = 0; + impulseLZ = 0; + impulseLX += jt.lin1X * impT; + impulseLY += jt.lin1Y * impT; + impulseLZ += jt.lin1Z * impT; + impulseLX += jb.lin1X * impB; + impulseLY += jb.lin1Y * impB; + impulseLZ += jb.lin1Z * impB; + imp.impulseLX = impulseLX; + imp.impulseLY = impulseLY; + imp.impulseLZ = impulseLZ; + lin1X += jn.lin1X * impN; + lin1Y += jn.lin1Y * impN; + lin1Z += jn.lin1Z * impN; + ang1X += jn.ang1X * impN; + ang1Y += jn.ang1Y * impN; + ang1Z += jn.ang1Z * impN; + ang2X += jn.ang2X * impN; + ang2Y += jn.ang2Y * impN; + ang2Z += jn.ang2Z * impN; + lin1X += jt.lin1X * impT; + lin1Y += jt.lin1Y * impT; + lin1Z += jt.lin1Z * impT; + ang1X += jt.ang1X * impT; + ang1Y += jt.ang1Y * impT; + ang1Z += jt.ang1Z * impT; + ang2X += jt.ang2X * impT; + ang2Y += jt.ang2Y * impT; + ang2Z += jt.ang2Z * impT; + lin1X += jb.lin1X * impB; + lin1Y += jb.lin1Y * impB; + lin1Z += jb.lin1Z * impB; + ang1X += jb.ang1X * impB; + ang1Y += jb.ang1Y * impB; + ang1Z += jb.ang1Z * impB; + ang2X += jb.ang2X * impB; + ang2Y += jb.ang2Y * impB; + ang2Z += jb.ang2Z * impB; + } + this._b1._linearContactImpulseX += lin1X; + this._b1._linearContactImpulseY += lin1Y; + this._b1._linearContactImpulseZ += lin1Z; + this._b1._angularContactImpulseX += ang1X; + this._b1._angularContactImpulseY += ang1Y; + this._b1._angularContactImpulseZ += ang1Z; + this._b2._linearContactImpulseX -= lin1X; + this._b2._linearContactImpulseY -= lin1Y; + this._b2._linearContactImpulseZ -= lin1Z; + this._b2._angularContactImpulseX -= ang2X; + this._b2._angularContactImpulseY -= ang2Y; + this._b2._angularContactImpulseZ -= ang2Z; + this.constraint._syncManifold(); + } +} +oimo.dynamics.constraint.solver.pgs.PgsJointConstraintSolver = class oimo_dynamics_constraint_solver_pgs_PgsJointConstraintSolver extends oimo.dynamics.constraint.ConstraintSolver { + constructor(joint) { + super(); + this.joint = joint; + this.info = new oimo.dynamics.constraint.info.joint.JointSolverInfo(); + this.massData = new Array(oimo.common.Setting.maxJacobianRows); + let _g = 0; + let _g1 = this.massData.length; + while(_g < _g1) this.massData[_g++] = new oimo.dynamics.constraint.solver.common.JointSolverMassDataRow(); + } + preSolveVelocity(timeStep) { + this.joint._syncAnchors(); + this.joint._getVelocitySolverInfo(timeStep,this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let j = row.jacobian; + j.updateSparsity(); + if((j.flag & 1) != 0) { + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + } else { + md.invMLin1X = 0; + md.invMLin1Y = 0; + md.invMLin1Z = 0; + md.invMLin2X = 0; + md.invMLin2Y = 0; + md.invMLin2Z = 0; + } + if((j.flag & 2) != 0) { + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + } else { + md.invMAng1X = 0; + md.invMAng1Y = 0; + md.invMAng1Z = 0; + md.invMAng2X = 0; + md.invMAng2Y = 0; + md.invMAng2Z = 0; + } + md.massWithoutCfm = md.invMLin1X * j.lin1X + md.invMLin1Y * j.lin1Y + md.invMLin1Z * j.lin1Z + (md.invMLin2X * j.lin2X + md.invMLin2Y * j.lin2Y + md.invMLin2Z * j.lin2Z) + (md.invMAng1X * j.ang1X + md.invMAng1Y * j.ang1Y + md.invMAng1Z * j.ang1Z) + (md.invMAng2X * j.ang2X + md.invMAng2Y * j.ang2Y + md.invMAng2Z * j.ang2Z); + md.mass = md.massWithoutCfm + row.cfm; + if(md.massWithoutCfm != 0) { + md.massWithoutCfm = 1 / md.massWithoutCfm; + } + if(md.mass != 0) { + md.mass = 1 / md.mass; + } + } + } + warmStart(timeStep) { + let factor = this.joint._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE ? oimo.common.Setting.jointWarmStartingFactorForBaungarte : oimo.common.Setting.jointWarmStartingFactor; + factor *= timeStep.dtRatio; + if(factor <= 0) { + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let _this = this.info.rows[_g++].impulse; + _this.impulse = 0; + _this.impulseM = 0; + _this.impulseP = 0; + } + return; + } + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let imp = this.info.rows[i].impulse; + imp.impulse *= factor; + imp.impulseM *= factor; + let impulse = imp.impulse + imp.impulseM; + lv1X += md.invMLin1X * impulse; + lv1Y += md.invMLin1Y * impulse; + lv1Z += md.invMLin1Z * impulse; + lv2X += md.invMLin2X * -impulse; + lv2Y += md.invMLin2Y * -impulse; + lv2Z += md.invMLin2Z * -impulse; + av1X += md.invMAng1X * impulse; + av1Y += md.invMAng1Y * impulse; + av1Z += md.invMAng1Z * impulse; + av2X += md.invMAng2X * -impulse; + av2Y += md.invMAng2Y * -impulse; + av2Z += md.invMAng2Z * -impulse; + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + solveVelocity() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + if(row.motorMaxImpulse == 0) { + continue; + } + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseM = (-row.motorSpeed - rv) * md.massWithoutCfm; + let oldImpulseM = imp.impulseM; + imp.impulseM += impulseM; + if(imp.impulseM < -row.motorMaxImpulse) { + imp.impulseM = -row.motorMaxImpulse; + } else if(imp.impulseM > row.motorMaxImpulse) { + imp.impulseM = row.motorMaxImpulse; + } + impulseM = imp.impulseM - oldImpulseM; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * impulseM; + lv1Y += md.invMLin1Y * impulseM; + lv1Z += md.invMLin1Z * impulseM; + lv2X += md.invMLin2X * -impulseM; + lv2Y += md.invMLin2Y * -impulseM; + lv2Z += md.invMLin2Z * -impulseM; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * impulseM; + av1Y += md.invMAng1Y * impulseM; + av1Z += md.invMAng1Z * impulseM; + av2X += md.invMAng2X * -impulseM; + av2Y += md.invMAng2Y * -impulseM; + av2Z += md.invMAng2Z * -impulseM; + } + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulse = (row.rhs - rv - imp.impulse * row.cfm) * md.mass; + let oldImpulse = imp.impulse; + imp.impulse += impulse; + if(imp.impulse < row.minImpulse) { + imp.impulse = row.minImpulse; + } else if(imp.impulse > row.maxImpulse) { + imp.impulse = row.maxImpulse; + } + impulse = imp.impulse - oldImpulse; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * impulse; + lv1Y += md.invMLin1Y * impulse; + lv1Z += md.invMLin1Z * impulse; + lv2X += md.invMLin2X * -impulse; + lv2Y += md.invMLin2Y * -impulse; + lv2Z += md.invMLin2Z * -impulse; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * impulse; + av1Y += md.invMAng1Y * impulse; + av1Z += md.invMAng1Z * impulse; + av2X += md.invMAng2X * -impulse; + av2Y += md.invMAng2Y * -impulse; + av2Z += md.invMAng2Z * -impulse; + } + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + postSolveVelocity(timeStep) { + let linX; + let linY; + let linZ; + let angX; + let angY; + let angZ; + linX = 0; + linY = 0; + linZ = 0; + angX = 0; + angY = 0; + angZ = 0; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let row = this.info.rows[_g++]; + let imp = row.impulse; + let j = row.jacobian; + if((j.flag & 1) != 0) { + linX += j.lin1X * imp.impulse; + linY += j.lin1Y * imp.impulse; + linZ += j.lin1Z * imp.impulse; + } else if((j.flag & 2) != 0) { + angX += j.ang1X * imp.impulse; + angY += j.ang1Y * imp.impulse; + angZ += j.ang1Z * imp.impulse; + } + } + this.joint._appliedForceX = linX * timeStep.invDt; + this.joint._appliedForceY = linY * timeStep.invDt; + this.joint._appliedForceZ = linZ * timeStep.invDt; + this.joint._appliedTorqueX = angX * timeStep.invDt; + this.joint._appliedTorqueY = angY * timeStep.invDt; + this.joint._appliedTorqueZ = angZ * timeStep.invDt; + } + preSolvePosition(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobian; + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + md.mass = md.invMLin1X * j.lin1X + md.invMLin1Y * j.lin1Y + md.invMLin1Z * j.lin1Z + (md.invMLin2X * j.lin2X + md.invMLin2Y * j.lin2Y + md.invMLin2Z * j.lin2Z) + (md.invMAng1X * j.ang1X + md.invMAng1Y * j.ang1Y + md.invMAng1Z * j.ang1Z) + (md.invMAng2X * j.ang2X + md.invMAng2Y * j.ang2Y + md.invMAng2Z * j.ang2Z); + if(md.mass != 0) { + md.mass = 1 / md.mass; + } + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) this.info.rows[_g2++].impulse.impulseP = 0; + } + solvePositionSplitImpulse() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs * oimo.common.Setting.positionSplitImpulseBaumgarte - rv) * md.mass; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < row.minImpulse) { + imp.impulseP = row.minImpulse; + } else if(imp.impulseP > row.maxImpulse) { + imp.impulseP = row.maxImpulse; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLin1X * impulseP; + lv1Y += md.invMLin1Y * impulseP; + lv1Z += md.invMLin1Z * impulseP; + lv2X += md.invMLin2X * -impulseP; + lv2Y += md.invMLin2Y * -impulseP; + lv2Z += md.invMLin2Z * -impulseP; + av1X += md.invMAng1X * impulseP; + av1Y += md.invMAng1Y * impulseP; + av1Z += md.invMAng1Z * impulseP; + av2X += md.invMAng2X * -impulseP; + av2Y += md.invMAng2Y * -impulseP; + av2Z += md.invMAng2Z * -impulseP; + } + this._b1._pseudoVelX = lv1X; + this._b1._pseudoVelY = lv1Y; + this._b1._pseudoVelZ = lv1Z; + this._b2._pseudoVelX = lv2X; + this._b2._pseudoVelY = lv2Y; + this._b2._pseudoVelZ = lv2Z; + this._b1._angPseudoVelX = av1X; + this._b1._angPseudoVelY = av1Y; + this._b1._angPseudoVelZ = av1Z; + this._b2._angPseudoVelX = av2X; + this._b2._angPseudoVelY = av2Y; + this._b2._angPseudoVelZ = av2Z; + } + solvePositionNgs(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobian; + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + md.mass = md.invMLin1X * j.lin1X + md.invMLin1Y * j.lin1Y + md.invMLin1Z * j.lin1Z + (md.invMLin2X * j.lin2X + md.invMLin2Y * j.lin2Y + md.invMLin2Z * j.lin2Z) + (md.invMAng1X * j.ang1X + md.invMAng1Y * j.ang1Y + md.invMAng1Z * j.ang1Z) + (md.invMAng2X * j.ang2X + md.invMAng2Y * j.ang2Y + md.invMAng2Z * j.ang2Z); + if(md.mass != 0) { + md.mass = 1 / md.mass; + } + } + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = 0; + lv1Y = 0; + lv1Z = 0; + lv2X = 0; + lv2Y = 0; + lv2Z = 0; + av1X = 0; + av1Y = 0; + av1Z = 0; + av2X = 0; + av2Y = 0; + av2Z = 0; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs * oimo.common.Setting.positionNgsBaumgarte - rv) * md.mass; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < row.minImpulse) { + imp.impulseP = row.minImpulse; + } else if(imp.impulseP > row.maxImpulse) { + imp.impulseP = row.maxImpulse; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLin1X * impulseP; + lv1Y += md.invMLin1Y * impulseP; + lv1Z += md.invMLin1Z * impulseP; + lv2X += md.invMLin2X * -impulseP; + lv2Y += md.invMLin2Y * -impulseP; + lv2Z += md.invMLin2Z * -impulseP; + av1X += md.invMAng1X * impulseP; + av1Y += md.invMAng1Y * impulseP; + av1Z += md.invMAng1Z * impulseP; + av2X += md.invMAng2X * -impulseP; + av2Y += md.invMAng2Y * -impulseP; + av2Z += md.invMAng2Z * -impulseP; + } + let _this = this._b1; + _this._transform._positionX += lv1X; + _this._transform._positionY += lv1Y; + _this._transform._positionZ += lv1Z; + let _this1 = this._b2; + _this1._transform._positionX += lv2X; + _this1._transform._positionY += lv2Y; + _this1._transform._positionZ += lv2Z; + let _this2 = this._b1; + let theta = Math.sqrt(av1X * av1X + av1Y * av1Y + av1Z * av1Z); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = av1X * rotationToSinAxisFactor; + sinAxisY = av1Y * rotationToSinAxisFactor; + sinAxisZ = av1Z * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = _this2._transform._rotation00; + let e11 = _this2._transform._rotation11; + let e22 = _this2._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + qY = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + qZ = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qW = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this2._transform._rotation00 = 1 - yy - zz; + _this2._transform._rotation01 = xy - wz; + _this2._transform._rotation02 = xz + wy; + _this2._transform._rotation10 = xy + wz; + _this2._transform._rotation11 = 1 - xx - zz; + _this2._transform._rotation12 = yz - wx; + _this2._transform._rotation20 = xz - wy; + _this2._transform._rotation21 = yz + wx; + _this2._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = _this2._transform._rotation00 * _this2._invLocalInertia00 + _this2._transform._rotation01 * _this2._invLocalInertia10 + _this2._transform._rotation02 * _this2._invLocalInertia20; + __tmp__01 = _this2._transform._rotation00 * _this2._invLocalInertia01 + _this2._transform._rotation01 * _this2._invLocalInertia11 + _this2._transform._rotation02 * _this2._invLocalInertia21; + __tmp__02 = _this2._transform._rotation00 * _this2._invLocalInertia02 + _this2._transform._rotation01 * _this2._invLocalInertia12 + _this2._transform._rotation02 * _this2._invLocalInertia22; + __tmp__10 = _this2._transform._rotation10 * _this2._invLocalInertia00 + _this2._transform._rotation11 * _this2._invLocalInertia10 + _this2._transform._rotation12 * _this2._invLocalInertia20; + __tmp__11 = _this2._transform._rotation10 * _this2._invLocalInertia01 + _this2._transform._rotation11 * _this2._invLocalInertia11 + _this2._transform._rotation12 * _this2._invLocalInertia21; + __tmp__12 = _this2._transform._rotation10 * _this2._invLocalInertia02 + _this2._transform._rotation11 * _this2._invLocalInertia12 + _this2._transform._rotation12 * _this2._invLocalInertia22; + __tmp__20 = _this2._transform._rotation20 * _this2._invLocalInertia00 + _this2._transform._rotation21 * _this2._invLocalInertia10 + _this2._transform._rotation22 * _this2._invLocalInertia20; + __tmp__21 = _this2._transform._rotation20 * _this2._invLocalInertia01 + _this2._transform._rotation21 * _this2._invLocalInertia11 + _this2._transform._rotation22 * _this2._invLocalInertia21; + __tmp__22 = _this2._transform._rotation20 * _this2._invLocalInertia02 + _this2._transform._rotation21 * _this2._invLocalInertia12 + _this2._transform._rotation22 * _this2._invLocalInertia22; + _this2._invInertia00 = __tmp__00; + _this2._invInertia01 = __tmp__01; + _this2._invInertia02 = __tmp__02; + _this2._invInertia10 = __tmp__10; + _this2._invInertia11 = __tmp__11; + _this2._invInertia12 = __tmp__12; + _this2._invInertia20 = __tmp__20; + _this2._invInertia21 = __tmp__21; + _this2._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = _this2._invInertia00 * _this2._transform._rotation00 + _this2._invInertia01 * _this2._transform._rotation01 + _this2._invInertia02 * _this2._transform._rotation02; + __tmp__011 = _this2._invInertia00 * _this2._transform._rotation10 + _this2._invInertia01 * _this2._transform._rotation11 + _this2._invInertia02 * _this2._transform._rotation12; + __tmp__021 = _this2._invInertia00 * _this2._transform._rotation20 + _this2._invInertia01 * _this2._transform._rotation21 + _this2._invInertia02 * _this2._transform._rotation22; + __tmp__101 = _this2._invInertia10 * _this2._transform._rotation00 + _this2._invInertia11 * _this2._transform._rotation01 + _this2._invInertia12 * _this2._transform._rotation02; + __tmp__111 = _this2._invInertia10 * _this2._transform._rotation10 + _this2._invInertia11 * _this2._transform._rotation11 + _this2._invInertia12 * _this2._transform._rotation12; + __tmp__121 = _this2._invInertia10 * _this2._transform._rotation20 + _this2._invInertia11 * _this2._transform._rotation21 + _this2._invInertia12 * _this2._transform._rotation22; + __tmp__201 = _this2._invInertia20 * _this2._transform._rotation00 + _this2._invInertia21 * _this2._transform._rotation01 + _this2._invInertia22 * _this2._transform._rotation02; + __tmp__211 = _this2._invInertia20 * _this2._transform._rotation10 + _this2._invInertia21 * _this2._transform._rotation11 + _this2._invInertia22 * _this2._transform._rotation12; + __tmp__221 = _this2._invInertia20 * _this2._transform._rotation20 + _this2._invInertia21 * _this2._transform._rotation21 + _this2._invInertia22 * _this2._transform._rotation22; + _this2._invInertia00 = __tmp__001; + _this2._invInertia01 = __tmp__011; + _this2._invInertia02 = __tmp__021; + _this2._invInertia10 = __tmp__101; + _this2._invInertia11 = __tmp__111; + _this2._invInertia12 = __tmp__121; + _this2._invInertia20 = __tmp__201; + _this2._invInertia21 = __tmp__211; + _this2._invInertia22 = __tmp__221; + _this2._invInertia00 *= _this2._rotFactor.x; + _this2._invInertia01 *= _this2._rotFactor.x; + _this2._invInertia02 *= _this2._rotFactor.x; + _this2._invInertia10 *= _this2._rotFactor.y; + _this2._invInertia11 *= _this2._rotFactor.y; + _this2._invInertia12 *= _this2._rotFactor.y; + _this2._invInertia20 *= _this2._rotFactor.z; + _this2._invInertia21 *= _this2._rotFactor.z; + _this2._invInertia22 *= _this2._rotFactor.z; + let _this3 = this._b2; + let theta1 = Math.sqrt(av2X * av2X + av2Y * av2Y + av2Z * av2Z); + let halfTheta1 = theta1 * 0.5; + let rotationToSinAxisFactor1; + let cosHalfTheta1; + if(halfTheta1 < 0.5) { + let ht2 = halfTheta1 * halfTheta1; + rotationToSinAxisFactor1 = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta1 = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor1 = Math.sin(halfTheta1) / theta1; + cosHalfTheta1 = Math.cos(halfTheta1); + } + let sinAxisX1; + let sinAxisY1; + let sinAxisZ1; + sinAxisX1 = av2X * rotationToSinAxisFactor1; + sinAxisY1 = av2Y * rotationToSinAxisFactor1; + sinAxisZ1 = av2Z * rotationToSinAxisFactor1; + let dqX1; + let dqY1; + let dqZ1; + let dqW1; + dqX1 = sinAxisX1; + dqY1 = sinAxisY1; + dqZ1 = sinAxisZ1; + dqW1 = cosHalfTheta1; + let qX1; + let qY1; + let qZ1; + let qW1; + let e001 = _this3._transform._rotation00; + let e111 = _this3._transform._rotation11; + let e221 = _this3._transform._rotation22; + let t1 = e001 + e111 + e221; + let s1; + if(t1 > 0) { + s1 = Math.sqrt(t1 + 1); + qW1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + qY1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + qZ1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } else if(e001 > e111) { + if(e001 > e221) { + s1 = Math.sqrt(e001 - e111 - e221 + 1); + qX1 = 0.5 * s1; + s1 = 0.5 / s1; + qY1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qW1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + } else if(e111 > e221) { + s1 = Math.sqrt(e111 - e221 - e001 + 1); + qY1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + qX1 = dqW1 * qX1 + dqX1 * qW1 + dqY1 * qZ1 - dqZ1 * qY1; + qY1 = dqW1 * qY1 - dqX1 * qZ1 + dqY1 * qW1 + dqZ1 * qX1; + qZ1 = dqW1 * qZ1 + dqX1 * qY1 - dqY1 * qX1 + dqZ1 * qW1; + qW1 = dqW1 * qW1 - dqX1 * qX1 - dqY1 * qY1 - dqZ1 * qZ1; + let l1 = qX1 * qX1 + qY1 * qY1 + qZ1 * qZ1 + qW1 * qW1; + if(l1 > 1e-32) { + l1 = 1 / Math.sqrt(l1); + } + qX1 *= l1; + qY1 *= l1; + qZ1 *= l1; + qW1 *= l1; + let x1 = qX1; + let y1 = qY1; + let z1 = qZ1; + let w1 = qW1; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + _this3._transform._rotation00 = 1 - yy1 - zz1; + _this3._transform._rotation01 = xy1 - wz1; + _this3._transform._rotation02 = xz1 + wy1; + _this3._transform._rotation10 = xy1 + wz1; + _this3._transform._rotation11 = 1 - xx1 - zz1; + _this3._transform._rotation12 = yz1 - wx1; + _this3._transform._rotation20 = xz1 - wy1; + _this3._transform._rotation21 = yz1 + wx1; + _this3._transform._rotation22 = 1 - xx1 - yy1; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = _this3._transform._rotation00 * _this3._invLocalInertia00 + _this3._transform._rotation01 * _this3._invLocalInertia10 + _this3._transform._rotation02 * _this3._invLocalInertia20; + __tmp__012 = _this3._transform._rotation00 * _this3._invLocalInertia01 + _this3._transform._rotation01 * _this3._invLocalInertia11 + _this3._transform._rotation02 * _this3._invLocalInertia21; + __tmp__022 = _this3._transform._rotation00 * _this3._invLocalInertia02 + _this3._transform._rotation01 * _this3._invLocalInertia12 + _this3._transform._rotation02 * _this3._invLocalInertia22; + __tmp__102 = _this3._transform._rotation10 * _this3._invLocalInertia00 + _this3._transform._rotation11 * _this3._invLocalInertia10 + _this3._transform._rotation12 * _this3._invLocalInertia20; + __tmp__112 = _this3._transform._rotation10 * _this3._invLocalInertia01 + _this3._transform._rotation11 * _this3._invLocalInertia11 + _this3._transform._rotation12 * _this3._invLocalInertia21; + __tmp__122 = _this3._transform._rotation10 * _this3._invLocalInertia02 + _this3._transform._rotation11 * _this3._invLocalInertia12 + _this3._transform._rotation12 * _this3._invLocalInertia22; + __tmp__202 = _this3._transform._rotation20 * _this3._invLocalInertia00 + _this3._transform._rotation21 * _this3._invLocalInertia10 + _this3._transform._rotation22 * _this3._invLocalInertia20; + __tmp__212 = _this3._transform._rotation20 * _this3._invLocalInertia01 + _this3._transform._rotation21 * _this3._invLocalInertia11 + _this3._transform._rotation22 * _this3._invLocalInertia21; + __tmp__222 = _this3._transform._rotation20 * _this3._invLocalInertia02 + _this3._transform._rotation21 * _this3._invLocalInertia12 + _this3._transform._rotation22 * _this3._invLocalInertia22; + _this3._invInertia00 = __tmp__002; + _this3._invInertia01 = __tmp__012; + _this3._invInertia02 = __tmp__022; + _this3._invInertia10 = __tmp__102; + _this3._invInertia11 = __tmp__112; + _this3._invInertia12 = __tmp__122; + _this3._invInertia20 = __tmp__202; + _this3._invInertia21 = __tmp__212; + _this3._invInertia22 = __tmp__222; + let __tmp__003; + let __tmp__013; + let __tmp__023; + let __tmp__103; + let __tmp__113; + let __tmp__123; + let __tmp__203; + let __tmp__213; + let __tmp__223; + __tmp__003 = _this3._invInertia00 * _this3._transform._rotation00 + _this3._invInertia01 * _this3._transform._rotation01 + _this3._invInertia02 * _this3._transform._rotation02; + __tmp__013 = _this3._invInertia00 * _this3._transform._rotation10 + _this3._invInertia01 * _this3._transform._rotation11 + _this3._invInertia02 * _this3._transform._rotation12; + __tmp__023 = _this3._invInertia00 * _this3._transform._rotation20 + _this3._invInertia01 * _this3._transform._rotation21 + _this3._invInertia02 * _this3._transform._rotation22; + __tmp__103 = _this3._invInertia10 * _this3._transform._rotation00 + _this3._invInertia11 * _this3._transform._rotation01 + _this3._invInertia12 * _this3._transform._rotation02; + __tmp__113 = _this3._invInertia10 * _this3._transform._rotation10 + _this3._invInertia11 * _this3._transform._rotation11 + _this3._invInertia12 * _this3._transform._rotation12; + __tmp__123 = _this3._invInertia10 * _this3._transform._rotation20 + _this3._invInertia11 * _this3._transform._rotation21 + _this3._invInertia12 * _this3._transform._rotation22; + __tmp__203 = _this3._invInertia20 * _this3._transform._rotation00 + _this3._invInertia21 * _this3._transform._rotation01 + _this3._invInertia22 * _this3._transform._rotation02; + __tmp__213 = _this3._invInertia20 * _this3._transform._rotation10 + _this3._invInertia21 * _this3._transform._rotation11 + _this3._invInertia22 * _this3._transform._rotation12; + __tmp__223 = _this3._invInertia20 * _this3._transform._rotation20 + _this3._invInertia21 * _this3._transform._rotation21 + _this3._invInertia22 * _this3._transform._rotation22; + _this3._invInertia00 = __tmp__003; + _this3._invInertia01 = __tmp__013; + _this3._invInertia02 = __tmp__023; + _this3._invInertia10 = __tmp__103; + _this3._invInertia11 = __tmp__113; + _this3._invInertia12 = __tmp__123; + _this3._invInertia20 = __tmp__203; + _this3._invInertia21 = __tmp__213; + _this3._invInertia22 = __tmp__223; + _this3._invInertia00 *= _this3._rotFactor.x; + _this3._invInertia01 *= _this3._rotFactor.x; + _this3._invInertia02 *= _this3._rotFactor.x; + _this3._invInertia10 *= _this3._rotFactor.y; + _this3._invInertia11 *= _this3._rotFactor.y; + _this3._invInertia12 *= _this3._rotFactor.y; + _this3._invInertia20 *= _this3._rotFactor.z; + _this3._invInertia21 *= _this3._rotFactor.z; + _this3._invInertia22 *= _this3._rotFactor.z; + } + postSolve() { + this.joint._syncAnchors(); + this.joint._checkDestruction(); + } +} +if(!oimo.dynamics.rigidbody) oimo.dynamics.rigidbody = {}; +oimo.dynamics.rigidbody.MassData = class oimo_dynamics_rigidbody_MassData { + constructor() { + this.mass = 0; + this.localInertia = new oimo.common.Mat3(); + } +} +oimo.dynamics.rigidbody.RigidBody = class oimo_dynamics_rigidbody_RigidBody { + constructor(config) { + this._next = null; + this._prev = null; + this._shapeList = null; + this._shapeListLast = null; + this._numShapes = 0; + this._contactLinkList = null; + this._contactLinkListLast = null; + this._numContactLinks = 0; + this._jointLinkList = null; + this._jointLinkListLast = null; + this._numJointLinks = 0; + let v = config.linearVelocity; + this._velX = v.x; + this._velY = v.y; + this._velZ = v.z; + let v1 = config.angularVelocity; + this._angVelX = v1.x; + this._angVelY = v1.y; + this._angVelZ = v1.z; + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + this._ptransform = new oimo.common.Transform(); + this._transform = new oimo.common.Transform(); + let v2 = config.position; + this._ptransform._positionX = v2.x; + this._ptransform._positionY = v2.y; + this._ptransform._positionZ = v2.z; + let m = config.rotation; + this._ptransform._rotation00 = m.e00; + this._ptransform._rotation01 = m.e01; + this._ptransform._rotation02 = m.e02; + this._ptransform._rotation10 = m.e10; + this._ptransform._rotation11 = m.e11; + this._ptransform._rotation12 = m.e12; + this._ptransform._rotation20 = m.e20; + this._ptransform._rotation21 = m.e21; + this._ptransform._rotation22 = m.e22; + let dst = this._transform; + let src = this._ptransform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + this._type = config.type; + this._sleepTime = 0; + this._sleeping = false; + this._autoSleep = config.autoSleep; + this._mass = 0; + this._invMass = 0; + this._localInertia00 = 0; + this._localInertia01 = 0; + this._localInertia02 = 0; + this._localInertia10 = 0; + this._localInertia11 = 0; + this._localInertia12 = 0; + this._localInertia20 = 0; + this._localInertia21 = 0; + this._localInertia22 = 0; + this._invLocalInertia00 = 0; + this._invLocalInertia01 = 0; + this._invLocalInertia02 = 0; + this._invLocalInertia10 = 0; + this._invLocalInertia11 = 0; + this._invLocalInertia12 = 0; + this._invLocalInertia20 = 0; + this._invLocalInertia21 = 0; + this._invLocalInertia22 = 0; + this._invLocalInertiaWithoutRotFactor00 = 0; + this._invLocalInertiaWithoutRotFactor01 = 0; + this._invLocalInertiaWithoutRotFactor02 = 0; + this._invLocalInertiaWithoutRotFactor10 = 0; + this._invLocalInertiaWithoutRotFactor11 = 0; + this._invLocalInertiaWithoutRotFactor12 = 0; + this._invLocalInertiaWithoutRotFactor20 = 0; + this._invLocalInertiaWithoutRotFactor21 = 0; + this._invLocalInertiaWithoutRotFactor22 = 0; + this._invInertia00 = 0; + this._invInertia01 = 0; + this._invInertia02 = 0; + this._invInertia10 = 0; + this._invInertia11 = 0; + this._invInertia12 = 0; + this._invInertia20 = 0; + this._invInertia21 = 0; + this._invInertia22 = 0; + this._linearDamping = config.linearDamping; + this._angularDamping = config.angularDamping; + this._forceX = 0; + this._forceY = 0; + this._forceZ = 0; + this._torqueX = 0; + this._torqueY = 0; + this._torqueZ = 0; + this._linearContactImpulseX = 0; + this._linearContactImpulseY = 0; + this._linearContactImpulseZ = 0; + this._angularContactImpulseX = 0; + this._angularContactImpulseY = 0; + this._angularContactImpulseZ = 0; + this._rotFactor = new oimo.common.Vec3(1,1,1); + this._addedToIsland = false; + this._gravityScale = 1; + this._world = null; + } + _integrate(dt) { + switch(this._type) { + case 1: + this._velX = 0; + this._velY = 0; + this._velZ = 0; + this._angVelX = 0; + this._angVelY = 0; + this._angVelZ = 0; + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + break; + case 0:case 2: + let translationX; + let translationY; + let translationZ; + let rotationX; + let rotationY; + let rotationZ; + translationX = this._velX * dt; + translationY = this._velY * dt; + translationZ = this._velZ * dt; + rotationX = this._angVelX * dt; + rotationY = this._angVelY * dt; + rotationZ = this._angVelZ * dt; + let translationLengthSq = translationX * translationX + translationY * translationY + translationZ * translationZ; + let rotationLengthSq = rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ; + if(translationLengthSq == 0 && rotationLengthSq == 0) { + return; + } + if(translationLengthSq > oimo.common.Setting.maxTranslationPerStep * oimo.common.Setting.maxTranslationPerStep) { + let l = oimo.common.Setting.maxTranslationPerStep / Math.sqrt(translationLengthSq); + this._velX *= l; + this._velY *= l; + this._velZ *= l; + translationX *= l; + translationY *= l; + translationZ *= l; + } + if(rotationLengthSq > oimo.common.Setting.maxRotationPerStep * oimo.common.Setting.maxRotationPerStep) { + let l = oimo.common.Setting.maxRotationPerStep / Math.sqrt(rotationLengthSq); + this._angVelX *= l; + this._angVelY *= l; + this._angVelZ *= l; + rotationX *= l; + rotationY *= l; + rotationZ *= l; + } + this._transform._positionX += translationX; + this._transform._positionY += translationY; + this._transform._positionZ += translationZ; + let theta = Math.sqrt(rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = rotationX * rotationToSinAxisFactor; + sinAxisY = rotationY * rotationToSinAxisFactor; + sinAxisZ = rotationZ * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation21 - this._transform._rotation12) * s; + qY = (this._transform._rotation02 - this._transform._rotation20) * s; + qZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation02 + this._transform._rotation20) * s; + qW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._transform._rotation00 = 1 - yy - zz; + this._transform._rotation01 = xy - wz; + this._transform._rotation02 = xz + wy; + this._transform._rotation10 = xy + wz; + this._transform._rotation11 = 1 - xx - zz; + this._transform._rotation12 = yz - wx; + this._transform._rotation20 = xz - wy; + this._transform._rotation21 = yz + wx; + this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + break; + } + } + _integratePseudoVelocity() { + if(this._pseudoVelX * this._pseudoVelX + this._pseudoVelY * this._pseudoVelY + this._pseudoVelZ * this._pseudoVelZ == 0 && this._angPseudoVelX * this._angPseudoVelX + this._angPseudoVelY * this._angPseudoVelY + this._angPseudoVelZ * this._angPseudoVelZ == 0) { + return; + } + switch(this._type) { + case 1: + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + break; + case 0:case 2: + let translationX; + let translationY; + let translationZ; + let rotationX; + let rotationY; + let rotationZ; + translationX = this._pseudoVelX; + translationY = this._pseudoVelY; + translationZ = this._pseudoVelZ; + rotationX = this._angPseudoVelX; + rotationY = this._angPseudoVelY; + rotationZ = this._angPseudoVelZ; + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + this._transform._positionX += translationX; + this._transform._positionY += translationY; + this._transform._positionZ += translationZ; + let theta = Math.sqrt(rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = rotationX * rotationToSinAxisFactor; + sinAxisY = rotationY * rotationToSinAxisFactor; + sinAxisZ = rotationZ * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation21 - this._transform._rotation12) * s; + qY = (this._transform._rotation02 - this._transform._rotation20) * s; + qZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation02 + this._transform._rotation20) * s; + qW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._transform._rotation00 = 1 - yy - zz; + this._transform._rotation01 = xy - wz; + this._transform._rotation02 = xz + wy; + this._transform._rotation10 = xy + wz; + this._transform._rotation11 = 1 - xx - zz; + this._transform._rotation12 = yz - wx; + this._transform._rotation20 = xz - wy; + this._transform._rotation21 = yz + wx; + this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + break; + } + } + updateMass() { + let totalInertia00; + let totalInertia01; + let totalInertia02; + let totalInertia10; + let totalInertia11; + let totalInertia12; + let totalInertia20; + let totalInertia21; + let totalInertia22; + totalInertia00 = 0; + totalInertia01 = 0; + totalInertia02 = 0; + totalInertia10 = 0; + totalInertia11 = 0; + totalInertia12 = 0; + totalInertia20 = 0; + totalInertia21 = 0; + totalInertia22 = 0; + let totalMass = 0; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let g = s._geom; + g._updateMass(); + let mass = s._density * g._volume; + let inertia00; + let inertia01; + let inertia02; + let inertia10; + let inertia11; + let inertia12; + let inertia20; + let inertia21; + let inertia22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = s._localTransform._rotation00 * g._inertiaCoeff00 + s._localTransform._rotation01 * g._inertiaCoeff10 + s._localTransform._rotation02 * g._inertiaCoeff20; + __tmp__01 = s._localTransform._rotation00 * g._inertiaCoeff01 + s._localTransform._rotation01 * g._inertiaCoeff11 + s._localTransform._rotation02 * g._inertiaCoeff21; + __tmp__02 = s._localTransform._rotation00 * g._inertiaCoeff02 + s._localTransform._rotation01 * g._inertiaCoeff12 + s._localTransform._rotation02 * g._inertiaCoeff22; + __tmp__10 = s._localTransform._rotation10 * g._inertiaCoeff00 + s._localTransform._rotation11 * g._inertiaCoeff10 + s._localTransform._rotation12 * g._inertiaCoeff20; + __tmp__11 = s._localTransform._rotation10 * g._inertiaCoeff01 + s._localTransform._rotation11 * g._inertiaCoeff11 + s._localTransform._rotation12 * g._inertiaCoeff21; + __tmp__12 = s._localTransform._rotation10 * g._inertiaCoeff02 + s._localTransform._rotation11 * g._inertiaCoeff12 + s._localTransform._rotation12 * g._inertiaCoeff22; + __tmp__20 = s._localTransform._rotation20 * g._inertiaCoeff00 + s._localTransform._rotation21 * g._inertiaCoeff10 + s._localTransform._rotation22 * g._inertiaCoeff20; + __tmp__21 = s._localTransform._rotation20 * g._inertiaCoeff01 + s._localTransform._rotation21 * g._inertiaCoeff11 + s._localTransform._rotation22 * g._inertiaCoeff21; + __tmp__22 = s._localTransform._rotation20 * g._inertiaCoeff02 + s._localTransform._rotation21 * g._inertiaCoeff12 + s._localTransform._rotation22 * g._inertiaCoeff22; + inertia00 = __tmp__00; + inertia01 = __tmp__01; + inertia02 = __tmp__02; + inertia10 = __tmp__10; + inertia11 = __tmp__11; + inertia12 = __tmp__12; + inertia20 = __tmp__20; + inertia21 = __tmp__21; + inertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = inertia00 * s._localTransform._rotation00 + inertia01 * s._localTransform._rotation01 + inertia02 * s._localTransform._rotation02; + __tmp__011 = inertia00 * s._localTransform._rotation10 + inertia01 * s._localTransform._rotation11 + inertia02 * s._localTransform._rotation12; + __tmp__021 = inertia00 * s._localTransform._rotation20 + inertia01 * s._localTransform._rotation21 + inertia02 * s._localTransform._rotation22; + __tmp__101 = inertia10 * s._localTransform._rotation00 + inertia11 * s._localTransform._rotation01 + inertia12 * s._localTransform._rotation02; + __tmp__111 = inertia10 * s._localTransform._rotation10 + inertia11 * s._localTransform._rotation11 + inertia12 * s._localTransform._rotation12; + __tmp__121 = inertia10 * s._localTransform._rotation20 + inertia11 * s._localTransform._rotation21 + inertia12 * s._localTransform._rotation22; + __tmp__201 = inertia20 * s._localTransform._rotation00 + inertia21 * s._localTransform._rotation01 + inertia22 * s._localTransform._rotation02; + __tmp__211 = inertia20 * s._localTransform._rotation10 + inertia21 * s._localTransform._rotation11 + inertia22 * s._localTransform._rotation12; + __tmp__221 = inertia20 * s._localTransform._rotation20 + inertia21 * s._localTransform._rotation21 + inertia22 * s._localTransform._rotation22; + inertia00 = __tmp__001; + inertia01 = __tmp__011; + inertia02 = __tmp__021; + inertia10 = __tmp__101; + inertia11 = __tmp__111; + inertia12 = __tmp__121; + inertia20 = __tmp__201; + inertia21 = __tmp__211; + inertia22 = __tmp__221; + inertia00 *= mass; + inertia01 *= mass; + inertia02 *= mass; + inertia10 *= mass; + inertia11 *= mass; + inertia12 *= mass; + inertia20 *= mass; + inertia21 *= mass; + inertia22 *= mass; + let cogInertia00; + let cogInertia01; + let cogInertia02; + let cogInertia10; + let cogInertia11; + let cogInertia12; + let cogInertia20; + let cogInertia21; + let cogInertia22; + let xx = s._localTransform._positionX * s._localTransform._positionX; + let yy = s._localTransform._positionY * s._localTransform._positionY; + let zz = s._localTransform._positionZ * s._localTransform._positionZ; + let xy = -s._localTransform._positionX * s._localTransform._positionY; + let yz = -s._localTransform._positionY * s._localTransform._positionZ; + let zx = -s._localTransform._positionZ * s._localTransform._positionX; + cogInertia00 = yy + zz; + cogInertia01 = xy; + cogInertia02 = zx; + cogInertia10 = xy; + cogInertia11 = xx + zz; + cogInertia12 = yz; + cogInertia20 = zx; + cogInertia21 = yz; + cogInertia22 = xx + yy; + inertia00 += cogInertia00 * mass; + inertia01 += cogInertia01 * mass; + inertia02 += cogInertia02 * mass; + inertia10 += cogInertia10 * mass; + inertia11 += cogInertia11 * mass; + inertia12 += cogInertia12 * mass; + inertia20 += cogInertia20 * mass; + inertia21 += cogInertia21 * mass; + inertia22 += cogInertia22 * mass; + totalMass += mass; + totalInertia00 += inertia00; + totalInertia01 += inertia01; + totalInertia02 += inertia02; + totalInertia10 += inertia10; + totalInertia11 += inertia11; + totalInertia12 += inertia12; + totalInertia20 += inertia20; + totalInertia21 += inertia21; + totalInertia22 += inertia22; + s = n; + } + this._mass = totalMass; + this._localInertia00 = totalInertia00; + this._localInertia01 = totalInertia01; + this._localInertia02 = totalInertia02; + this._localInertia10 = totalInertia10; + this._localInertia11 = totalInertia11; + this._localInertia12 = totalInertia12; + this._localInertia20 = totalInertia20; + this._localInertia21 = totalInertia21; + this._localInertia22 = totalInertia22; + if(this._mass > 0 && this._localInertia00 * (this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21) - this._localInertia01 * (this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20) + this._localInertia02 * (this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20) > 0 && this._type == 0) { + this._invMass = 1 / this._mass; + let d00 = this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21; + let d01 = this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20; + let d02 = this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20; + let d = this._localInertia00 * d00 - this._localInertia01 * d01 + this._localInertia02 * d02; + if(d < -1e-32 || d > 1e-32) { + d = 1 / d; + } + this._invLocalInertia00 = d00 * d; + this._invLocalInertia01 = -(this._localInertia01 * this._localInertia22 - this._localInertia02 * this._localInertia21) * d; + this._invLocalInertia02 = (this._localInertia01 * this._localInertia12 - this._localInertia02 * this._localInertia11) * d; + this._invLocalInertia10 = -d01 * d; + this._invLocalInertia11 = (this._localInertia00 * this._localInertia22 - this._localInertia02 * this._localInertia20) * d; + this._invLocalInertia12 = -(this._localInertia00 * this._localInertia12 - this._localInertia02 * this._localInertia10) * d; + this._invLocalInertia20 = d02 * d; + this._invLocalInertia21 = -(this._localInertia00 * this._localInertia21 - this._localInertia01 * this._localInertia20) * d; + this._invLocalInertia22 = (this._localInertia00 * this._localInertia11 - this._localInertia01 * this._localInertia10) * d; + this._invLocalInertiaWithoutRotFactor00 = this._invLocalInertia00; + this._invLocalInertiaWithoutRotFactor01 = this._invLocalInertia01; + this._invLocalInertiaWithoutRotFactor02 = this._invLocalInertia02; + this._invLocalInertiaWithoutRotFactor10 = this._invLocalInertia10; + this._invLocalInertiaWithoutRotFactor11 = this._invLocalInertia11; + this._invLocalInertiaWithoutRotFactor12 = this._invLocalInertia12; + this._invLocalInertiaWithoutRotFactor20 = this._invLocalInertia20; + this._invLocalInertiaWithoutRotFactor21 = this._invLocalInertia21; + this._invLocalInertiaWithoutRotFactor22 = this._invLocalInertia22; + this._invLocalInertia00 = this._invLocalInertiaWithoutRotFactor00 * this._rotFactor.x; + this._invLocalInertia01 = this._invLocalInertiaWithoutRotFactor01 * this._rotFactor.x; + this._invLocalInertia02 = this._invLocalInertiaWithoutRotFactor02 * this._rotFactor.x; + this._invLocalInertia10 = this._invLocalInertiaWithoutRotFactor10 * this._rotFactor.y; + this._invLocalInertia11 = this._invLocalInertiaWithoutRotFactor11 * this._rotFactor.y; + this._invLocalInertia12 = this._invLocalInertiaWithoutRotFactor12 * this._rotFactor.y; + this._invLocalInertia20 = this._invLocalInertiaWithoutRotFactor20 * this._rotFactor.z; + this._invLocalInertia21 = this._invLocalInertiaWithoutRotFactor21 * this._rotFactor.z; + this._invLocalInertia22 = this._invLocalInertiaWithoutRotFactor22 * this._rotFactor.z; + } else { + this._invMass = 0; + this._invLocalInertia00 = 0; + this._invLocalInertia01 = 0; + this._invLocalInertia02 = 0; + this._invLocalInertia10 = 0; + this._invLocalInertia11 = 0; + this._invLocalInertia12 = 0; + this._invLocalInertia20 = 0; + this._invLocalInertia21 = 0; + this._invLocalInertia22 = 0; + this._invLocalInertiaWithoutRotFactor00 = 0; + this._invLocalInertiaWithoutRotFactor01 = 0; + this._invLocalInertiaWithoutRotFactor02 = 0; + this._invLocalInertiaWithoutRotFactor10 = 0; + this._invLocalInertiaWithoutRotFactor11 = 0; + this._invLocalInertiaWithoutRotFactor12 = 0; + this._invLocalInertiaWithoutRotFactor20 = 0; + this._invLocalInertiaWithoutRotFactor21 = 0; + this._invLocalInertiaWithoutRotFactor22 = 0; + if(this._type == 0) { + this._type = 1; + } + } + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + this._sleeping = false; + this._sleepTime = 0; + } + getPosition() { + let v = new oimo.common.Vec3(); + v.x = this._transform._positionX; + v.y = this._transform._positionY; + v.z = this._transform._positionZ; + return v; + } + getPositionTo(position) { + position.x = this._transform._positionX; + position.y = this._transform._positionY; + position.z = this._transform._positionZ; + } + setPosition(position) { + this._transform._positionX = position.x; + this._transform._positionY = position.y; + this._transform._positionZ = position.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + translate(translation) { + let diffX; + let diffY; + let diffZ; + diffX = translation.x; + diffY = translation.y; + diffZ = translation.z; + this._transform._positionX += diffX; + this._transform._positionY += diffY; + this._transform._positionZ += diffZ; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getRotation() { + let m = new oimo.common.Mat3(); + m.e00 = this._transform._rotation00; + m.e01 = this._transform._rotation01; + m.e02 = this._transform._rotation02; + m.e10 = this._transform._rotation10; + m.e11 = this._transform._rotation11; + m.e12 = this._transform._rotation12; + m.e20 = this._transform._rotation20; + m.e21 = this._transform._rotation21; + m.e22 = this._transform._rotation22; + return m; + } + getRotationTo(rotation) { + rotation.e00 = this._transform._rotation00; + rotation.e01 = this._transform._rotation01; + rotation.e02 = this._transform._rotation02; + rotation.e10 = this._transform._rotation10; + rotation.e11 = this._transform._rotation11; + rotation.e12 = this._transform._rotation12; + rotation.e20 = this._transform._rotation20; + rotation.e21 = this._transform._rotation21; + rotation.e22 = this._transform._rotation22; + } + setRotation(rotation) { + this._transform._rotation00 = rotation.e00; + this._transform._rotation01 = rotation.e01; + this._transform._rotation02 = rotation.e02; + this._transform._rotation10 = rotation.e10; + this._transform._rotation11 = rotation.e11; + this._transform._rotation12 = rotation.e12; + this._transform._rotation20 = rotation.e20; + this._transform._rotation21 = rotation.e21; + this._transform._rotation22 = rotation.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + setRotationXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + this._transform._rotation00 = cy * cz; + this._transform._rotation01 = -cy * sz; + this._transform._rotation02 = sy; + this._transform._rotation10 = cx * sz + cz * sx * sy; + this._transform._rotation11 = cx * cz - sx * sy * sz; + this._transform._rotation12 = -cy * sx; + this._transform._rotation20 = sx * sz - cx * cz * sy; + this._transform._rotation21 = cz * sx + cx * sy * sz; + this._transform._rotation22 = cx * cy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + rotate(rotation) { + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + rot00 = rotation.e00; + rot01 = rotation.e01; + rot02 = rotation.e02; + rot10 = rotation.e10; + rot11 = rotation.e11; + rot12 = rotation.e12; + rot20 = rotation.e20; + rot21 = rotation.e21; + rot22 = rotation.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._transform._rotation00 + rot01 * this._transform._rotation10 + rot02 * this._transform._rotation20; + __tmp__01 = rot00 * this._transform._rotation01 + rot01 * this._transform._rotation11 + rot02 * this._transform._rotation21; + __tmp__02 = rot00 * this._transform._rotation02 + rot01 * this._transform._rotation12 + rot02 * this._transform._rotation22; + __tmp__10 = rot10 * this._transform._rotation00 + rot11 * this._transform._rotation10 + rot12 * this._transform._rotation20; + __tmp__11 = rot10 * this._transform._rotation01 + rot11 * this._transform._rotation11 + rot12 * this._transform._rotation21; + __tmp__12 = rot10 * this._transform._rotation02 + rot11 * this._transform._rotation12 + rot12 * this._transform._rotation22; + __tmp__20 = rot20 * this._transform._rotation00 + rot21 * this._transform._rotation10 + rot22 * this._transform._rotation20; + __tmp__21 = rot20 * this._transform._rotation01 + rot21 * this._transform._rotation11 + rot22 * this._transform._rotation21; + __tmp__22 = rot20 * this._transform._rotation02 + rot21 * this._transform._rotation12 + rot22 * this._transform._rotation22; + this._transform._rotation00 = __tmp__00; + this._transform._rotation01 = __tmp__01; + this._transform._rotation02 = __tmp__02; + this._transform._rotation10 = __tmp__10; + this._transform._rotation11 = __tmp__11; + this._transform._rotation12 = __tmp__12; + this._transform._rotation20 = __tmp__20; + this._transform._rotation21 = __tmp__21; + this._transform._rotation22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__011 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__021 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__101 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__111 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__121 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__201 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__211 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__221 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__012 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__022 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__102 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__112 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__122 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__202 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__212 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__222 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__002; + this._invInertia01 = __tmp__012; + this._invInertia02 = __tmp__022; + this._invInertia10 = __tmp__102; + this._invInertia11 = __tmp__112; + this._invInertia12 = __tmp__122; + this._invInertia20 = __tmp__202; + this._invInertia21 = __tmp__212; + this._invInertia22 = __tmp__222; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + rotateXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + rot00 = cy * cz; + rot01 = -cy * sz; + rot02 = sy; + rot10 = cx * sz + cz * sx * sy; + rot11 = cx * cz - sx * sy * sz; + rot12 = -cy * sx; + rot20 = sx * sz - cx * cz * sy; + rot21 = cz * sx + cx * sy * sz; + rot22 = cx * cy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._transform._rotation00 + rot01 * this._transform._rotation10 + rot02 * this._transform._rotation20; + __tmp__01 = rot00 * this._transform._rotation01 + rot01 * this._transform._rotation11 + rot02 * this._transform._rotation21; + __tmp__02 = rot00 * this._transform._rotation02 + rot01 * this._transform._rotation12 + rot02 * this._transform._rotation22; + __tmp__10 = rot10 * this._transform._rotation00 + rot11 * this._transform._rotation10 + rot12 * this._transform._rotation20; + __tmp__11 = rot10 * this._transform._rotation01 + rot11 * this._transform._rotation11 + rot12 * this._transform._rotation21; + __tmp__12 = rot10 * this._transform._rotation02 + rot11 * this._transform._rotation12 + rot12 * this._transform._rotation22; + __tmp__20 = rot20 * this._transform._rotation00 + rot21 * this._transform._rotation10 + rot22 * this._transform._rotation20; + __tmp__21 = rot20 * this._transform._rotation01 + rot21 * this._transform._rotation11 + rot22 * this._transform._rotation21; + __tmp__22 = rot20 * this._transform._rotation02 + rot21 * this._transform._rotation12 + rot22 * this._transform._rotation22; + this._transform._rotation00 = __tmp__00; + this._transform._rotation01 = __tmp__01; + this._transform._rotation02 = __tmp__02; + this._transform._rotation10 = __tmp__10; + this._transform._rotation11 = __tmp__11; + this._transform._rotation12 = __tmp__12; + this._transform._rotation20 = __tmp__20; + this._transform._rotation21 = __tmp__21; + this._transform._rotation22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__011 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__021 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__101 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__111 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__121 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__201 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__211 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__221 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__012 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__022 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__102 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__112 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__122 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__202 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__212 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__222 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__002; + this._invInertia01 = __tmp__012; + this._invInertia02 = __tmp__022; + this._invInertia10 = __tmp__102; + this._invInertia11 = __tmp__112; + this._invInertia12 = __tmp__122; + this._invInertia20 = __tmp__202; + this._invInertia21 = __tmp__212; + this._invInertia22 = __tmp__222; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getOrientation() { + let q = new oimo.common.Quat(); + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation21 - this._transform._rotation12) * s; + iqY = (this._transform._rotation02 - this._transform._rotation20) * s; + iqZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation02 + this._transform._rotation20) * s; + iqW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + q.x = iqX; + q.y = iqY; + q.z = iqZ; + q.w = iqW; + return q; + } + getOrientationTo(orientation) { + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation21 - this._transform._rotation12) * s; + iqY = (this._transform._rotation02 - this._transform._rotation20) * s; + iqZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation02 + this._transform._rotation20) * s; + iqW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + orientation.x = iqX; + orientation.y = iqY; + orientation.z = iqZ; + orientation.w = iqW; + } + setOrientation(quaternion) { + let qX; + let qY; + let qZ; + let qW; + qX = quaternion.x; + qY = quaternion.y; + qZ = quaternion.z; + qW = quaternion.w; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._transform._rotation00 = 1 - yy - zz; + this._transform._rotation01 = xy - wz; + this._transform._rotation02 = xz + wy; + this._transform._rotation10 = xy + wz; + this._transform._rotation11 = 1 - xx - zz; + this._transform._rotation12 = yz - wx; + this._transform._rotation20 = xz - wy; + this._transform._rotation21 = yz + wx; + this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getTransform() { + let _this = this._transform; + let tf = new oimo.common.Transform(); + tf._positionX = _this._positionX; + tf._positionY = _this._positionY; + tf._positionZ = _this._positionZ; + tf._rotation00 = _this._rotation00; + tf._rotation01 = _this._rotation01; + tf._rotation02 = _this._rotation02; + tf._rotation10 = _this._rotation10; + tf._rotation11 = _this._rotation11; + tf._rotation12 = _this._rotation12; + tf._rotation20 = _this._rotation20; + tf._rotation21 = _this._rotation21; + tf._rotation22 = _this._rotation22; + return tf; + } + getTransformTo(transform) { + let transform1 = this._transform; + transform._positionX = transform1._positionX; + transform._positionY = transform1._positionY; + transform._positionZ = transform1._positionZ; + transform._rotation00 = transform1._rotation00; + transform._rotation01 = transform1._rotation01; + transform._rotation02 = transform1._rotation02; + transform._rotation10 = transform1._rotation10; + transform._rotation11 = transform1._rotation11; + transform._rotation12 = transform1._rotation12; + transform._rotation20 = transform1._rotation20; + transform._rotation21 = transform1._rotation21; + transform._rotation22 = transform1._rotation22; + } + setTransform(transform) { + this._transform._positionX = transform._positionX; + this._transform._positionY = transform._positionY; + this._transform._positionZ = transform._positionZ; + this._transform._rotation00 = transform._rotation00; + this._transform._rotation01 = transform._rotation01; + this._transform._rotation02 = transform._rotation02; + this._transform._rotation10 = transform._rotation10; + this._transform._rotation11 = transform._rotation11; + this._transform._rotation12 = transform._rotation12; + this._transform._rotation20 = transform._rotation20; + this._transform._rotation21 = transform._rotation21; + this._transform._rotation22 = transform._rotation22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getMass() { + return this._mass; + } + getLocalInertia() { + let m = new oimo.common.Mat3(); + m.e00 = this._localInertia00; + m.e01 = this._localInertia01; + m.e02 = this._localInertia02; + m.e10 = this._localInertia10; + m.e11 = this._localInertia11; + m.e12 = this._localInertia12; + m.e20 = this._localInertia20; + m.e21 = this._localInertia21; + m.e22 = this._localInertia22; + return m; + } + getLocalInertiaTo(inertia) { + inertia.e00 = this._localInertia00; + inertia.e01 = this._localInertia01; + inertia.e02 = this._localInertia02; + inertia.e10 = this._localInertia10; + inertia.e11 = this._localInertia11; + inertia.e12 = this._localInertia12; + inertia.e20 = this._localInertia20; + inertia.e21 = this._localInertia21; + inertia.e22 = this._localInertia22; + } + getMassData() { + let md = new oimo.dynamics.rigidbody.MassData(); + md.mass = this._mass; + let m = md.localInertia; + m.e00 = this._localInertia00; + m.e01 = this._localInertia01; + m.e02 = this._localInertia02; + m.e10 = this._localInertia10; + m.e11 = this._localInertia11; + m.e12 = this._localInertia12; + m.e20 = this._localInertia20; + m.e21 = this._localInertia21; + m.e22 = this._localInertia22; + return md; + } + getMassDataTo(massData) { + massData.mass = this._mass; + let m = massData.localInertia; + m.e00 = this._localInertia00; + m.e01 = this._localInertia01; + m.e02 = this._localInertia02; + m.e10 = this._localInertia10; + m.e11 = this._localInertia11; + m.e12 = this._localInertia12; + m.e20 = this._localInertia20; + m.e21 = this._localInertia21; + m.e22 = this._localInertia22; + } + setMassData(massData) { + this._mass = massData.mass; + let m = massData.localInertia; + this._localInertia00 = m.e00; + this._localInertia01 = m.e01; + this._localInertia02 = m.e02; + this._localInertia10 = m.e10; + this._localInertia11 = m.e11; + this._localInertia12 = m.e12; + this._localInertia20 = m.e20; + this._localInertia21 = m.e21; + this._localInertia22 = m.e22; + if(this._mass > 0 && this._localInertia00 * (this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21) - this._localInertia01 * (this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20) + this._localInertia02 * (this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20) > 0 && this._type == 0) { + this._invMass = 1 / this._mass; + let d00 = this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21; + let d01 = this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20; + let d02 = this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20; + let d = this._localInertia00 * d00 - this._localInertia01 * d01 + this._localInertia02 * d02; + if(d < -1e-32 || d > 1e-32) { + d = 1 / d; + } + this._invLocalInertia00 = d00 * d; + this._invLocalInertia01 = -(this._localInertia01 * this._localInertia22 - this._localInertia02 * this._localInertia21) * d; + this._invLocalInertia02 = (this._localInertia01 * this._localInertia12 - this._localInertia02 * this._localInertia11) * d; + this._invLocalInertia10 = -d01 * d; + this._invLocalInertia11 = (this._localInertia00 * this._localInertia22 - this._localInertia02 * this._localInertia20) * d; + this._invLocalInertia12 = -(this._localInertia00 * this._localInertia12 - this._localInertia02 * this._localInertia10) * d; + this._invLocalInertia20 = d02 * d; + this._invLocalInertia21 = -(this._localInertia00 * this._localInertia21 - this._localInertia01 * this._localInertia20) * d; + this._invLocalInertia22 = (this._localInertia00 * this._localInertia11 - this._localInertia01 * this._localInertia10) * d; + this._invLocalInertiaWithoutRotFactor00 = this._invLocalInertia00; + this._invLocalInertiaWithoutRotFactor01 = this._invLocalInertia01; + this._invLocalInertiaWithoutRotFactor02 = this._invLocalInertia02; + this._invLocalInertiaWithoutRotFactor10 = this._invLocalInertia10; + this._invLocalInertiaWithoutRotFactor11 = this._invLocalInertia11; + this._invLocalInertiaWithoutRotFactor12 = this._invLocalInertia12; + this._invLocalInertiaWithoutRotFactor20 = this._invLocalInertia20; + this._invLocalInertiaWithoutRotFactor21 = this._invLocalInertia21; + this._invLocalInertiaWithoutRotFactor22 = this._invLocalInertia22; + this._invLocalInertia00 = this._invLocalInertiaWithoutRotFactor00 * this._rotFactor.x; + this._invLocalInertia01 = this._invLocalInertiaWithoutRotFactor01 * this._rotFactor.x; + this._invLocalInertia02 = this._invLocalInertiaWithoutRotFactor02 * this._rotFactor.x; + this._invLocalInertia10 = this._invLocalInertiaWithoutRotFactor10 * this._rotFactor.y; + this._invLocalInertia11 = this._invLocalInertiaWithoutRotFactor11 * this._rotFactor.y; + this._invLocalInertia12 = this._invLocalInertiaWithoutRotFactor12 * this._rotFactor.y; + this._invLocalInertia20 = this._invLocalInertiaWithoutRotFactor20 * this._rotFactor.z; + this._invLocalInertia21 = this._invLocalInertiaWithoutRotFactor21 * this._rotFactor.z; + this._invLocalInertia22 = this._invLocalInertiaWithoutRotFactor22 * this._rotFactor.z; + } else { + this._invMass = 0; + this._invLocalInertia00 = 0; + this._invLocalInertia01 = 0; + this._invLocalInertia02 = 0; + this._invLocalInertia10 = 0; + this._invLocalInertia11 = 0; + this._invLocalInertia12 = 0; + this._invLocalInertia20 = 0; + this._invLocalInertia21 = 0; + this._invLocalInertia22 = 0; + this._invLocalInertiaWithoutRotFactor00 = 0; + this._invLocalInertiaWithoutRotFactor01 = 0; + this._invLocalInertiaWithoutRotFactor02 = 0; + this._invLocalInertiaWithoutRotFactor10 = 0; + this._invLocalInertiaWithoutRotFactor11 = 0; + this._invLocalInertiaWithoutRotFactor12 = 0; + this._invLocalInertiaWithoutRotFactor20 = 0; + this._invLocalInertiaWithoutRotFactor21 = 0; + this._invLocalInertiaWithoutRotFactor22 = 0; + if(this._type == 0) { + this._type = 1; + } + } + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + this._sleeping = false; + this._sleepTime = 0; + } + getRotationFactor() { + let _this = this._rotFactor; + return new oimo.common.Vec3(_this.x,_this.y,_this.z); + } + setRotationFactor(rotationFactor) { + let _this = this._rotFactor; + _this.x = rotationFactor.x; + _this.y = rotationFactor.y; + _this.z = rotationFactor.z; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + this._sleeping = false; + this._sleepTime = 0; + } + getLinearVelocity() { + let v = new oimo.common.Vec3(); + v.x = this._velX; + v.y = this._velY; + v.z = this._velZ; + return v; + } + getLinearVelocityTo(linearVelocity) { + linearVelocity.x = this._velX; + linearVelocity.y = this._velY; + linearVelocity.z = this._velZ; + } + setLinearVelocity(linearVelocity) { + if(this._type == 1) { + this._velX = 0; + this._velY = 0; + this._velZ = 0; + } else { + this._velX = linearVelocity.x; + this._velY = linearVelocity.y; + this._velZ = linearVelocity.z; + } + this._sleeping = false; + this._sleepTime = 0; + } + getAngularVelocity() { + let v = new oimo.common.Vec3(); + v.x = this._angVelX; + v.y = this._angVelY; + v.z = this._angVelZ; + return v; + } + getAngularVelocityTo(angularVelocity) { + angularVelocity.x = this._velX; + angularVelocity.y = this._velY; + angularVelocity.z = this._velZ; + } + setAngularVelocity(angularVelocity) { + if(this._type == 1) { + this._angVelX = 0; + this._angVelY = 0; + this._angVelZ = 0; + } else { + this._angVelX = angularVelocity.x; + this._angVelY = angularVelocity.y; + this._angVelZ = angularVelocity.z; + } + this._sleeping = false; + this._sleepTime = 0; + } + addLinearVelocity(linearVelocityChange) { + if(this._type != 1) { + let dX; + let dY; + let dZ; + dX = linearVelocityChange.x; + dY = linearVelocityChange.y; + dZ = linearVelocityChange.z; + this._velX += dX; + this._velY += dY; + this._velZ += dZ; + } + this._sleeping = false; + this._sleepTime = 0; + } + addAngularVelocity(angularVelocityChange) { + if(this._type != 1) { + let dX; + let dY; + let dZ; + dX = angularVelocityChange.x; + dY = angularVelocityChange.y; + dZ = angularVelocityChange.z; + this._angVelX += dX; + this._angVelY += dY; + this._angVelZ += dZ; + } + this._sleeping = false; + this._sleepTime = 0; + } + applyImpulse(impulse,positionInWorld) { + let impX; + let impY; + let impZ; + impX = impulse.x; + impY = impulse.y; + impZ = impulse.z; + this._velX += impX * this._invMass; + this._velY += impY * this._invMass; + this._velZ += impZ * this._invMass; + let aimpX; + let aimpY; + let aimpZ; + let posX; + let posY; + let posZ; + posX = positionInWorld.x; + posY = positionInWorld.y; + posZ = positionInWorld.z; + posX -= this._transform._positionX; + posY -= this._transform._positionY; + posZ -= this._transform._positionZ; + aimpX = posY * impZ - posZ * impY; + aimpY = posZ * impX - posX * impZ; + aimpZ = posX * impY - posY * impX; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._invInertia00 * aimpX + this._invInertia01 * aimpY + this._invInertia02 * aimpZ; + __tmp__Y = this._invInertia10 * aimpX + this._invInertia11 * aimpY + this._invInertia12 * aimpZ; + __tmp__Z = this._invInertia20 * aimpX + this._invInertia21 * aimpY + this._invInertia22 * aimpZ; + aimpX = __tmp__X; + aimpY = __tmp__Y; + aimpZ = __tmp__Z; + this._angVelX += aimpX; + this._angVelY += aimpY; + this._angVelZ += aimpZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyLinearImpulse(impulse) { + let impX; + let impY; + let impZ; + impX = impulse.x; + impY = impulse.y; + impZ = impulse.z; + this._velX += impX * this._invMass; + this._velY += impY * this._invMass; + this._velZ += impZ * this._invMass; + this._sleeping = false; + this._sleepTime = 0; + } + applyAngularImpulse(impulse) { + let impX; + let impY; + let impZ; + impX = impulse.x; + impY = impulse.y; + impZ = impulse.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._invInertia00 * impX + this._invInertia01 * impY + this._invInertia02 * impZ; + __tmp__Y = this._invInertia10 * impX + this._invInertia11 * impY + this._invInertia12 * impZ; + __tmp__Z = this._invInertia20 * impX + this._invInertia21 * impY + this._invInertia22 * impZ; + impX = __tmp__X; + impY = __tmp__Y; + impZ = __tmp__Z; + this._angVelX += impX; + this._angVelY += impY; + this._angVelZ += impZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyForce(force,positionInWorld) { + let iforceX; + let iforceY; + let iforceZ; + iforceX = force.x; + iforceY = force.y; + iforceZ = force.z; + this._forceX += iforceX; + this._forceY += iforceY; + this._forceZ += iforceZ; + let itorqueX; + let itorqueY; + let itorqueZ; + let posX; + let posY; + let posZ; + posX = positionInWorld.x; + posY = positionInWorld.y; + posZ = positionInWorld.z; + posX -= this._transform._positionX; + posY -= this._transform._positionY; + posZ -= this._transform._positionZ; + itorqueX = posY * iforceZ - posZ * iforceY; + itorqueY = posZ * iforceX - posX * iforceZ; + itorqueZ = posX * iforceY - posY * iforceX; + this._torqueX += itorqueX; + this._torqueY += itorqueY; + this._torqueZ += itorqueZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyForceToCenter(force) { + let iforceX; + let iforceY; + let iforceZ; + iforceX = force.x; + iforceY = force.y; + iforceZ = force.z; + this._forceX += iforceX; + this._forceY += iforceY; + this._forceZ += iforceZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyTorque(torque) { + let itorqueX; + let itorqueY; + let itorqueZ; + itorqueX = torque.x; + itorqueY = torque.y; + itorqueZ = torque.z; + this._torqueX += itorqueX; + this._torqueY += itorqueY; + this._torqueZ += itorqueZ; + this._sleeping = false; + this._sleepTime = 0; + } + getLinearContactImpulse() { + let res = new oimo.common.Vec3(); + res.x = this._linearContactImpulseX; + res.y = this._linearContactImpulseY; + res.z = this._linearContactImpulseZ; + return res; + } + getLinearContactImpulseTo(linearContactImpulse) { + linearContactImpulse.x = this._linearContactImpulseX; + linearContactImpulse.y = this._linearContactImpulseY; + linearContactImpulse.z = this._linearContactImpulseZ; + } + getAngularContactImpulse() { + let res = new oimo.common.Vec3(); + res.x = this._angularContactImpulseX; + res.y = this._angularContactImpulseY; + res.z = this._angularContactImpulseZ; + return res; + } + getAngularContactImpulseTo(angularContactImpulse) { + angularContactImpulse.x = this._angularContactImpulseX; + angularContactImpulse.y = this._angularContactImpulseY; + angularContactImpulse.z = this._angularContactImpulseZ; + } + getGravityScale() { + return this._gravityScale; + } + setGravityScale(gravityScale) { + this._gravityScale = gravityScale; + this._sleeping = false; + this._sleepTime = 0; + } + getLocalPoint(worldPoint) { + let vX; + let vY; + let vZ; + vX = worldPoint.x; + vY = worldPoint.y; + vZ = worldPoint.z; + vX -= this._transform._positionX; + vY -= this._transform._positionY; + vZ -= this._transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getLocalPointTo(worldPoint,localPoint) { + let vX; + let vY; + let vZ; + vX = worldPoint.x; + vY = worldPoint.y; + vZ = worldPoint.z; + vX -= this._transform._positionX; + vY -= this._transform._positionY; + vZ -= this._transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localPoint.x = vX; + localPoint.y = vY; + localPoint.z = vZ; + } + getLocalVector(worldVector) { + let vX; + let vY; + let vZ; + vX = worldVector.x; + vY = worldVector.y; + vZ = worldVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getLocalVectorTo(worldVector,localVector) { + let vX; + let vY; + let vZ; + vX = worldVector.x; + vY = worldVector.y; + vZ = worldVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + } + getWorldPoint(localPoint) { + let vX; + let vY; + let vZ; + vX = localPoint.x; + vY = localPoint.y; + vZ = localPoint.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += this._transform._positionX; + vY += this._transform._positionY; + vZ += this._transform._positionZ; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getWorldPointTo(localPoint,worldPoint) { + let vX; + let vY; + let vZ; + vX = localPoint.x; + vY = localPoint.y; + vZ = localPoint.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += this._transform._positionX; + vY += this._transform._positionY; + vZ += this._transform._positionZ; + worldPoint.x = vX; + worldPoint.y = vY; + worldPoint.z = vZ; + } + getWorldVector(localVector) { + let vX; + let vY; + let vZ; + vX = localVector.x; + vY = localVector.y; + vZ = localVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getWorldVectorTo(localVector,worldVector) { + let vX; + let vY; + let vZ; + vX = localVector.x; + vY = localVector.y; + vZ = localVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + worldVector.x = vX; + worldVector.y = vY; + worldVector.z = vZ; + } + getNumShapes() { + return this._numShapes; + } + getShapeList() { + return this._shapeList; + } + getNumContectLinks() { + return this._numContactLinks; + } + getContactLinkList() { + return this._contactLinkList; + } + getNumJointLinks() { + return this._numJointLinks; + } + getJointLinkList() { + return this._jointLinkList; + } + addShape(shape) { + if(this._shapeList == null) { + this._shapeList = shape; + this._shapeListLast = shape; + } else { + this._shapeListLast._next = shape; + shape._prev = this._shapeListLast; + this._shapeListLast = shape; + } + this._numShapes++; + shape._rigidBody = this; + if(this._world != null) { + let _this = this._world; + shape._proxy = _this._broadPhase.createProxy(shape,shape._aabb); + shape._id = _this._shapeIdCount++; + _this._numShapes++; + } + this.updateMass(); + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + removeShape(shape) { + let prev = shape._prev; + let next = shape._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(shape == this._shapeList) { + this._shapeList = this._shapeList._next; + } + if(shape == this._shapeListLast) { + this._shapeListLast = this._shapeListLast._prev; + } + shape._next = null; + shape._prev = null; + this._numShapes--; + shape._rigidBody = null; + if(this._world != null) { + let _this = this._world; + _this._broadPhase.destroyProxy(shape._proxy); + shape._proxy = null; + shape._id = -1; + let cl = shape._rigidBody._contactLinkList; + while(cl != null) { + let n = cl._next; + let c = cl._contact; + if(c._s1 == shape || c._s2 == shape) { + let _this1 = cl._other; + _this1._sleeping = false; + _this1._sleepTime = 0; + let _this2 = _this._contactManager; + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == _this2._contactList) { + _this2._contactList = _this2._contactList._next; + } + if(c == _this2._contactListLast) { + _this2._contactListLast = _this2._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this3 = c._contactConstraint; + _this3._s1 = null; + _this3._s2 = null; + _this3._b1 = null; + _this3._b2 = null; + _this3._tf1 = null; + _this3._tf2 = null; + c._next = _this2._contactPool; + _this2._contactPool = c; + _this2._numContacts--; + } + cl = n; + } + _this._numShapes--; + } + this.updateMass(); + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + getType() { + return this._type; + } + setType(type) { + this._type = type; + this.updateMass(); + } + wakeUp() { + this._sleeping = false; + this._sleepTime = 0; + } + sleep() { + this._sleeping = true; + this._sleepTime = 0; + } + isSleeping() { + return this._sleeping; + } + getSleepTime() { + return this._sleepTime; + } + setAutoSleep(autoSleepEnabled) { + this._autoSleep = autoSleepEnabled; + this._sleeping = false; + this._sleepTime = 0; + } + getLinearDamping() { + return this._linearDamping; + } + setLinearDamping(damping) { + this._linearDamping = damping; + } + getAngularDamping() { + return this._angularDamping; + } + setAngularDamping(damping) { + this._angularDamping = damping; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.rigidbody.RigidBodyConfig = class oimo_dynamics_rigidbody_RigidBodyConfig { + constructor() { + this.position = new oimo.common.Vec3(); + this.rotation = new oimo.common.Mat3(); + this.linearVelocity = new oimo.common.Vec3(); + this.angularVelocity = new oimo.common.Vec3(); + this.type = 0; + this.autoSleep = true; + this.linearDamping = 0; + this.angularDamping = 0; + } +} +oimo.dynamics.rigidbody.RigidBodyType = class oimo_dynamics_rigidbody_RigidBodyType { +} +oimo.dynamics.rigidbody.Shape = class oimo_dynamics_rigidbody_Shape { + constructor(config) { + this._id = -1; + this._localTransform = new oimo.common.Transform(); + this._ptransform = new oimo.common.Transform(); + this._transform = new oimo.common.Transform(); + let v = config.position; + this._localTransform._positionX = v.x; + this._localTransform._positionY = v.y; + this._localTransform._positionZ = v.z; + let m = config.rotation; + this._localTransform._rotation00 = m.e00; + this._localTransform._rotation01 = m.e01; + this._localTransform._rotation02 = m.e02; + this._localTransform._rotation10 = m.e10; + this._localTransform._rotation11 = m.e11; + this._localTransform._rotation12 = m.e12; + this._localTransform._rotation20 = m.e20; + this._localTransform._rotation21 = m.e21; + this._localTransform._rotation22 = m.e22; + let dst = this._ptransform; + let src = this._localTransform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let dst1 = this._transform; + let src1 = this._localTransform; + dst1._positionX = src1._positionX; + dst1._positionY = src1._positionY; + dst1._positionZ = src1._positionZ; + dst1._rotation00 = src1._rotation00; + dst1._rotation01 = src1._rotation01; + dst1._rotation02 = src1._rotation02; + dst1._rotation10 = src1._rotation10; + dst1._rotation11 = src1._rotation11; + dst1._rotation12 = src1._rotation12; + dst1._rotation20 = src1._rotation20; + dst1._rotation21 = src1._rotation21; + dst1._rotation22 = src1._rotation22; + this._restitution = config.restitution; + this._friction = config.friction; + this._density = config.density; + this._geom = config.geometry; + this._collisionGroup = config.collisionGroup; + this._collisionMask = config.collisionMask; + this._contactCallback = config.contactCallback; + this._aabb = new oimo.collision.geometry.Aabb(); + this._proxy = null; + this.displacement = new oimo.common.Vec3(); + } + getFriction() { + return this._friction; + } + setFriction(friction) { + this._friction = friction; + } + getRestitution() { + return this._restitution; + } + setRestitution(restitution) { + this._restitution = restitution; + } + getLocalTransform() { + let _this = this._localTransform; + let tf = new oimo.common.Transform(); + tf._positionX = _this._positionX; + tf._positionY = _this._positionY; + tf._positionZ = _this._positionZ; + tf._rotation00 = _this._rotation00; + tf._rotation01 = _this._rotation01; + tf._rotation02 = _this._rotation02; + tf._rotation10 = _this._rotation10; + tf._rotation11 = _this._rotation11; + tf._rotation12 = _this._rotation12; + tf._rotation20 = _this._rotation20; + tf._rotation21 = _this._rotation21; + tf._rotation22 = _this._rotation22; + return tf; + } + getLocalTransformTo(transform) { + let transform1 = this._localTransform; + transform._positionX = transform1._positionX; + transform._positionY = transform1._positionY; + transform._positionZ = transform1._positionZ; + transform._rotation00 = transform1._rotation00; + transform._rotation01 = transform1._rotation01; + transform._rotation02 = transform1._rotation02; + transform._rotation10 = transform1._rotation10; + transform._rotation11 = transform1._rotation11; + transform._rotation12 = transform1._rotation12; + transform._rotation20 = transform1._rotation20; + transform._rotation21 = transform1._rotation21; + transform._rotation22 = transform1._rotation22; + } + getTransform() { + let _this = this._transform; + let tf = new oimo.common.Transform(); + tf._positionX = _this._positionX; + tf._positionY = _this._positionY; + tf._positionZ = _this._positionZ; + tf._rotation00 = _this._rotation00; + tf._rotation01 = _this._rotation01; + tf._rotation02 = _this._rotation02; + tf._rotation10 = _this._rotation10; + tf._rotation11 = _this._rotation11; + tf._rotation12 = _this._rotation12; + tf._rotation20 = _this._rotation20; + tf._rotation21 = _this._rotation21; + tf._rotation22 = _this._rotation22; + return tf; + } + getTransformTo(transform) { + let transform1 = this._transform; + transform._positionX = transform1._positionX; + transform._positionY = transform1._positionY; + transform._positionZ = transform1._positionZ; + transform._rotation00 = transform1._rotation00; + transform._rotation01 = transform1._rotation01; + transform._rotation02 = transform1._rotation02; + transform._rotation10 = transform1._rotation10; + transform._rotation11 = transform1._rotation11; + transform._rotation12 = transform1._rotation12; + transform._rotation20 = transform1._rotation20; + transform._rotation21 = transform1._rotation21; + transform._rotation22 = transform1._rotation22; + } + setLocalTransform(transform) { + let _this = this._localTransform; + _this._positionX = transform._positionX; + _this._positionY = transform._positionY; + _this._positionZ = transform._positionZ; + _this._rotation00 = transform._rotation00; + _this._rotation01 = transform._rotation01; + _this._rotation02 = transform._rotation02; + _this._rotation10 = transform._rotation10; + _this._rotation11 = transform._rotation11; + _this._rotation12 = transform._rotation12; + _this._rotation20 = transform._rotation20; + _this._rotation21 = transform._rotation21; + _this._rotation22 = transform._rotation22; + if(this._rigidBody != null) { + let _this = this._rigidBody; + _this.updateMass(); + let s = _this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = _this._ptransform; + let tf2 = _this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } + getDensity() { + return this._density; + } + setDensity(density) { + this._density = density; + if(this._rigidBody != null) { + let _this = this._rigidBody; + _this.updateMass(); + let s = _this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = _this._ptransform; + let tf2 = _this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } + getAabb() { + return this._aabb.clone(); + } + getAabbTo(aabb) { + aabb.copyFrom(this._aabb); + } + getGeometry() { + return this._geom; + } + getRigidBody() { + return this._rigidBody; + } + getCollisionGroup() { + return this._collisionGroup; + } + setCollisionGroup(collisionGroup) { + this._collisionGroup = collisionGroup; + } + getCollisionMask() { + return this._collisionMask; + } + setCollisionMask(collisionMask) { + this._collisionMask = collisionMask; + } + getContactCallback() { + return this._contactCallback; + } + setContactCallback(callback) { + this._contactCallback = callback; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.rigidbody.ShapeConfig = class oimo_dynamics_rigidbody_ShapeConfig { + constructor() { + this.position = new oimo.common.Vec3(); + this.rotation = new oimo.common.Mat3(); + this.friction = oimo.common.Setting.defaultFriction; + this.restitution = oimo.common.Setting.defaultRestitution; + this.density = oimo.common.Setting.defaultDensity; + this.collisionGroup = oimo.common.Setting.defaultCollisionGroup; + this.collisionMask = oimo.common.Setting.defaultCollisionMask; + this.geometry = null; + this.contactCallback = null; + } +} +if(!oimo.m) oimo.m = {}; +oimo.m.M = class oimo_m_M { +} + +oimo.collision.broadphase.BroadPhaseType._BRUTE_FORCE = 1; +oimo.collision.broadphase.BroadPhaseType._BVH = 2; +oimo.collision.broadphase.BroadPhaseType.BRUTE_FORCE = 1; +oimo.collision.broadphase.BroadPhaseType.BVH = 2; +oimo.collision.broadphase.bvh.BvhInsertionStrategy.SIMPLE = 0; +oimo.collision.broadphase.bvh.BvhInsertionStrategy.MINIMIZE_SURFACE_AREA = 1; +oimo.collision.geometry.GeometryType._SPHERE = 0; +oimo.collision.geometry.GeometryType._BOX = 1; +oimo.collision.geometry.GeometryType._CYLINDER = 2; +oimo.collision.geometry.GeometryType._CONE = 3; +oimo.collision.geometry.GeometryType._CAPSULE = 4; +oimo.collision.geometry.GeometryType._CONVEX_HULL = 5; +oimo.collision.geometry.GeometryType._CONVEX_MIN = 0; +oimo.collision.geometry.GeometryType._CONVEX_MAX = 5; +oimo.collision.geometry.GeometryType.SPHERE = 0; +oimo.collision.geometry.GeometryType.BOX = 1; +oimo.collision.geometry.GeometryType.CYLINDER = 2; +oimo.collision.geometry.GeometryType.CONE = 3; +oimo.collision.geometry.GeometryType.CAPSULE = 4; +oimo.collision.geometry.GeometryType.CONVEX_HULL = 5; +oimo.collision.narrowphase.detector.BoxBoxDetector.EDGE_BIAS_MULT = 1.0; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.OK = 0; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.INVALID_TRIANGLE = 1; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.NO_ADJACENT_PAIR_INDEX = 2; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.NO_ADJACENT_TRIANGLE = 3; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.EDGE_LOOP_BROKEN = 4; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.NO_OUTER_TRIANGLE = 5; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.TRIANGLE_INVISIBLE = 6; +oimo.collision.narrowphase.detector.gjkepa.EpaTriangle.count = 0; +oimo.common.Vec3.numCreations = 0; +oimo.common.Setting.defaultFriction = 0.2; +oimo.common.Setting.defaultRestitution = 0.2; +oimo.common.Setting.defaultDensity = 1; +oimo.common.Setting.defaultCollisionGroup = 1; +oimo.common.Setting.defaultCollisionMask = 1; +oimo.common.Setting.maxTranslationPerStep = 20; +oimo.common.Setting.maxRotationPerStep = 3.14159265358979; +oimo.common.Setting.bvhProxyPadding = 0.1; +oimo.common.Setting.bvhIncrementalCollisionThreshold = 0.45; +oimo.common.Setting.defaultGJKMargin = 0.05; +oimo.common.Setting.enableGJKCaching = true; +oimo.common.Setting.maxEPAVertices = 128; +oimo.common.Setting.maxEPAPolyhedronFaces = 128; +oimo.common.Setting.contactEnableBounceThreshold = 0.5; +oimo.common.Setting.velocityBaumgarte = 0.2; +oimo.common.Setting.positionSplitImpulseBaumgarte = 0.4; +oimo.common.Setting.positionNgsBaumgarte = 1.0; +oimo.common.Setting.contactUseAlternativePositionCorrectionAlgorithmDepthThreshold = 0.05; +oimo.common.Setting.defaultContactPositionCorrectionAlgorithm = 0; +oimo.common.Setting.alternativeContactPositionCorrectionAlgorithm = 1; +oimo.common.Setting.contactPersistenceThreshold = 0.05; +oimo.common.Setting.maxManifoldPoints = 4; +oimo.common.Setting.defaultJointConstraintSolverType = 0; +oimo.common.Setting.defaultJointPositionCorrectionAlgorithm = 0; +oimo.common.Setting.jointWarmStartingFactorForBaungarte = 0.8; +oimo.common.Setting.jointWarmStartingFactor = 0.95; +oimo.common.Setting.minSpringDamperDampingRatio = 1e-6; +oimo.common.Setting.minRagdollMaxSwingAngle = 1e-6; +oimo.common.Setting.maxJacobianRows = 6; +oimo.common.Setting.directMlcpSolverEps = 1e-9; +oimo.common.Setting.islandInitialRigidBodyArraySize = 128; +oimo.common.Setting.islandInitialConstraintArraySize = 128; +oimo.common.Setting.sleepingVelocityThreshold = 0.2; +oimo.common.Setting.sleepingAngularVelocityThreshold = 0.5; +oimo.common.Setting.sleepingTimeThreshold = 1.0; +oimo.common.Setting.disableSleeping = false; +oimo.common.Setting.linearSlop = 0.005; +oimo.common.Setting.angularSlop = 0.017453292519943278; +oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance = new oimo.collision.narrowphase.detector.gjkepa.GjkEpa(); +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._SUCCEEDED = 0; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._GJK_FAILED_TO_MAKE_TETRAHEDRON = 1; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._GJK_DID_NOT_CONVERGE = 2; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._EPA_FAILED_TO_INIT = 257; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._EPA_FAILED_TO_ADD_VERTEX = 258; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._EPA_DID_NOT_CONVERGE = 259; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.SUCCEEDED = 0; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.GJK_FAILED_TO_MAKE_TETRAHEDRON = 1; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.GJK_DID_NOT_CONVERGE = 2; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_INIT = 257; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_ADD_VERTEX = 258; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_DID_NOT_CONVERGE = 259; +oimo.common.Mat3.numCreations = 0; +oimo.common.Mat4.numCreations = 0; +oimo.common.MathUtil.POSITIVE_INFINITY = 1e65536; +oimo.common.MathUtil.NEGATIVE_INFINITY = -1e65536; +oimo.common.MathUtil.PI = 3.14159265358979; +oimo.common.MathUtil.TWO_PI = 6.28318530717958; +oimo.common.MathUtil.HALF_PI = 1.570796326794895; +oimo.common.MathUtil.TO_RADIANS = 0.017453292519943278; +oimo.common.MathUtil.TO_DEGREES = 57.29577951308238; +oimo.common.Quat.numCreations = 0; +oimo.dynamics.common.DebugDraw.SPHERE_PHI_DIVISION = 8; +oimo.dynamics.common.DebugDraw.SPHERE_THETA_DIVISION = 4; +oimo.dynamics.common.DebugDraw.CIRCLE_THETA_DIVISION = 8; +oimo.dynamics.common.Performance.broadPhaseCollisionTime = 0; +oimo.dynamics.common.Performance.narrowPhaseCollisionTime = 0; +oimo.dynamics.common.Performance.dynamicsTime = 0; +oimo.dynamics.common.Performance.totalTime = 0; +oimo.dynamics.constraint.PositionCorrectionAlgorithm._BAUMGARTE = 0; +oimo.dynamics.constraint.PositionCorrectionAlgorithm._SPLIT_IMPULSE = 1; +oimo.dynamics.constraint.PositionCorrectionAlgorithm._NGS = 2; +oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE = 0; +oimo.dynamics.constraint.PositionCorrectionAlgorithm.SPLIT_IMPULSE = 1; +oimo.dynamics.constraint.PositionCorrectionAlgorithm.NGS = 2; +oimo.dynamics.constraint.info.JacobianRow.BIT_LINEAR_SET = 1; +oimo.dynamics.constraint.info.JacobianRow.BIT_ANGULAR_SET = 2; +oimo.dynamics.constraint.joint.JointType._SPHERICAL = 0; +oimo.dynamics.constraint.joint.JointType._REVOLUTE = 1; +oimo.dynamics.constraint.joint.JointType._CYLINDRICAL = 2; +oimo.dynamics.constraint.joint.JointType._PRISMATIC = 3; +oimo.dynamics.constraint.joint.JointType._UNIVERSAL = 4; +oimo.dynamics.constraint.joint.JointType._RAGDOLL = 5; +oimo.dynamics.constraint.joint.JointType._GENERIC = 6; +oimo.dynamics.constraint.joint.JointType.SPHERICAL = 0; +oimo.dynamics.constraint.joint.JointType.REVOLUTE = 1; +oimo.dynamics.constraint.joint.JointType.CYLINDRICAL = 2; +oimo.dynamics.constraint.joint.JointType.PRISMATIC = 3; +oimo.dynamics.constraint.joint.JointType.UNIVERSAL = 4; +oimo.dynamics.constraint.joint.JointType.RAGDOLL = 5; +oimo.dynamics.constraint.joint.JointType.GENERIC = 6; +oimo.dynamics.constraint.solver.ConstraintSolverType._ITERATIVE = 0; +oimo.dynamics.constraint.solver.ConstraintSolverType._DIRECT = 1; +oimo.dynamics.constraint.solver.ConstraintSolverType.ITERATIVE = 0; +oimo.dynamics.constraint.solver.ConstraintSolverType.DIRECT = 1; +oimo.dynamics.rigidbody.RigidBodyType._DYNAMIC = 0; +oimo.dynamics.rigidbody.RigidBodyType._STATIC = 1; +oimo.dynamics.rigidbody.RigidBodyType._KINEMATIC = 2; +oimo.dynamics.rigidbody.RigidBodyType.DYNAMIC = 0; +oimo.dynamics.rigidbody.RigidBodyType.STATIC = 1; +oimo.dynamics.rigidbody.RigidBodyType.KINEMATIC = 2; +export {oimo}; diff --git a/jsm/libs/OimoPhysics/index.js b/jsm/libs/OimoPhysics/index.js new file mode 100644 index 0000000..0bf5789 --- /dev/null +++ b/jsm/libs/OimoPhysics/index.js @@ -0,0 +1,43 @@ +import {oimo} from './OimoPhysics.js'; + +// dynamics +export const World = oimo.dynamics.World; +export const RigidBodyType = oimo.dynamics.rigidbody.RigidBodyType; +export const RigidBodyConfig = oimo.dynamics.rigidbody.RigidBodyConfig; +export const ShapeConfig = oimo.dynamics.rigidbody.ShapeConfig; +export const RigidBody = oimo.dynamics.rigidbody.RigidBody; +export const Shape = oimo.dynamics.rigidbody.Shape; +export const SphericalJoint = oimo.dynamics.constraint.joint.SphericalJoint; +export const RevoluteJointConfig = oimo.dynamics.constraint.joint.RevoluteJointConfig; +export const UniversalJointConfig = oimo.dynamics.constraint.joint.UniversalJointConfig; +export const CylindricalJoint = oimo.dynamics.constraint.joint.CylindricalJoint; +export const PrismaticJoint = oimo.dynamics.constraint.joint.PrismaticJoint; +export const PrismaticJointConfig = oimo.dynamics.constraint.joint.PrismaticJointConfig; +export const RevoluteJoint = oimo.dynamics.constraint.joint.RevoluteJoint; +export const RagdollJoint = oimo.dynamics.constraint.joint.RagdollJoint; +export const CylindricalJointConfig = oimo.dynamics.constraint.joint.CylindricalJointConfig; +export const SphericalJointConfig = oimo.dynamics.constraint.joint.SphericalJointConfig; +export const RagdollJointConfig = oimo.dynamics.constraint.joint.RagdollJointConfig; +export const SpringDamper = oimo.dynamics.constraint.joint.SpringDamper; +export const TranslationalLimitMotor = oimo.dynamics.constraint.joint.TranslationalLimitMotor; +export const RotationalLimitMotor = oimo.dynamics.constraint.joint.RotationalLimitMotor; +export const UniversalJoint = oimo.dynamics.constraint.joint.UniversalJoint; + +// common +export const Vec3 = oimo.common.Vec3; +export const Quat = oimo.common.Quat; +export const Mat3 = oimo.common.Mat3; +export const MathUtil = oimo.common.MathUtil; +export const Transform = oimo.common.Transform; + +// collision +export const OCapsuleGeometry = oimo.collision.geometry.CapsuleGeometry; +export const OConvexHullGeometry = oimo.collision.geometry.ConvexHullGeometry; +export const OBoxGeometry = oimo.collision.geometry.BoxGeometry; +export const OSphereGeometry = oimo.collision.geometry.SphereGeometry; +export const OCylinderGeometry = oimo.collision.geometry.CylinderGeometry; +export const OConeGeometry = oimo.collision.geometry.ConeGeometry; +export const OGeometry = oimo.collision.geometry.Geometry; + +// callback +export const RayCastClosest = oimo.dynamics.callback.RayCastClosest; diff --git a/jsm/libs/chevrotain.module.min.js b/jsm/libs/chevrotain.module.min.js new file mode 100644 index 0000000..424790c --- /dev/null +++ b/jsm/libs/chevrotain.module.min.js @@ -0,0 +1,141 @@ +/*! chevrotain - v9.0.1 */ +var R=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var Er=R(Pt=>{"use strict";Object.defineProperty(Pt,"__esModule",{value:!0});Pt.VERSION=void 0;Pt.VERSION="9.0.1"});var k=R((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(t,e){for(var r=0,n=e.length,i=t.length;r{(function(t,e){typeof define=="function"&&define.amd?define([],e):typeof St=="object"&&St.exports?St.exports=e():t.regexpToAst=e()})(typeof self!="undefined"?self:sn,function(){function t(){}t.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},t.prototype.restoreState=function(u){this.idx=u.idx,this.input=u.input,this.groupIdx=u.groupIdx},t.prototype.pattern=function(u){this.idx=0,this.input=u,this.groupIdx=0,this.consumeChar("/");var d=this.disjunction();this.consumeChar("/");for(var A={type:"Flags",loc:{begin:this.idx,end:u.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(A,"global");break;case"i":o(A,"ignoreCase");break;case"m":o(A,"multiLine");break;case"u":o(A,"unicode");break;case"y":o(A,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:A,value:d,loc:this.loc(0)}},t.prototype.disjunction=function(){var u=[],d=this.idx;for(u.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),u.push(this.alternative());return{type:"Disjunction",value:u,loc:this.loc(d)}},t.prototype.alternative=function(){for(var u=[],d=this.idx;this.isTerm();)u.push(this.term());return{type:"Alternative",value:u,loc:this.loc(d)}},t.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},t.prototype.assertion=function(){var u=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(u)};case"$":return{type:"EndAnchor",loc:this.loc(u)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(u)};case"B":return{type:"NonWordBoundary",loc:this.loc(u)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var d;switch(this.popChar()){case"=":d="Lookahead";break;case"!":d="NegativeLookahead";break}s(d);var A=this.disjunction();return this.consumeChar(")"),{type:d,value:A,loc:this.loc(u)}}c()},t.prototype.quantifier=function(u){var d,A=this.idx;switch(this.popChar()){case"*":d={atLeast:0,atMost:Infinity};break;case"+":d={atLeast:1,atMost:Infinity};break;case"?":d={atLeast:0,atMost:1};break;case"{":var _=this.integerIncludingZero();switch(this.popChar()){case"}":d={atLeast:_,atMost:_};break;case",":var g;this.isDigit()?(g=this.integerIncludingZero(),d={atLeast:_,atMost:g}):d={atLeast:_,atMost:Infinity},this.consumeChar("}");break}if(u===!0&&d===void 0)return;s(d);break}if(!(u===!0&&d===void 0))return s(d),this.peekChar(0)==="?"?(this.consumeChar("?"),d.greedy=!1):d.greedy=!0,d.type="Quantifier",d.loc=this.loc(A),d},t.prototype.atom=function(){var u,d=this.idx;switch(this.peekChar()){case".":u=this.dotAll();break;case"\\":u=this.atomEscape();break;case"[":u=this.characterClass();break;case"(":u=this.group();break}return u===void 0&&this.isPatternCharacter()&&(u=this.patternCharacter()),s(u),u.loc=this.loc(d),this.isQuantifier()&&(u.quantifier=this.quantifier()),u},t.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[i(` +`),i("\r"),i("\u2028"),i("\u2029")]}},t.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},t.prototype.decimalEscapeAtom=function(){var u=this.positiveInteger();return{type:"GroupBackReference",value:u}},t.prototype.characterClassEscape=function(){var u,d=!1;switch(this.popChar()){case"d":u=p;break;case"D":u=p,d=!0;break;case"s":u=m;break;case"S":u=m,d=!0;break;case"w":u=l;break;case"W":u=l,d=!0;break}return s(u),{type:"Set",value:u,complement:d}},t.prototype.controlEscapeAtom=function(){var u;switch(this.popChar()){case"f":u=i("\f");break;case"n":u=i(` +`);break;case"r":u=i("\r");break;case"t":u=i(" ");break;case"v":u=i("\v");break}return s(u),{type:"Character",value:u}},t.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var u=this.popChar();if(/[a-zA-Z]/.test(u)===!1)throw Error("Invalid ");var d=u.toUpperCase().charCodeAt(0)-64;return{type:"Character",value:d}},t.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:i("\0")}},t.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},t.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},t.prototype.identityEscapeAtom=function(){var u=this.popChar();return{type:"Character",value:i(u)}},t.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case` +`:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:var u=this.popChar();return{type:"Character",value:i(u)}}},t.prototype.characterClass=function(){var u=[],d=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),d=!0);this.isClassAtom();){var A=this.classAtom(),_=A.type==="Character";if(_&&this.isRangeDash()){this.consumeChar("-");var g=this.classAtom(),y=g.type==="Character";if(y){if(g.value=this.input.length)throw Error("Unexpected end of input");this.idx++},t.prototype.loc=function(u){return{begin:u,end:this.idx}};var e=/[0-9a-fA-F]/,r=/[0-9]/,n=/[1-9]/;function i(u){return u.charCodeAt(0)}function a(u,d){u.length!==void 0?u.forEach(function(A){d.push(A)}):d.push(u)}function o(u,d){if(u[d]===!0)throw"duplicate flag "+d;u[d]=!0}function s(u){if(u===void 0)throw Error("Internal Error - Should never get here!")}function c(){throw Error("Internal Error - Should never get here!")}var f,p=[];for(f=i("0");f<=i("9");f++)p.push(f);var l=[i("_")].concat(p);for(f=i("a");f<=i("z");f++)l.push(f);for(f=i("A");f<=i("Z");f++)l.push(f);var m=[i(" "),i("\f"),i(` +`),i("\r"),i(" "),i("\v"),i(" "),i("\xA0"),i("\u1680"),i("\u2000"),i("\u2001"),i("\u2002"),i("\u2003"),i("\u2004"),i("\u2005"),i("\u2006"),i("\u2007"),i("\u2008"),i("\u2009"),i("\u200A"),i("\u2028"),i("\u2029"),i("\u202F"),i("\u205F"),i("\u3000"),i("\uFEFF")];function v(){}return v.prototype.visitChildren=function(u){for(var d in u){var A=u[d];u.hasOwnProperty(d)&&(A.type!==void 0?this.visit(A):Array.isArray(A)&&A.forEach(function(_){this.visit(_)},this))}},v.prototype.visit=function(u){switch(u.type){case"Pattern":this.visitPattern(u);break;case"Flags":this.visitFlags(u);break;case"Disjunction":this.visitDisjunction(u);break;case"Alternative":this.visitAlternative(u);break;case"StartAnchor":this.visitStartAnchor(u);break;case"EndAnchor":this.visitEndAnchor(u);break;case"WordBoundary":this.visitWordBoundary(u);break;case"NonWordBoundary":this.visitNonWordBoundary(u);break;case"Lookahead":this.visitLookahead(u);break;case"NegativeLookahead":this.visitNegativeLookahead(u);break;case"Character":this.visitCharacter(u);break;case"Set":this.visitSet(u);break;case"Group":this.visitGroup(u);break;case"GroupBackReference":this.visitGroupBackReference(u);break;case"Quantifier":this.visitQuantifier(u);break}this.visitChildren(u)},v.prototype.visitPattern=function(u){},v.prototype.visitFlags=function(u){},v.prototype.visitDisjunction=function(u){},v.prototype.visitAlternative=function(u){},v.prototype.visitStartAnchor=function(u){},v.prototype.visitEndAnchor=function(u){},v.prototype.visitWordBoundary=function(u){},v.prototype.visitNonWordBoundary=function(u){},v.prototype.visitLookahead=function(u){},v.prototype.visitNegativeLookahead=function(u){},v.prototype.visitCharacter=function(u){},v.prototype.visitSet=function(u){},v.prototype.visitGroup=function(u){},v.prototype.visitGroupBackReference=function(u){},v.prototype.visitQuantifier=function(u){},{RegExpParser:t,BaseRegExpVisitor:v,VERSION:"0.5.0"}})});var Lt=R(He=>{"use strict";Object.defineProperty(He,"__esModule",{value:!0});He.clearRegExpParserCache=He.getRegExpAst=void 0;var Ga=xt(),Ct={},Wa=new Ga.RegExpParser;function Ba(t){var e=t.toString();if(Ct.hasOwnProperty(e))return Ct[e];var r=Wa.pattern(e);return Ct[e]=r,r}He.getRegExpAst=Ba;function qa(){Ct={}}He.clearRegExpParserCache=qa});var pn=R(re=>{"use strict";var ja=re&&re.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(re,"__esModule",{value:!0});re.canMatchCharCode=re.firstCharOptimizedIndices=re.getOptimizedStartCodesIndices=re.failedOptimizationPrefixMsg=void 0;var un=xt(),pe=k(),cn=Lt(),Ce=Tr(),ln="Complement Sets are not supported for first char optimization";re.failedOptimizationPrefixMsg=`Unable to use "first char" lexer optimizations: +`;function Va(t,e){e===void 0&&(e=!1);try{var r=cn.getRegExpAst(t),n=Mt(r.value,{},r.flags.ignoreCase);return n}catch(a){if(a.message===ln)e&&pe.PRINT_WARNING(""+re.failedOptimizationPrefixMsg+(" Unable to optimize: < "+t.toString()+` > +`)+` Complement Sets cannot be automatically optimized. + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.`);else{var i="";e&&(i=` + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.`),pe.PRINT_ERROR(re.failedOptimizationPrefixMsg+` +`+(" Failed parsing: < "+t.toString()+` > +`)+(" Using the regexp-to-ast library version: "+un.VERSION+` +`)+" Please open an issue at: https://github.com/bd82/regexp-to-ast/issues"+i)}}return[]}re.getOptimizedStartCodesIndices=Va;function Mt(t,e,r){switch(t.type){case"Disjunction":for(var n=0;n=Ce.minOptimizationVal)for(var m=p.from>=Ce.minOptimizationVal?p.from:Ce.minOptimizationVal,v=p.to,u=Ce.charCodeToOptimizedIndex(m),d=Ce.charCodeToOptimizedIndex(v),A=u;A<=d;A++)e[A]=A}}});break;case"Group":Mt(o.value,e,r);break;default:throw Error("Non Exhaustive Match")}var s=o.quantifier!==void 0&&o.quantifier.atLeast===0;if(o.type==="Group"&&yr(o)===!1||o.type!=="Group"&&s===!1)break}break;default:throw Error("non exhaustive match!")}return pe.values(e)}re.firstCharOptimizedIndices=Mt;function bt(t,e,r){var n=Ce.charCodeToOptimizedIndex(t);e[n]=n,r===!0&&Ka(t,e)}function Ka(t,e){var r=String.fromCharCode(t),n=r.toUpperCase();if(n!==r){var i=Ce.charCodeToOptimizedIndex(n.charCodeAt(0));e[i]=i}else{var a=r.toLowerCase();if(a!==r){var i=Ce.charCodeToOptimizedIndex(a.charCodeAt(0));e[i]=i}}}function fn(t,e){return pe.find(t.value,function(r){if(typeof r=="number")return pe.contains(e,r);var n=r;return pe.find(e,function(i){return n.from<=i&&i<=n.to})!==void 0})}function yr(t){return t.quantifier&&t.quantifier.atLeast===0?!0:t.value?pe.isArray(t.value)?pe.every(t.value,yr):yr(t.value):!1}var za=function(t){ja(e,t);function e(r){var n=t.call(this)||this;return n.targetCharCodes=r,n.found=!1,n}return e.prototype.visitChildren=function(r){if(this.found!==!0){switch(r.type){case"Lookahead":this.visitLookahead(r);return;case"NegativeLookahead":this.visitNegativeLookahead(r);return}t.prototype.visitChildren.call(this,r)}},e.prototype.visitCharacter=function(r){pe.contains(this.targetCharCodes,r.value)&&(this.found=!0)},e.prototype.visitSet=function(r){r.complement?fn(r,this.targetCharCodes)===void 0&&(this.found=!0):fn(r,this.targetCharCodes)!==void 0&&(this.found=!0)},e}(un.BaseRegExpVisitor);function Ha(t,e){if(e instanceof RegExp){var r=cn.getRegExpAst(e),n=new za(t);return n.visit(r),n.found}else return pe.find(e,function(i){return pe.contains(t,i.charCodeAt(0))})!==void 0}re.canMatchCharCode=Ha});var Tr=R(T=>{"use strict";var hn=T&&T.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(T,"__esModule",{value:!0});T.charCodeToOptimizedIndex=T.minOptimizationVal=T.buildLineBreakIssueMessage=T.LineTerminatorOptimizedTester=T.isShortPattern=T.isCustomPattern=T.cloneEmptyGroups=T.performWarningRuntimeChecks=T.performRuntimeChecks=T.addStickyFlag=T.addStartOfInput=T.findUnreachablePatterns=T.findModesThatDoNotExist=T.findInvalidGroupType=T.findDuplicatePatterns=T.findUnsupportedFlags=T.findStartOfInputAnchor=T.findEmptyMatchRegExps=T.findEndOfInputAnchor=T.findInvalidPatterns=T.findMissingPatterns=T.validatePatterns=T.analyzeTokenTypes=T.enableSticky=T.disableSticky=T.SUPPORT_STICKY=T.MODES=T.DEFAULT_MODE=void 0;var dn=xt(),F=ft(),h=k(),Ye=pn(),vn=Lt(),Ae="PATTERN";T.DEFAULT_MODE="defaultMode";T.MODES="modes";T.SUPPORT_STICKY=typeof new RegExp("(?:)").sticky=="boolean";function Ya(){T.SUPPORT_STICKY=!1}T.disableSticky=Ya;function Xa(){T.SUPPORT_STICKY=!0}T.enableSticky=Xa;function Za(t,e){e=h.defaults(e,{useSticky:T.SUPPORT_STICKY,debug:!1,safeMode:!1,positionTracking:"full",lineTerminatorCharacters:["\r",` +`],tracer:function(g,y){return y()}});var r=e.tracer;r("initCharCodeToOptimizedIndexMap",function(){$a()});var n;r("Reject Lexer.NA",function(){n=h.reject(t,function(g){return g[Ae]===F.Lexer.NA})});var i=!1,a;r("Transform Patterns",function(){i=!1,a=h.map(n,function(g){var y=g[Ae];if(h.isRegExp(y)){var b=y.source;return b.length===1&&b!=="^"&&b!=="$"&&b!=="."&&!y.ignoreCase?b:b.length===2&&b[0]==="\\"&&!h.contains(["d","D","s","S","t","r","n","t","0","c","b","B","f","v","w","W"],b[1])?b[1]:e.useSticky?gr(y):_r(y)}else{if(h.isFunction(y))return i=!0,{exec:y};if(h.has(y,"exec"))return i=!0,y;if(typeof y=="string"){if(y.length===1)return y;var L=y.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&"),se=new RegExp(L);return e.useSticky?gr(se):_r(se)}else throw Error("non exhaustive match")}})});var o,s,c,f,p;r("misc mapping",function(){o=h.map(n,function(g){return g.tokenTypeIdx}),s=h.map(n,function(g){var y=g.GROUP;if(y!==F.Lexer.SKIPPED){if(h.isString(y))return y;if(h.isUndefined(y))return!1;throw Error("non exhaustive match")}}),c=h.map(n,function(g){var y=g.LONGER_ALT;if(y){var b=h.indexOf(n,y);return b}}),f=h.map(n,function(g){return g.PUSH_MODE}),p=h.map(n,function(g){return h.has(g,"POP_MODE")})});var l;r("Line Terminator Handling",function(){var g=Tn(e.lineTerminatorCharacters);l=h.map(n,function(y){return!1}),e.positionTracking!=="onlyOffset"&&(l=h.map(n,function(y){if(h.has(y,"LINE_BREAKS"))return y.LINE_BREAKS;if(En(y,g)===!1)return Ye.canMatchCharCode(g,y.PATTERN)}))});var m,v,u,d;r("Misc Mapping #2",function(){m=h.map(n,Ar),v=h.map(a,mn),u=h.reduce(n,function(g,y){var b=y.GROUP;return h.isString(b)&&b!==F.Lexer.SKIPPED&&(g[b]=[]),g},{}),d=h.map(a,function(g,y){return{pattern:a[y],longerAlt:c[y],canLineTerminator:l[y],isCustom:m[y],short:v[y],group:s[y],push:f[y],pop:p[y],tokenTypeIdx:o[y],tokenType:n[y]}})});var A=!0,_=[];return e.safeMode||r("First Char Optimization",function(){_=h.reduce(n,function(g,y,b){if(typeof y.PATTERN=="string"){var L=y.PATTERN.charCodeAt(0),se=Or(L);Rr(g,se,d[b])}else if(h.isArray(y.START_CHARS_HINT)){var fe;h.forEach(y.START_CHARS_HINT,function(ue){var Q=typeof ue=="string"?ue.charCodeAt(0):ue,te=Or(Q);fe!==te&&(fe=te,Rr(g,te,d[b]))})}else if(h.isRegExp(y.PATTERN))if(y.PATTERN.unicode)A=!1,e.ensureOptimizations&&h.PRINT_ERROR(""+Ye.failedOptimizationPrefixMsg+(" Unable to analyze < "+y.PATTERN.toString()+` > pattern. +`)+` The regexp unicode flag is not currently supported by the regexp-to-ast library. + This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNICODE_OPTIMIZE`);else{var Z=Ye.getOptimizedStartCodesIndices(y.PATTERN,e.ensureOptimizations);h.isEmpty(Z)&&(A=!1),h.forEach(Z,function(ue){Rr(g,ue,d[b])})}else e.ensureOptimizations&&h.PRINT_ERROR(""+Ye.failedOptimizationPrefixMsg+(" TokenType: <"+y.name+`> is using a custom token pattern without providing parameter. +`)+` This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_OPTIMIZE`),A=!1;return g},[])}),r("ArrayPacking",function(){_=h.packArray(_)}),{emptyGroups:u,patternIdxToConfig:d,charCodeToPatternIdxToConfig:_,hasCustom:i,canBeOptimized:A}}T.analyzeTokenTypes=Za;function Ja(t,e){var r=[],n=yn(t);r=r.concat(n.errors);var i=_n(n.valid),a=i.valid;return r=r.concat(i.errors),r=r.concat(Qa(a)),r=r.concat(gn(a)),r=r.concat(An(a,e)),r=r.concat(Rn(a)),r}T.validatePatterns=Ja;function Qa(t){var e=[],r=h.filter(t,function(n){return h.isRegExp(n[Ae])});return e=e.concat(On(r)),e=e.concat(In(r)),e=e.concat(kn(r)),e=e.concat(Pn(r)),e=e.concat(Nn(r)),e}function yn(t){var e=h.filter(t,function(i){return!h.has(i,Ae)}),r=h.map(e,function(i){return{message:"Token Type: ->"+i.name+"<- missing static 'PATTERN' property",type:F.LexerDefinitionErrorType.MISSING_PATTERN,tokenTypes:[i]}}),n=h.difference(t,e);return{errors:r,valid:n}}T.findMissingPatterns=yn;function _n(t){var e=h.filter(t,function(i){var a=i[Ae];return!h.isRegExp(a)&&!h.isFunction(a)&&!h.has(a,"exec")&&!h.isString(a)}),r=h.map(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' can only be a RegExp, a Function matching the {CustomPatternMatcherFunc} type or an Object matching the {ICustomPattern} interface.",type:F.LexerDefinitionErrorType.INVALID_PATTERN,tokenTypes:[i]}}),n=h.difference(t,e);return{errors:r,valid:n}}T.findInvalidPatterns=_n;var eo=/[^\\][\$]/;function On(t){var e=function(i){hn(a,i);function a(){var o=i!==null&&i.apply(this,arguments)||this;return o.found=!1,o}return a.prototype.visitEndAnchor=function(o){this.found=!0},a}(dn.BaseRegExpVisitor),r=h.filter(t,function(i){var a=i[Ae];try{var o=vn.getRegExpAst(a),s=new e;return s.visit(o),s.found}catch(c){return eo.test(a.source)}}),n=h.map(r,function(i){return{message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain end of input anchor '$' + See chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:F.LexerDefinitionErrorType.EOI_ANCHOR_FOUND,tokenTypes:[i]}});return n}T.findEndOfInputAnchor=On;function Nn(t){var e=h.filter(t,function(n){var i=n[Ae];return i.test("")}),r=h.map(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'PATTERN' must not match an empty string",type:F.LexerDefinitionErrorType.EMPTY_MATCH_PATTERN,tokenTypes:[n]}});return r}T.findEmptyMatchRegExps=Nn;var to=/[^\\[][\^]|^\^/;function In(t){var e=function(i){hn(a,i);function a(){var o=i!==null&&i.apply(this,arguments)||this;return o.found=!1,o}return a.prototype.visitStartAnchor=function(o){this.found=!0},a}(dn.BaseRegExpVisitor),r=h.filter(t,function(i){var a=i[Ae];try{var o=vn.getRegExpAst(a),s=new e;return s.visit(o),s.found}catch(c){return to.test(a.source)}}),n=h.map(r,function(i){return{message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain start of input anchor '^' + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:F.LexerDefinitionErrorType.SOI_ANCHOR_FOUND,tokenTypes:[i]}});return n}T.findStartOfInputAnchor=In;function kn(t){var e=h.filter(t,function(n){var i=n[Ae];return i instanceof RegExp&&(i.multiline||i.global)}),r=h.map(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'PATTERN' may NOT contain global('g') or multiline('m')",type:F.LexerDefinitionErrorType.UNSUPPORTED_FLAGS_FOUND,tokenTypes:[n]}});return r}T.findUnsupportedFlags=kn;function Pn(t){var e=[],r=h.map(t,function(a){return h.reduce(t,function(o,s){return a.PATTERN.source===s.PATTERN.source&&!h.contains(e,s)&&s.PATTERN!==F.Lexer.NA&&(e.push(s),o.push(s)),o},[])});r=h.compact(r);var n=h.filter(r,function(a){return a.length>1}),i=h.map(n,function(a){var o=h.map(a,function(c){return c.name}),s=h.first(a).PATTERN;return{message:"The same RegExp pattern ->"+s+"<-"+("has been used in all of the following Token Types: "+o.join(", ")+" <-"),type:F.LexerDefinitionErrorType.DUPLICATE_PATTERNS_FOUND,tokenTypes:a}});return i}T.findDuplicatePatterns=Pn;function gn(t){var e=h.filter(t,function(n){if(!h.has(n,"GROUP"))return!1;var i=n.GROUP;return i!==F.Lexer.SKIPPED&&i!==F.Lexer.NA&&!h.isString(i)}),r=h.map(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",type:F.LexerDefinitionErrorType.INVALID_GROUP_TYPE_FOUND,tokenTypes:[n]}});return r}T.findInvalidGroupType=gn;function An(t,e){var r=h.filter(t,function(i){return i.PUSH_MODE!==void 0&&!h.contains(e,i.PUSH_MODE)}),n=h.map(r,function(i){var a="Token Type: ->"+i.name+"<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->"+i.PUSH_MODE+"<-which does not exist";return{message:a,type:F.LexerDefinitionErrorType.PUSH_MODE_DOES_NOT_EXIST,tokenTypes:[i]}});return n}T.findModesThatDoNotExist=An;function Rn(t){var e=[],r=h.reduce(t,function(n,i,a){var o=i.PATTERN;return o===F.Lexer.NA||(h.isString(o)?n.push({str:o,idx:a,tokenType:i}):h.isRegExp(o)&&no(o)&&n.push({str:o.source,idx:a,tokenType:i})),n},[]);return h.forEach(t,function(n,i){h.forEach(r,function(a){var o=a.str,s=a.idx,c=a.tokenType;if(i"+n.name+"<-")+`in the lexer's definition. +See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNREACHABLE`;e.push({message:f,type:F.LexerDefinitionErrorType.UNREACHABLE_PATTERN,tokenTypes:[n,c]})}})}),e}T.findUnreachablePatterns=Rn;function ro(t,e){if(h.isRegExp(e)){var r=e.exec(t);return r!==null&&r.index===0}else{if(h.isFunction(e))return e(t,0,[],{});if(h.has(e,"exec"))return e.exec(t,0,[],{});if(typeof e=="string")return e===t;throw Error("non exhaustive match")}}function no(t){var e=[".","\\","[","]","|","^","$","(",")","?","*","+","{"];return h.find(e,function(r){return t.source.indexOf(r)!==-1})===void 0}function _r(t){var e=t.ignoreCase?"i":"";return new RegExp("^(?:"+t.source+")",e)}T.addStartOfInput=_r;function gr(t){var e=t.ignoreCase?"iy":"y";return new RegExp(""+t.source,e)}T.addStickyFlag=gr;function io(t,e,r){var n=[];return h.has(t,T.DEFAULT_MODE)||n.push({message:"A MultiMode Lexer cannot be initialized without a <"+T.DEFAULT_MODE+`> property in its definition +`,type:F.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE}),h.has(t,T.MODES)||n.push({message:"A MultiMode Lexer cannot be initialized without a <"+T.MODES+`> property in its definition +`,type:F.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY}),h.has(t,T.MODES)&&h.has(t,T.DEFAULT_MODE)&&!h.has(t.modes,t.defaultMode)&&n.push({message:"A MultiMode Lexer cannot be initialized with a "+T.DEFAULT_MODE+": <"+t.defaultMode+`>which does not exist +`,type:F.LexerDefinitionErrorType.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST}),h.has(t,T.MODES)&&h.forEach(t.modes,function(i,a){h.forEach(i,function(o,s){h.isUndefined(o)&&n.push({message:"A Lexer cannot be initialized using an undefined Token Type. Mode:"+("<"+a+"> at index: <"+s+`> +`),type:F.LexerDefinitionErrorType.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED})})}),n}T.performRuntimeChecks=io;function ao(t,e,r){var n=[],i=!1,a=h.compact(h.flatten(h.mapValues(t.modes,function(c){return c}))),o=h.reject(a,function(c){return c[Ae]===F.Lexer.NA}),s=Tn(r);return e&&h.forEach(o,function(c){var f=En(c,s);if(f!==!1){var p=Sn(c,f),l={message:p,type:f.issue,tokenType:c};n.push(l)}else h.has(c,"LINE_BREAKS")?c.LINE_BREAKS===!0&&(i=!0):Ye.canMatchCharCode(s,c.PATTERN)&&(i=!0)}),e&&!i&&n.push({message:`Warning: No LINE_BREAKS Found. + This Lexer has been defined to track line and column information, + But none of the Token Types can be identified as matching a line terminator. + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#LINE_BREAKS + for details.`,type:F.LexerDefinitionErrorType.NO_LINE_BREAKS_FLAGS}),n}T.performWarningRuntimeChecks=ao;function oo(t){var e={},r=h.keys(t);return h.forEach(r,function(n){var i=t[n];if(h.isArray(i))e[n]=[];else throw Error("non exhaustive match")}),e}T.cloneEmptyGroups=oo;function Ar(t){var e=t.PATTERN;if(h.isRegExp(e))return!1;if(h.isFunction(e))return!0;if(h.has(e,"exec"))return!0;if(h.isString(e))return!1;throw Error("non exhaustive match")}T.isCustomPattern=Ar;function mn(t){return h.isString(t)&&t.length===1?t.charCodeAt(0):!1}T.isShortPattern=mn;T.LineTerminatorOptimizedTester={test:function(t){for(var e=t.length,r=this.lastIndex;r Token Type +`)+(" Root cause: "+e.errMsg+`. +`)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#IDENTIFY_TERMINATOR";if(e.issue===F.LexerDefinitionErrorType.CUSTOM_LINE_BREAK)return`Warning: A Custom Token Pattern should specify the option. +`+(" The problem is in the <"+t.name+`> Token Type +`)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_LINE_BREAK";throw Error("non exhaustive match")}T.buildLineBreakIssueMessage=Sn;function Tn(t){var e=h.map(t,function(r){return h.isString(r)&&r.length>0?r.charCodeAt(0):r});return e}function Rr(t,e,r){t[e]===void 0?t[e]=[r]:t[e].push(r)}T.minOptimizationVal=256;var Ft=[];function Or(t){return t255?255+~~(t/255):t}}});var Xe=R(N=>{"use strict";Object.defineProperty(N,"__esModule",{value:!0});N.isTokenType=N.hasExtendingTokensTypesMapProperty=N.hasExtendingTokensTypesProperty=N.hasCategoriesProperty=N.hasShortKeyProperty=N.singleAssignCategoriesToksMap=N.assignCategoriesMapProp=N.assignCategoriesTokensProp=N.assignTokenDefaultProps=N.expandCategories=N.augmentTokenTypes=N.tokenIdxToClass=N.tokenShortNameIdx=N.tokenStructuredMatcherNoCategories=N.tokenStructuredMatcher=void 0;var V=k();function so(t,e){var r=t.tokenTypeIdx;return r===e.tokenTypeIdx?!0:e.isParent===!0&&e.categoryMatchesMap[r]===!0}N.tokenStructuredMatcher=so;function uo(t,e){return t.tokenTypeIdx===e.tokenTypeIdx}N.tokenStructuredMatcherNoCategories=uo;N.tokenShortNameIdx=1;N.tokenIdxToClass={};function co(t){var e=xn(t);Cn(e),Mn(e),Ln(e),V.forEach(e,function(r){r.isParent=r.categoryMatches.length>0})}N.augmentTokenTypes=co;function xn(t){for(var e=V.cloneArr(t),r=t,n=!0;n;){r=V.compact(V.flatten(V.map(r,function(a){return a.CATEGORIES})));var i=V.difference(r,e);e=e.concat(i),V.isEmpty(i)?n=!1:r=i}return e}N.expandCategories=xn;function Cn(t){V.forEach(t,function(e){bn(e)||(N.tokenIdxToClass[N.tokenShortNameIdx]=e,e.tokenTypeIdx=N.tokenShortNameIdx++),Nr(e)&&!V.isArray(e.CATEGORIES)&&(e.CATEGORIES=[e.CATEGORIES]),Nr(e)||(e.CATEGORIES=[]),Fn(e)||(e.categoryMatches=[]),wn(e)||(e.categoryMatchesMap={})})}N.assignTokenDefaultProps=Cn;function Ln(t){V.forEach(t,function(e){e.categoryMatches=[],V.forEach(e.categoryMatchesMap,function(r,n){e.categoryMatches.push(N.tokenIdxToClass[n].tokenTypeIdx)})})}N.assignCategoriesTokensProp=Ln;function Mn(t){V.forEach(t,function(e){Ir([],e)})}N.assignCategoriesMapProp=Mn;function Ir(t,e){V.forEach(t,function(r){e.categoryMatchesMap[r.tokenTypeIdx]=!0}),V.forEach(e.CATEGORIES,function(r){var n=t.concat(e);V.contains(n,r)||Ir(n,r)})}N.singleAssignCategoriesToksMap=Ir;function bn(t){return V.has(t,"tokenTypeIdx")}N.hasShortKeyProperty=bn;function Nr(t){return V.has(t,"CATEGORIES")}N.hasCategoriesProperty=Nr;function Fn(t){return V.has(t,"categoryMatches")}N.hasExtendingTokensTypesProperty=Fn;function wn(t){return V.has(t,"categoryMatchesMap")}N.hasExtendingTokensTypesMapProperty=wn;function lo(t){return V.has(t,"tokenTypeIdx")}N.isTokenType=lo});var kr=R(wt=>{"use strict";Object.defineProperty(wt,"__esModule",{value:!0});wt.defaultLexerErrorProvider=void 0;wt.defaultLexerErrorProvider={buildUnableToPopLexerModeMessage:function(t){return"Unable to pop Lexer Mode after encountering Token ->"+t.image+"<- The Mode Stack is empty"},buildUnexpectedCharactersMessage:function(t,e,r,n,i){return"unexpected character: ->"+t.charAt(e)+"<- at offset: "+e+","+(" skipped "+r+" characters.")}}});var ft=R(qe=>{"use strict";Object.defineProperty(qe,"__esModule",{value:!0});qe.Lexer=qe.LexerDefinitionErrorType=void 0;var Ee=Tr(),w=k(),fo=Xe(),po=kr(),ho=Lt(),vo;(function(t){t[t.MISSING_PATTERN=0]="MISSING_PATTERN",t[t.INVALID_PATTERN=1]="INVALID_PATTERN",t[t.EOI_ANCHOR_FOUND=2]="EOI_ANCHOR_FOUND",t[t.UNSUPPORTED_FLAGS_FOUND=3]="UNSUPPORTED_FLAGS_FOUND",t[t.DUPLICATE_PATTERNS_FOUND=4]="DUPLICATE_PATTERNS_FOUND",t[t.INVALID_GROUP_TYPE_FOUND=5]="INVALID_GROUP_TYPE_FOUND",t[t.PUSH_MODE_DOES_NOT_EXIST=6]="PUSH_MODE_DOES_NOT_EXIST",t[t.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE=7]="MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE",t[t.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY=8]="MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY",t[t.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST=9]="MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST",t[t.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED=10]="LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED",t[t.SOI_ANCHOR_FOUND=11]="SOI_ANCHOR_FOUND",t[t.EMPTY_MATCH_PATTERN=12]="EMPTY_MATCH_PATTERN",t[t.NO_LINE_BREAKS_FLAGS=13]="NO_LINE_BREAKS_FLAGS",t[t.UNREACHABLE_PATTERN=14]="UNREACHABLE_PATTERN",t[t.IDENTIFY_TERMINATOR=15]="IDENTIFY_TERMINATOR",t[t.CUSTOM_LINE_BREAK=16]="CUSTOM_LINE_BREAK"})(vo=qe.LexerDefinitionErrorType||(qe.LexerDefinitionErrorType={}));var pt={deferDefinitionErrorsHandling:!1,positionTracking:"full",lineTerminatorsPattern:/\n|\r\n?/g,lineTerminatorCharacters:[` +`,"\r"],ensureOptimizations:!1,safeMode:!1,errorMessageProvider:po.defaultLexerErrorProvider,traceInitPerf:!1,skipValidations:!1};Object.freeze(pt);var mo=function(){function t(e,r){var n=this;if(r===void 0&&(r=pt),this.lexerDefinition=e,this.lexerDefinitionErrors=[],this.lexerDefinitionWarning=[],this.patternIdxToConfig={},this.charCodeToPatternIdxToConfig={},this.modes=[],this.emptyGroups={},this.config=void 0,this.trackStartLines=!0,this.trackEndLines=!0,this.hasCustom=!1,this.canModeBeOptimized={},typeof r=="boolean")throw Error(`The second argument to the Lexer constructor is now an ILexerConfig Object. +a boolean 2nd argument is no longer supported`);this.config=w.merge(pt,r);var i=this.config.traceInitPerf;i===!0?(this.traceInitMaxIdent=Infinity,this.traceInitPerf=!0):typeof i=="number"&&(this.traceInitMaxIdent=i,this.traceInitPerf=!0),this.traceInitIndent=-1,this.TRACE_INIT("Lexer Constructor",function(){var a,o=!0;n.TRACE_INIT("Lexer Config handling",function(){if(n.config.lineTerminatorsPattern===pt.lineTerminatorsPattern)n.config.lineTerminatorsPattern=Ee.LineTerminatorOptimizedTester;else if(n.config.lineTerminatorCharacters===pt.lineTerminatorCharacters)throw Error(`Error: Missing property on the Lexer config. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#MISSING_LINE_TERM_CHARS`);if(r.safeMode&&r.ensureOptimizations)throw Error('"safeMode" and "ensureOptimizations" flags are mutually exclusive.');n.trackStartLines=/full|onlyStart/i.test(n.config.positionTracking),n.trackEndLines=/full/i.test(n.config.positionTracking),w.isArray(e)?(a={modes:{}},a.modes[Ee.DEFAULT_MODE]=w.cloneArr(e),a[Ee.DEFAULT_MODE]=Ee.DEFAULT_MODE):(o=!1,a=w.cloneObj(e))}),n.config.skipValidations===!1&&(n.TRACE_INIT("performRuntimeChecks",function(){n.lexerDefinitionErrors=n.lexerDefinitionErrors.concat(Ee.performRuntimeChecks(a,n.trackStartLines,n.config.lineTerminatorCharacters))}),n.TRACE_INIT("performWarningRuntimeChecks",function(){n.lexerDefinitionWarning=n.lexerDefinitionWarning.concat(Ee.performWarningRuntimeChecks(a,n.trackStartLines,n.config.lineTerminatorCharacters))})),a.modes=a.modes?a.modes:{},w.forEach(a.modes,function(p,l){a.modes[l]=w.reject(p,function(m){return w.isUndefined(m)})});var s=w.keys(a.modes);if(w.forEach(a.modes,function(p,l){n.TRACE_INIT("Mode: <"+l+"> processing",function(){if(n.modes.push(l),n.config.skipValidations===!1&&n.TRACE_INIT("validatePatterns",function(){n.lexerDefinitionErrors=n.lexerDefinitionErrors.concat(Ee.validatePatterns(p,s))}),w.isEmpty(n.lexerDefinitionErrors)){fo.augmentTokenTypes(p);var m;n.TRACE_INIT("analyzeTokenTypes",function(){m=Ee.analyzeTokenTypes(p,{lineTerminatorCharacters:n.config.lineTerminatorCharacters,positionTracking:r.positionTracking,ensureOptimizations:r.ensureOptimizations,safeMode:r.safeMode,tracer:n.TRACE_INIT.bind(n)})}),n.patternIdxToConfig[l]=m.patternIdxToConfig,n.charCodeToPatternIdxToConfig[l]=m.charCodeToPatternIdxToConfig,n.emptyGroups=w.merge(n.emptyGroups,m.emptyGroups),n.hasCustom=m.hasCustom||n.hasCustom,n.canModeBeOptimized[l]=m.canBeOptimized}})}),n.defaultMode=a.defaultMode,!w.isEmpty(n.lexerDefinitionErrors)&&!n.config.deferDefinitionErrorsHandling){var c=w.map(n.lexerDefinitionErrors,function(p){return p.message}),f=c.join(`----------------------- +`);throw new Error(`Errors detected in definition of Lexer: +`+f)}w.forEach(n.lexerDefinitionWarning,function(p){w.PRINT_WARNING(p.message)}),n.TRACE_INIT("Choosing sub-methods implementations",function(){if(Ee.SUPPORT_STICKY?(n.chopInput=w.IDENTITY,n.match=n.matchWithTest):(n.updateLastIndex=w.NOOP,n.match=n.matchWithExec),o&&(n.handleModes=w.NOOP),n.trackStartLines===!1&&(n.computeNewColumn=w.IDENTITY),n.trackEndLines===!1&&(n.updateTokenEndLineColumnLocation=w.NOOP),/full/i.test(n.config.positionTracking))n.createTokenInstance=n.createFullToken;else if(/onlyStart/i.test(n.config.positionTracking))n.createTokenInstance=n.createStartOnlyToken;else if(/onlyOffset/i.test(n.config.positionTracking))n.createTokenInstance=n.createOffsetOnlyToken;else throw Error('Invalid config option: "'+n.config.positionTracking+'"');n.hasCustom?(n.addToken=n.addTokenUsingPush,n.handlePayload=n.handlePayloadWithCustom):(n.addToken=n.addTokenUsingMemberAccess,n.handlePayload=n.handlePayloadNoCustom)}),n.TRACE_INIT("Failed Optimization Warnings",function(){var p=w.reduce(n.canModeBeOptimized,function(l,m,v){return m===!1&&l.push(v),l},[]);if(r.ensureOptimizations&&!w.isEmpty(p))throw Error("Lexer Modes: < "+p.join(", ")+` > cannot be optimized. + Disable the "ensureOptimizations" lexer config flag to silently ignore this and run the lexer in an un-optimized mode. + Or inspect the console log for details on how to resolve these issues.`)}),n.TRACE_INIT("clearRegExpParserCache",function(){ho.clearRegExpParserCache()}),n.TRACE_INIT("toFastProperties",function(){w.toFastProperties(n)})})}return t.prototype.tokenize=function(e,r){if(r===void 0&&(r=this.defaultMode),!w.isEmpty(this.lexerDefinitionErrors)){var n=w.map(this.lexerDefinitionErrors,function(o){return o.message}),i=n.join(`----------------------- +`);throw new Error(`Unable to Tokenize because Errors detected in definition of Lexer: +`+i)}var a=this.tokenizeInternal(e,r);return a},t.prototype.tokenizeInternal=function(e,r){var n=this,i,a,o,s,c,f,p,l,m,v,u,d,A,_,g,y=e,b=y.length,L=0,se=0,fe=this.hasCustom?0:Math.floor(e.length/10),Z=new Array(fe),ue=[],Q=this.trackStartLines?1:void 0,te=this.trackStartLines?1:void 0,xe=Ee.cloneEmptyGroups(this.emptyGroups),it=this.trackStartLines,at=this.config.lineTerminatorsPattern,Ke=0,we=[],ot=[],It=[],Qr=[];Object.freeze(Qr);var st=void 0;function Jr(){return we}function en(J){var lt=Ee.charCodeToOptimizedIndex(J),ze=ot[lt];return ze===void 0?Qr:ze}var wa=function(J){if(It.length===1&&J.tokenType.PUSH_MODE===void 0){var lt=n.config.errorMessageProvider.buildUnableToPopLexerModeMessage(J);ue.push({offset:J.startOffset,line:J.startLine!==void 0?J.startLine:void 0,column:J.startColumn!==void 0?J.startColumn:void 0,length:J.image.length,message:lt})}else{It.pop();var ze=w.last(It);we=n.patternIdxToConfig[ze],ot=n.charCodeToPatternIdxToConfig[ze],Ke=we.length;var Ua=n.canModeBeOptimized[ze]&&n.config.safeMode===!1;ot&&Ua?st=en:st=Jr}};function tn(J){It.push(J),ot=this.charCodeToPatternIdxToConfig[J],we=this.patternIdxToConfig[J],Ke=we.length,Ke=we.length;var lt=this.canModeBeOptimized[J]&&this.config.safeMode===!1;ot&<?st=en:st=Jr}tn.call(this,r);for(var me;Lc.length&&(c=o,f=p,me=fr)}break}}if(c!==null){if(l=c.length,m=me.group,m!==void 0&&(v=me.tokenTypeIdx,u=this.createTokenInstance(c,L,v,me.tokenType,Q,te,l),this.handlePayload(u,f),m===!1?se=this.addToken(Z,se,u):xe[m].push(u)),e=this.chopInput(e,l),L=L+l,te=this.computeNewColumn(te,l),it===!0&&me.canLineTerminator===!0){var kt=0,hr=void 0,dr=void 0;at.lastIndex=0;do hr=at.test(c),hr===!0&&(dr=at.lastIndex-1,kt++);while(hr===!0);kt!==0&&(Q=Q+kt,te=l-dr,this.updateTokenEndLineColumnLocation(u,m,dr,kt,Q,te,l))}this.handleModes(me,wa,tn,u)}else{for(var vr=L,an=Q,on=te,ct=!1;!ct&&L <"+e+">");var i=w.timer(r),a=i.time,o=i.value,s=a>10?console.warn:console.log;return this.traceInitIndent time: "+a+"ms"),this.traceInitIndent--,o}else return r()},t.SKIPPED="This marks a skipped Token pattern, this means each token identified by it willbe consumed and then thrown into oblivion, this can be used to for example to completely ignore whitespace.",t.NA=/NOT_APPLICABLE/,t}();qe.Lexer=mo});var Ue=R(H=>{"use strict";Object.defineProperty(H,"__esModule",{value:!0});H.tokenMatcher=H.createTokenInstance=H.EOF=H.createToken=H.hasTokenLabel=H.tokenName=H.tokenLabel=void 0;var Te=k(),Eo=ft(),Pr=Xe();function To(t){return Dn(t)?t.LABEL:t.name}H.tokenLabel=To;function yo(t){return t.name}H.tokenName=yo;function Dn(t){return Te.isString(t.LABEL)&&t.LABEL!==""}H.hasTokenLabel=Dn;var _o="parent",Un="categories",Gn="label",Wn="group",Bn="push_mode",qn="pop_mode",jn="longer_alt",Vn="line_breaks",Kn="start_chars_hint";function zn(t){return go(t)}H.createToken=zn;function go(t){var e=t.pattern,r={};if(r.name=t.name,Te.isUndefined(e)||(r.PATTERN=e),Te.has(t,_o))throw`The parent property is no longer supported. +See: https://github.com/chevrotain/chevrotain/issues/564#issuecomment-349062346 for details.`;return Te.has(t,Un)&&(r.CATEGORIES=t[Un]),Pr.augmentTokenTypes([r]),Te.has(t,Gn)&&(r.LABEL=t[Gn]),Te.has(t,Wn)&&(r.GROUP=t[Wn]),Te.has(t,qn)&&(r.POP_MODE=t[qn]),Te.has(t,Bn)&&(r.PUSH_MODE=t[Bn]),Te.has(t,jn)&&(r.LONGER_ALT=t[jn]),Te.has(t,Vn)&&(r.LINE_BREAKS=t[Vn]),Te.has(t,Kn)&&(r.START_CHARS_HINT=t[Kn]),r}H.EOF=zn({name:"EOF",pattern:Eo.Lexer.NA});Pr.augmentTokenTypes([H.EOF]);function Ao(t,e,r,n,i,a,o,s){return{image:e,startOffset:r,endOffset:n,startLine:i,endLine:a,startColumn:o,endColumn:s,tokenTypeIdx:t.tokenTypeIdx,tokenType:t}}H.createTokenInstance=Ao;function Ro(t,e){return Pr.tokenStructuredMatcher(t,e)}H.tokenMatcher=Ro});var ne=R(S=>{"use strict";var Le=S&&S.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(S,"__esModule",{value:!0});S.serializeProduction=S.serializeGrammar=S.Terminal=S.Alternation=S.RepetitionWithSeparator=S.Repetition=S.RepetitionMandatoryWithSeparator=S.RepetitionMandatory=S.Option=S.Alternative=S.Rule=S.NonTerminal=S.AbstractProduction=void 0;var G=k(),Oo=Ue(),Re=function(){function t(e){this._definition=e}return Object.defineProperty(t.prototype,"definition",{get:function(){return this._definition},set:function(e){this._definition=e},enumerable:!1,configurable:!0}),t.prototype.accept=function(e){e.visit(this),G.forEach(this.definition,function(r){r.accept(e)})},t}();S.AbstractProduction=Re;var Hn=function(t){Le(e,t);function e(r){var n=t.call(this,[])||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return Object.defineProperty(e.prototype,"definition",{get:function(){return this.referencedRule!==void 0?this.referencedRule.definition:[]},set:function(r){},enumerable:!1,configurable:!0}),e.prototype.accept=function(r){r.visit(this)},e}(Re);S.NonTerminal=Hn;var Yn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.orgText="",G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Rule=Yn;var Xn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.ignoreAmbiguities=!1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Alternative=Xn;var $n=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Option=$n;var Zn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.RepetitionMandatory=Zn;var Qn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.RepetitionMandatoryWithSeparator=Qn;var Jn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Repetition=Jn;var ei=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.RepetitionWithSeparator=ei;var ti=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,n.ignoreAmbiguities=!1,n.hasPredicates=!1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return Object.defineProperty(e.prototype,"definition",{get:function(){return this._definition},set:function(r){this._definition=r},enumerable:!1,configurable:!0}),e}(Re);S.Alternation=ti;var Dt=function(){function t(e){this.idx=1,G.assign(this,G.pick(e,function(r){return r!==void 0}))}return t.prototype.accept=function(e){e.visit(this)},t}();S.Terminal=Dt;function No(t){return G.map(t,ht)}S.serializeGrammar=No;function ht(t){function e(i){return G.map(i,ht)}if(t instanceof Hn)return{type:"NonTerminal",name:t.nonTerminalName,idx:t.idx};if(t instanceof Xn)return{type:"Alternative",definition:e(t.definition)};if(t instanceof $n)return{type:"Option",idx:t.idx,definition:e(t.definition)};if(t instanceof Zn)return{type:"RepetitionMandatory",idx:t.idx,definition:e(t.definition)};if(t instanceof Qn)return{type:"RepetitionMandatoryWithSeparator",idx:t.idx,separator:ht(new Dt({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof ei)return{type:"RepetitionWithSeparator",idx:t.idx,separator:ht(new Dt({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof Jn)return{type:"Repetition",idx:t.idx,definition:e(t.definition)};if(t instanceof ti)return{type:"Alternation",idx:t.idx,definition:e(t.definition)};if(t instanceof Dt){var r={type:"Terminal",name:t.terminalType.name,label:Oo.tokenLabel(t.terminalType),idx:t.idx},n=t.terminalType.PATTERN;return t.terminalType.PATTERN&&(r.pattern=G.isRegExp(n)?n.source:n),r}else{if(t instanceof Yn)return{type:"Rule",name:t.name,orgText:t.orgText,definition:e(t.definition)};throw Error("non exhaustive match")}}S.serializeProduction=ht});var Gt=R(Ut=>{"use strict";Object.defineProperty(Ut,"__esModule",{value:!0});Ut.RestWalker=void 0;var Sr=k(),ie=ne(),Io=function(){function t(){}return t.prototype.walk=function(e,r){var n=this;r===void 0&&(r=[]),Sr.forEach(e.definition,function(i,a){var o=Sr.drop(e.definition,a+1);if(i instanceof ie.NonTerminal)n.walkProdRef(i,o,r);else if(i instanceof ie.Terminal)n.walkTerminal(i,o,r);else if(i instanceof ie.Alternative)n.walkFlat(i,o,r);else if(i instanceof ie.Option)n.walkOption(i,o,r);else if(i instanceof ie.RepetitionMandatory)n.walkAtLeastOne(i,o,r);else if(i instanceof ie.RepetitionMandatoryWithSeparator)n.walkAtLeastOneSep(i,o,r);else if(i instanceof ie.RepetitionWithSeparator)n.walkManySep(i,o,r);else if(i instanceof ie.Repetition)n.walkMany(i,o,r);else if(i instanceof ie.Alternation)n.walkOr(i,o,r);else throw Error("non exhaustive match")})},t.prototype.walkTerminal=function(e,r,n){},t.prototype.walkProdRef=function(e,r,n){},t.prototype.walkFlat=function(e,r,n){var i=r.concat(n);this.walk(e,i)},t.prototype.walkOption=function(e,r,n){var i=r.concat(n);this.walk(e,i)},t.prototype.walkAtLeastOne=function(e,r,n){var i=[new ie.Option({definition:e.definition})].concat(r,n);this.walk(e,i)},t.prototype.walkAtLeastOneSep=function(e,r,n){var i=ri(e,r,n);this.walk(e,i)},t.prototype.walkMany=function(e,r,n){var i=[new ie.Option({definition:e.definition})].concat(r,n);this.walk(e,i)},t.prototype.walkManySep=function(e,r,n){var i=ri(e,r,n);this.walk(e,i)},t.prototype.walkOr=function(e,r,n){var i=this,a=r.concat(n);Sr.forEach(e.definition,function(o){var s=new ie.Alternative({definition:[o]});i.walk(s,a)})},t}();Ut.RestWalker=Io;function ri(t,e,r){var n=[new ie.Option({definition:[new ie.Terminal({terminalType:t.separator})].concat(t.definition)})],i=n.concat(e,r);return i}});var $e=R(Wt=>{"use strict";Object.defineProperty(Wt,"__esModule",{value:!0});Wt.GAstVisitor=void 0;var Oe=ne(),ko=function(){function t(){}return t.prototype.visit=function(e){var r=e;switch(r.constructor){case Oe.NonTerminal:return this.visitNonTerminal(r);case Oe.Alternative:return this.visitAlternative(r);case Oe.Option:return this.visitOption(r);case Oe.RepetitionMandatory:return this.visitRepetitionMandatory(r);case Oe.RepetitionMandatoryWithSeparator:return this.visitRepetitionMandatoryWithSeparator(r);case Oe.RepetitionWithSeparator:return this.visitRepetitionWithSeparator(r);case Oe.Repetition:return this.visitRepetition(r);case Oe.Alternation:return this.visitAlternation(r);case Oe.Terminal:return this.visitTerminal(r);case Oe.Rule:return this.visitRule(r);default:throw Error("non exhaustive match")}},t.prototype.visitNonTerminal=function(e){},t.prototype.visitAlternative=function(e){},t.prototype.visitOption=function(e){},t.prototype.visitRepetition=function(e){},t.prototype.visitRepetitionMandatory=function(e){},t.prototype.visitRepetitionMandatoryWithSeparator=function(e){},t.prototype.visitRepetitionWithSeparator=function(e){},t.prototype.visitAlternation=function(e){},t.prototype.visitTerminal=function(e){},t.prototype.visitRule=function(e){},t}();Wt.GAstVisitor=ko});var vt=R(X=>{"use strict";var Po=X&&X.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(X,"__esModule",{value:!0});X.collectMethods=X.DslMethodsCollectorVisitor=X.getProductionDslName=X.isBranchingProd=X.isOptionalProd=X.isSequenceProd=void 0;var dt=k(),W=ne(),So=$e();function xo(t){return t instanceof W.Alternative||t instanceof W.Option||t instanceof W.Repetition||t instanceof W.RepetitionMandatory||t instanceof W.RepetitionMandatoryWithSeparator||t instanceof W.RepetitionWithSeparator||t instanceof W.Terminal||t instanceof W.Rule}X.isSequenceProd=xo;function xr(t,e){e===void 0&&(e=[]);var r=t instanceof W.Option||t instanceof W.Repetition||t instanceof W.RepetitionWithSeparator;return r?!0:t instanceof W.Alternation?dt.some(t.definition,function(n){return xr(n,e)}):t instanceof W.NonTerminal&&dt.contains(e,t)?!1:t instanceof W.AbstractProduction?(t instanceof W.NonTerminal&&e.push(t),dt.every(t.definition,function(n){return xr(n,e)})):!1}X.isOptionalProd=xr;function Co(t){return t instanceof W.Alternation}X.isBranchingProd=Co;function Lo(t){if(t instanceof W.NonTerminal)return"SUBRULE";if(t instanceof W.Option)return"OPTION";if(t instanceof W.Alternation)return"OR";if(t instanceof W.RepetitionMandatory)return"AT_LEAST_ONE";if(t instanceof W.RepetitionMandatoryWithSeparator)return"AT_LEAST_ONE_SEP";if(t instanceof W.RepetitionWithSeparator)return"MANY_SEP";if(t instanceof W.Repetition)return"MANY";if(t instanceof W.Terminal)return"CONSUME";throw Error("non exhaustive match")}X.getProductionDslName=Lo;var ni=function(t){Po(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.separator="-",r.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]},r}return e.prototype.reset=function(){this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}},e.prototype.visitTerminal=function(r){var n=r.terminalType.name+this.separator+"Terminal";dt.has(this.dslMethods,n)||(this.dslMethods[n]=[]),this.dslMethods[n].push(r)},e.prototype.visitNonTerminal=function(r){var n=r.nonTerminalName+this.separator+"Terminal";dt.has(this.dslMethods,n)||(this.dslMethods[n]=[]),this.dslMethods[n].push(r)},e.prototype.visitOption=function(r){this.dslMethods.option.push(r)},e.prototype.visitRepetitionWithSeparator=function(r){this.dslMethods.repetitionWithSeparator.push(r)},e.prototype.visitRepetitionMandatory=function(r){this.dslMethods.repetitionMandatory.push(r)},e.prototype.visitRepetitionMandatoryWithSeparator=function(r){this.dslMethods.repetitionMandatoryWithSeparator.push(r)},e.prototype.visitRepetition=function(r){this.dslMethods.repetition.push(r)},e.prototype.visitAlternation=function(r){this.dslMethods.alternation.push(r)},e}(So.GAstVisitor);X.DslMethodsCollectorVisitor=ni;var Bt=new ni;function Mo(t){Bt.reset(),t.accept(Bt);var e=Bt.dslMethods;return Bt.reset(),e}X.collectMethods=Mo});var Lr=R(Ne=>{"use strict";Object.defineProperty(Ne,"__esModule",{value:!0});Ne.firstForTerminal=Ne.firstForBranching=Ne.firstForSequence=Ne.first=void 0;var qt=k(),ii=ne(),Cr=vt();function jt(t){if(t instanceof ii.NonTerminal)return jt(t.referencedRule);if(t instanceof ii.Terminal)return si(t);if(Cr.isSequenceProd(t))return ai(t);if(Cr.isBranchingProd(t))return oi(t);throw Error("non exhaustive match")}Ne.first=jt;function ai(t){for(var e=[],r=t.definition,n=0,i=r.length>n,a,o=!0;i&&o;)a=r[n],o=Cr.isOptionalProd(a),e=e.concat(jt(a)),n=n+1,i=r.length>n;return qt.uniq(e)}Ne.firstForSequence=ai;function oi(t){var e=qt.map(t.definition,function(r){return jt(r)});return qt.uniq(qt.flatten(e))}Ne.firstForBranching=oi;function si(t){return[t.terminalType]}Ne.firstForTerminal=si});var Mr=R(Vt=>{"use strict";Object.defineProperty(Vt,"__esModule",{value:!0});Vt.IN=void 0;Vt.IN="_~IN~_"});var pi=R(he=>{"use strict";var bo=he&&he.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(he,"__esModule",{value:!0});he.buildInProdFollowPrefix=he.buildBetweenProdsFollowPrefix=he.computeAllProdsFollows=he.ResyncFollowsWalker=void 0;var Fo=Gt(),wo=Lr(),ui=k(),ci=Mr(),Do=ne(),fi=function(t){bo(e,t);function e(r){var n=t.call(this)||this;return n.topProd=r,n.follows={},n}return e.prototype.startWalking=function(){return this.walk(this.topProd),this.follows},e.prototype.walkTerminal=function(r,n,i){},e.prototype.walkProdRef=function(r,n,i){var a=li(r.referencedRule,r.idx)+this.topProd.name,o=n.concat(i),s=new Do.Alternative({definition:o}),c=wo.first(s);this.follows[a]=c},e}(Fo.RestWalker);he.ResyncFollowsWalker=fi;function Uo(t){var e={};return ui.forEach(t,function(r){var n=new fi(r).startWalking();ui.assign(e,n)}),e}he.computeAllProdsFollows=Uo;function li(t,e){return t.name+e+ci.IN}he.buildBetweenProdsFollowPrefix=li;function Go(t){var e=t.terminalType.name;return e+t.idx+ci.IN}he.buildInProdFollowPrefix=Go});var mt=R(Me=>{"use strict";Object.defineProperty(Me,"__esModule",{value:!0});Me.defaultGrammarValidatorErrorProvider=Me.defaultGrammarResolverErrorProvider=Me.defaultParserErrorProvider=void 0;var Ze=Ue(),Wo=k(),ye=k(),br=ne(),hi=vt();Me.defaultParserErrorProvider={buildMismatchTokenMessage:function(t){var e=t.expected,r=t.actual,n=t.previous,i=t.ruleName,a=Ze.hasTokenLabel(e),o=a?"--> "+Ze.tokenLabel(e)+" <--":"token of type --> "+e.name+" <--",s="Expecting "+o+" but found --> '"+r.image+"' <--";return s},buildNotAllInputParsedMessage:function(t){var e=t.firstRedundant,r=t.ruleName;return"Redundant input, expecting EOF but found: "+e.image},buildNoViableAltMessage:function(t){var e=t.expectedPathsPerAlt,r=t.actual,n=t.previous,i=t.customUserDescription,a=t.ruleName,o="Expecting: ",s=ye.first(r).image,c=` +but found: '`+s+"'";if(i)return o+i+c;var f=ye.reduce(e,function(v,u){return v.concat(u)},[]),p=ye.map(f,function(v){return"["+ye.map(v,function(u){return Ze.tokenLabel(u)}).join(", ")+"]"}),l=ye.map(p,function(v,u){return" "+(u+1)+". "+v}),m=`one of these possible Token sequences: +`+l.join(` +`);return o+m+c},buildEarlyExitMessage:function(t){var e=t.expectedIterationPaths,r=t.actual,n=t.customUserDescription,i=t.ruleName,a="Expecting: ",o=ye.first(r).image,s=` +but found: '`+o+"'";if(n)return a+n+s;var c=ye.map(e,function(p){return"["+ye.map(p,function(l){return Ze.tokenLabel(l)}).join(",")+"]"}),f=`expecting at least one iteration which starts with one of these possible Token sequences:: + `+("<"+c.join(" ,")+">");return a+f+s}};Object.freeze(Me.defaultParserErrorProvider);Me.defaultGrammarResolverErrorProvider={buildRuleNotFoundError:function(t,e){var r="Invalid grammar, reference to a rule which is not defined: ->"+e.nonTerminalName+`<- +inside top level rule: ->`+t.name+"<-";return r}};Me.defaultGrammarValidatorErrorProvider={buildDuplicateFoundError:function(t,e){function r(p){return p instanceof br.Terminal?p.terminalType.name:p instanceof br.NonTerminal?p.nonTerminalName:""}var n=t.name,i=ye.first(e),a=i.idx,o=hi.getProductionDslName(i),s=r(i),c=a>0,f="->"+o+(c?a:"")+"<- "+(s?"with argument: ->"+s+"<-":"")+` + appears more than once (`+e.length+" times) in the top level rule: ->"+n+`<-. + For further details see: https://chevrotain.io/docs/FAQ.html#NUMERICAL_SUFFIXES + `;return f=f.replace(/[ \t]+/g," "),f=f.replace(/\s\s+/g,` +`),f},buildNamespaceConflictError:function(t){var e=`Namespace conflict found in grammar. +`+("The grammar has both a Terminal(Token) and a Non-Terminal(Rule) named: <"+t.name+`>. +`)+`To resolve this make sure each Terminal and Non-Terminal names are unique +This is easy to accomplish by using the convention that Terminal names start with an uppercase letter +and Non-Terminal names start with a lower case letter.`;return e},buildAlternationPrefixAmbiguityError:function(t){var e=ye.map(t.prefixPath,function(i){return Ze.tokenLabel(i)}).join(", "),r=t.alternation.idx===0?"":t.alternation.idx,n="Ambiguous alternatives: <"+t.ambiguityIndices.join(" ,")+`> due to common lookahead prefix +`+("in inside <"+t.topLevelRule.name+`> Rule, +`)+("<"+e+`> may appears as a prefix path in all these alternatives. +`)+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#COMMON_PREFIX +For Further details.`;return n},buildAlternationAmbiguityError:function(t){var e=ye.map(t.prefixPath,function(i){return Ze.tokenLabel(i)}).join(", "),r=t.alternation.idx===0?"":t.alternation.idx,n="Ambiguous Alternatives Detected: <"+t.ambiguityIndices.join(" ,")+"> in "+(" inside <"+t.topLevelRule.name+`> Rule, +`)+("<"+e+`> may appears as a prefix path in all these alternatives. +`);return n=n+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES +For Further details.`,n},buildEmptyRepetitionError:function(t){var e=hi.getProductionDslName(t.repetition);t.repetition.idx!==0&&(e+=t.repetition.idx);var r="The repetition <"+e+"> within Rule <"+t.topLevelRule.name+`> can never consume any tokens. +This could lead to an infinite loop.`;return r},buildTokenNameError:function(t){return"deprecated"},buildEmptyAlternationError:function(t){var e="Ambiguous empty alternative: <"+(t.emptyChoiceIdx+1)+">"+(" in inside <"+t.topLevelRule.name+`> Rule. +`)+"Only the last alternative may be an empty alternative.";return e},buildTooManyAlternativesError:function(t){var e=`An Alternation cannot have more than 256 alternatives: +`+(" inside <"+t.topLevelRule.name+`> Rule. + has `+(t.alternation.definition.length+1)+" alternatives.");return e},buildLeftRecursionError:function(t){var e=t.topLevelRule.name,r=Wo.map(t.leftRecursionPath,function(a){return a.name}),n=e+" --> "+r.concat([e]).join(" --> "),i=`Left Recursion found in grammar. +`+("rule: <"+e+`> can be invoked from itself (directly or indirectly) +`)+(`without consuming any Tokens. The grammar path that causes this is: + `+n+` +`)+` To fix this refactor your grammar to remove the left recursion. +see: https://en.wikipedia.org/wiki/LL_parser#Left_Factoring.`;return i},buildInvalidRuleNameError:function(t){return"deprecated"},buildDuplicateRuleNameError:function(t){var e;t.topLevelRule instanceof br.Rule?e=t.topLevelRule.name:e=t.topLevelRule;var r="Duplicate definition, rule: ->"+e+"<- is already defined in the grammar: ->"+t.grammarName+"<-";return r}}});var mi=R(Ge=>{"use strict";var Bo=Ge&&Ge.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(Ge,"__esModule",{value:!0});Ge.GastRefResolverVisitor=Ge.resolveGrammar=void 0;var qo=ce(),di=k(),jo=$e();function Vo(t,e){var r=new vi(t,e);return r.resolveRefs(),r.errors}Ge.resolveGrammar=Vo;var vi=function(t){Bo(e,t);function e(r,n){var i=t.call(this)||this;return i.nameToTopRule=r,i.errMsgProvider=n,i.errors=[],i}return e.prototype.resolveRefs=function(){var r=this;di.forEach(di.values(this.nameToTopRule),function(n){r.currTopLevel=n,n.accept(r)})},e.prototype.visitNonTerminal=function(r){var n=this.nameToTopRule[r.nonTerminalName];if(n)r.referencedRule=n;else{var i=this.errMsgProvider.buildRuleNotFoundError(this.currTopLevel,r);this.errors.push({message:i,type:qo.ParserDefinitionErrorType.UNRESOLVED_SUBRULE_REF,ruleName:this.currTopLevel.name,unresolvedRefName:r.nonTerminalName})}},e}(jo.GAstVisitor);Ge.GastRefResolverVisitor=vi});var Tt=R(j=>{"use strict";var je=j&&j.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(j,"__esModule",{value:!0});j.nextPossibleTokensAfter=j.possiblePathsFrom=j.NextTerminalAfterAtLeastOneSepWalker=j.NextTerminalAfterAtLeastOneWalker=j.NextTerminalAfterManySepWalker=j.NextTerminalAfterManyWalker=j.AbstractNextTerminalAfterProductionWalker=j.NextAfterTokenWalker=j.AbstractNextPossibleTokensWalker=void 0;var Ei=Gt(),I=k(),Ko=Lr(),O=ne(),Ti=function(t){je(e,t);function e(r,n){var i=t.call(this)||this;return i.topProd=r,i.path=n,i.possibleTokTypes=[],i.nextProductionName="",i.nextProductionOccurrence=0,i.found=!1,i.isAtEndOfPath=!1,i}return e.prototype.startWalking=function(){if(this.found=!1,this.path.ruleStack[0]!==this.topProd.name)throw Error("The path does not start with the walker's top Rule!");return this.ruleStack=I.cloneArr(this.path.ruleStack).reverse(),this.occurrenceStack=I.cloneArr(this.path.occurrenceStack).reverse(),this.ruleStack.pop(),this.occurrenceStack.pop(),this.updateExpectedNext(),this.walk(this.topProd),this.possibleTokTypes},e.prototype.walk=function(r,n){n===void 0&&(n=[]),this.found||t.prototype.walk.call(this,r,n)},e.prototype.walkProdRef=function(r,n,i){if(r.referencedRule.name===this.nextProductionName&&r.idx===this.nextProductionOccurrence){var a=n.concat(i);this.updateExpectedNext(),this.walk(r.referencedRule,a)}},e.prototype.updateExpectedNext=function(){I.isEmpty(this.ruleStack)?(this.nextProductionName="",this.nextProductionOccurrence=0,this.isAtEndOfPath=!0):(this.nextProductionName=this.ruleStack.pop(),this.nextProductionOccurrence=this.occurrenceStack.pop())},e}(Ei.RestWalker);j.AbstractNextPossibleTokensWalker=Ti;var zo=function(t){je(e,t);function e(r,n){var i=t.call(this,r,n)||this;return i.path=n,i.nextTerminalName="",i.nextTerminalOccurrence=0,i.nextTerminalName=i.path.lastTok.name,i.nextTerminalOccurrence=i.path.lastTokOccurrence,i}return e.prototype.walkTerminal=function(r,n,i){if(this.isAtEndOfPath&&r.terminalType.name===this.nextTerminalName&&r.idx===this.nextTerminalOccurrence&&!this.found){var a=n.concat(i),o=new O.Alternative({definition:a});this.possibleTokTypes=Ko.first(o),this.found=!0}},e}(Ti);j.NextAfterTokenWalker=zo;var Et=function(t){je(e,t);function e(r,n){var i=t.call(this)||this;return i.topRule=r,i.occurrence=n,i.result={token:void 0,occurrence:void 0,isEndOfRule:void 0},i}return e.prototype.startWalking=function(){return this.walk(this.topRule),this.result},e}(Ei.RestWalker);j.AbstractNextTerminalAfterProductionWalker=Et;var Ho=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkMany=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkMany.call(this,r,n,i)},e}(Et);j.NextTerminalAfterManyWalker=Ho;var Yo=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkManySep=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkManySep.call(this,r,n,i)},e}(Et);j.NextTerminalAfterManySepWalker=Yo;var Xo=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkAtLeastOne=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkAtLeastOne.call(this,r,n,i)},e}(Et);j.NextTerminalAfterAtLeastOneWalker=Xo;var $o=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkAtLeastOneSep=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkAtLeastOneSep.call(this,r,n,i)},e}(Et);j.NextTerminalAfterAtLeastOneSepWalker=$o;function yi(t,e,r){r===void 0&&(r=[]),r=I.cloneArr(r);var n=[],i=0;function a(f){return f.concat(I.drop(t,i+1))}function o(f){var p=yi(a(f),e,r);return n.concat(p)}for(;r.length=0;it--){var at=_.definition[it],Ke={idx:u,def:at.definition.concat(I.drop(v)),ruleStack:d,occurrenceStack:A};l.push(Ke),l.push(o)}else if(_ instanceof O.Alternative)l.push({idx:u,def:_.definition.concat(I.drop(v)),ruleStack:d,occurrenceStack:A});else if(_ instanceof O.Rule)l.push(Zo(_,u,d,A));else throw Error("non exhaustive match")}}return p}j.nextPossibleTokensAfter=Qo;function Zo(t,e,r,n){var i=I.cloneArr(r);i.push(t.name);var a=I.cloneArr(n);return a.push(1),{idx:e,def:t.definition,ruleStack:i,occurrenceStack:a}}});var yt=R(C=>{"use strict";var _i=C&&C.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(C,"__esModule",{value:!0});C.areTokenCategoriesNotUsed=C.isStrictPrefixOfPath=C.containsPath=C.getLookaheadPathsForOptionalProd=C.getLookaheadPathsForOr=C.lookAheadSequenceFromAlternatives=C.buildSingleAlternativeLookaheadFunction=C.buildAlternativesLookAheadFunc=C.buildLookaheadFuncForOptionalProd=C.buildLookaheadFuncForOr=C.getProdType=C.PROD_TYPE=void 0;var D=k(),gi=Tt(),Jo=Gt(),Kt=Xe(),We=ne(),es=$e(),z;(function(t){t[t.OPTION=0]="OPTION",t[t.REPETITION=1]="REPETITION",t[t.REPETITION_MANDATORY=2]="REPETITION_MANDATORY",t[t.REPETITION_MANDATORY_WITH_SEPARATOR=3]="REPETITION_MANDATORY_WITH_SEPARATOR",t[t.REPETITION_WITH_SEPARATOR=4]="REPETITION_WITH_SEPARATOR",t[t.ALTERNATION=5]="ALTERNATION"})(z=C.PROD_TYPE||(C.PROD_TYPE={}));function ts(t){if(t instanceof We.Option)return z.OPTION;if(t instanceof We.Repetition)return z.REPETITION;if(t instanceof We.RepetitionMandatory)return z.REPETITION_MANDATORY;if(t instanceof We.RepetitionMandatoryWithSeparator)return z.REPETITION_MANDATORY_WITH_SEPARATOR;if(t instanceof We.RepetitionWithSeparator)return z.REPETITION_WITH_SEPARATOR;if(t instanceof We.Alternation)return z.ALTERNATION;throw Error("non exhaustive match")}C.getProdType=ts;function rs(t,e,r,n,i,a){var o=Ai(t,e,r),s=Fr(o)?Kt.tokenStructuredMatcherNoCategories:Kt.tokenStructuredMatcher;return a(o,n,s,i)}C.buildLookaheadFuncForOr=rs;function ns(t,e,r,n,i,a){var o=Ri(t,e,i,r),s=Fr(o)?Kt.tokenStructuredMatcherNoCategories:Kt.tokenStructuredMatcher;return a(o[0],s,n)}C.buildLookaheadFuncForOptionalProd=ns;function is(t,e,r,n){var i=t.length,a=D.every(t,function(c){return D.every(c,function(f){return f.length===1})});if(e)return function(c){for(var f=D.map(c,function(y){return y.GATE}),p=0;p{"use strict";var Ur=x&&x.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(x,"__esModule",{value:!0});x.checkPrefixAlternativesAmbiguities=x.validateSomeNonEmptyLookaheadPath=x.validateTooManyAlts=x.RepetionCollector=x.validateAmbiguousAlternationAlternatives=x.validateEmptyOrAlternative=x.getFirstNoneTerminal=x.validateNoLeftRecursion=x.validateRuleIsOverridden=x.validateRuleDoesNotAlreadyExist=x.OccurrenceValidationCollector=x.identifyProductionForDuplicates=x.validateGrammar=void 0;var M=k(),B=k(),Ie=ce(),Gr=vt(),Qe=yt(),cs=Tt(),_e=ne(),Wr=$e();function ps(t,e,r,n,i){var a=M.map(t,function(v){return ls(v,n)}),o=M.map(t,function(v){return Br(v,v,n)}),s=[],c=[],f=[];B.every(o,B.isEmpty)&&(s=B.map(t,function(v){return Pi(v,n)}),c=B.map(t,function(v){return Si(v,e,n)}),f=Ci(t,e,n));var p=fs(t,r,n),l=B.map(t,function(v){return xi(v,n)}),m=B.map(t,function(v){return ki(v,t,i,n)});return M.flatten(a.concat(f,o,s,c,p,l,m))}x.validateGrammar=ps;function ls(t,e){var r=new bi;t.accept(r);var n=r.allProductions,i=M.groupBy(n,Li),a=M.pick(i,function(s){return s.length>1}),o=M.map(M.values(a),function(s){var c=M.first(s),f=e.buildDuplicateFoundError(t,s),p=Gr.getProductionDslName(c),l={message:f,type:Ie.ParserDefinitionErrorType.DUPLICATE_PRODUCTIONS,ruleName:t.name,dslName:p,occurrence:c.idx},m=Mi(c);return m&&(l.parameter=m),l});return o}function Li(t){return Gr.getProductionDslName(t)+"_#_"+t.idx+"_#_"+Mi(t)}x.identifyProductionForDuplicates=Li;function Mi(t){return t instanceof _e.Terminal?t.terminalType.name:t instanceof _e.NonTerminal?t.nonTerminalName:""}var bi=function(t){Ur(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.allProductions=[],r}return e.prototype.visitNonTerminal=function(r){this.allProductions.push(r)},e.prototype.visitOption=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatory=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatoryWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetition=function(r){this.allProductions.push(r)},e.prototype.visitAlternation=function(r){this.allProductions.push(r)},e.prototype.visitTerminal=function(r){this.allProductions.push(r)},e}(Wr.GAstVisitor);x.OccurrenceValidationCollector=bi;function ki(t,e,r,n){var i=[],a=B.reduce(e,function(s,c){return c.name===t.name?s+1:s},0);if(a>1){var o=n.buildDuplicateRuleNameError({topLevelRule:t,grammarName:r});i.push({message:o,type:Ie.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:t.name})}return i}x.validateRuleDoesNotAlreadyExist=ki;function hs(t,e,r){var n=[],i;return M.contains(e,t)||(i="Invalid rule override, rule: ->"+t+"<- cannot be overridden in the grammar: ->"+r+"<-as it is not defined in any of the super grammars ",n.push({message:i,type:Ie.ParserDefinitionErrorType.INVALID_RULE_OVERRIDE,ruleName:t})),n}x.validateRuleIsOverridden=hs;function Br(t,e,r,n){n===void 0&&(n=[]);var i=[],a=_t(e.definition);if(M.isEmpty(a))return[];var o=t.name,s=M.contains(a,t);s&&i.push({message:r.buildLeftRecursionError({topLevelRule:t,leftRecursionPath:n}),type:Ie.ParserDefinitionErrorType.LEFT_RECURSION,ruleName:o});var c=M.difference(a,n.concat([t])),f=M.map(c,function(p){var l=M.cloneArr(n);return l.push(p),Br(t,p,r,l)});return i.concat(M.flatten(f))}x.validateNoLeftRecursion=Br;function _t(t){var e=[];if(M.isEmpty(t))return e;var r=M.first(t);if(r instanceof _e.NonTerminal)e.push(r.referencedRule);else if(r instanceof _e.Alternative||r instanceof _e.Option||r instanceof _e.RepetitionMandatory||r instanceof _e.RepetitionMandatoryWithSeparator||r instanceof _e.RepetitionWithSeparator||r instanceof _e.Repetition)e=e.concat(_t(r.definition));else if(r instanceof _e.Alternation)e=M.flatten(M.map(r.definition,function(o){return _t(o.definition)}));else if(!(r instanceof _e.Terminal))throw Error("non exhaustive match");var n=Gr.isOptionalProd(r),i=t.length>1;if(n&&i){var a=M.drop(t);return e.concat(_t(a))}else return e}x.getFirstNoneTerminal=_t;var qr=function(t){Ur(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.alternations=[],r}return e.prototype.visitAlternation=function(r){this.alternations.push(r)},e}(Wr.GAstVisitor);function Pi(t,e){var r=new qr;t.accept(r);var n=r.alternations,i=M.reduce(n,function(a,o){var s=M.dropRight(o.definition),c=M.map(s,function(f,p){var l=cs.nextPossibleTokensAfter([f],[],null,1);return M.isEmpty(l)?{message:e.buildEmptyAlternationError({topLevelRule:t,alternation:o,emptyChoiceIdx:p}),type:Ie.ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,ruleName:t.name,occurrence:o.idx,alternative:p+1}:null});return a.concat(M.compact(c))},[]);return i}x.validateEmptyOrAlternative=Pi;function Si(t,e,r){var n=new qr;t.accept(n);var i=n.alternations;i=B.reject(i,function(o){return o.ignoreAmbiguities===!0});var a=M.reduce(i,function(o,s){var c=s.idx,f=s.maxLookahead||e,p=Qe.getLookaheadPathsForOr(c,t,f,s),l=ds(p,s,t,r),m=Fi(p,s,t,r);return o.concat(l,m)},[]);return a}x.validateAmbiguousAlternationAlternatives=Si;var wi=function(t){Ur(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.allProductions=[],r}return e.prototype.visitRepetitionWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatory=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatoryWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetition=function(r){this.allProductions.push(r)},e}(Wr.GAstVisitor);x.RepetionCollector=wi;function xi(t,e){var r=new qr;t.accept(r);var n=r.alternations,i=M.reduce(n,function(a,o){return o.definition.length>255&&a.push({message:e.buildTooManyAlternativesError({topLevelRule:t,alternation:o}),type:Ie.ParserDefinitionErrorType.TOO_MANY_ALTS,ruleName:t.name,occurrence:o.idx}),a},[]);return i}x.validateTooManyAlts=xi;function Ci(t,e,r){var n=[];return B.forEach(t,function(i){var a=new wi;i.accept(a);var o=a.allProductions;B.forEach(o,function(s){var c=Qe.getProdType(s),f=s.maxLookahead||e,p=s.idx,l=Qe.getLookaheadPathsForOptionalProd(p,i,c,f),m=l[0];if(B.isEmpty(B.flatten(m))){var v=r.buildEmptyRepetitionError({topLevelRule:i,repetition:s});n.push({message:v,type:Ie.ParserDefinitionErrorType.NO_NON_EMPTY_LOOKAHEAD,ruleName:i.name})}})}),n}x.validateSomeNonEmptyLookaheadPath=Ci;function ds(t,e,r,n){var i=[],a=B.reduce(t,function(s,c,f){return e.definition[f].ignoreAmbiguities===!0||B.forEach(c,function(p){var l=[f];B.forEach(t,function(m,v){f!==v&&Qe.containsPath(m,p)&&e.definition[v].ignoreAmbiguities!==!0&&l.push(v)}),l.length>1&&!Qe.containsPath(i,p)&&(i.push(p),s.push({alts:l,path:p}))}),s},[]),o=M.map(a,function(s){var c=B.map(s.alts,function(p){return p+1}),f=n.buildAlternationAmbiguityError({topLevelRule:r,alternation:e,ambiguityIndices:c,prefixPath:s.path});return{message:f,type:Ie.ParserDefinitionErrorType.AMBIGUOUS_ALTS,ruleName:r.name,occurrence:e.idx,alternatives:[s.alts]}});return o}function Fi(t,e,r,n){var i=[],a=B.reduce(t,function(o,s,c){var f=B.map(s,function(p){return{idx:c,path:p}});return o.concat(f)},[]);return B.forEach(a,function(o){var s=e.definition[o.idx];if(s.ignoreAmbiguities!==!0){var c=o.idx,f=o.path,p=B.findAll(a,function(m){return e.definition[m.idx].ignoreAmbiguities!==!0&&m.idx{"use strict";Object.defineProperty(Je,"__esModule",{value:!0});Je.validateGrammar=Je.resolveGrammar=void 0;var Vr=k(),vs=mi(),ms=jr(),Di=mt();function Es(t){t=Vr.defaults(t,{errMsgProvider:Di.defaultGrammarResolverErrorProvider});var e={};return Vr.forEach(t.rules,function(r){e[r.name]=r}),vs.resolveGrammar(e,t.errMsgProvider)}Je.resolveGrammar=Es;function Ts(t){return t=Vr.defaults(t,{errMsgProvider:Di.defaultGrammarValidatorErrorProvider}),ms.validateGrammar(t.rules,t.maxLookahead,t.tokenTypes,t.errMsgProvider,t.grammarName)}Je.validateGrammar=Ts});var et=R(ae=>{"use strict";var gt=ae&&ae.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(ae,"__esModule",{value:!0});ae.EarlyExitException=ae.NotAllInputParsedException=ae.NoViableAltException=ae.MismatchedTokenException=ae.isRecognitionException=void 0;var ys=k(),Gi="MismatchedTokenException",Wi="NoViableAltException",Bi="EarlyExitException",qi="NotAllInputParsedException",ji=[Gi,Wi,Bi,qi];Object.freeze(ji);function _s(t){return ys.contains(ji,t.name)}ae.isRecognitionException=_s;var zt=function(t){gt(e,t);function e(r,n){var i=this.constructor,a=t.call(this,r)||this;return a.token=n,a.resyncedTokens=[],Object.setPrototypeOf(a,i.prototype),Error.captureStackTrace&&Error.captureStackTrace(a,a.constructor),a}return e}(Error),gs=function(t){gt(e,t);function e(r,n,i){var a=t.call(this,r,n)||this;return a.previousToken=i,a.name=Gi,a}return e}(zt);ae.MismatchedTokenException=gs;var As=function(t){gt(e,t);function e(r,n,i){var a=t.call(this,r,n)||this;return a.previousToken=i,a.name=Wi,a}return e}(zt);ae.NoViableAltException=As;var Rs=function(t){gt(e,t);function e(r,n){var i=t.call(this,r,n)||this;return i.name=qi,i}return e}(zt);ae.NotAllInputParsedException=Rs;var Os=function(t){gt(e,t);function e(r,n,i){var a=t.call(this,r,n)||this;return a.previousToken=i,a.name=Bi,a}return e}(zt);ae.EarlyExitException=Os});var zr=R($=>{"use strict";Object.defineProperty($,"__esModule",{value:!0});$.attemptInRepetitionRecovery=$.Recoverable=$.InRuleRecoveryException=$.IN_RULE_RECOVERY_EXCEPTION=$.EOF_FOLLOW_KEY=void 0;var Ht=Ue(),de=k(),Ns=et(),Is=Mr(),ks=ce();$.EOF_FOLLOW_KEY={};$.IN_RULE_RECOVERY_EXCEPTION="InRuleRecoveryException";function Kr(t){this.name=$.IN_RULE_RECOVERY_EXCEPTION,this.message=t}$.InRuleRecoveryException=Kr;Kr.prototype=Error.prototype;var Ps=function(){function t(){}return t.prototype.initRecoverable=function(e){this.firstAfterRepMap={},this.resyncFollows={},this.recoveryEnabled=de.has(e,"recoveryEnabled")?e.recoveryEnabled:ks.DEFAULT_PARSER_CONFIG.recoveryEnabled,this.recoveryEnabled&&(this.attemptInRepetitionRecovery=Vi)},t.prototype.getTokenToInsert=function(e){var r=Ht.createTokenInstance(e,"",NaN,NaN,NaN,NaN,NaN,NaN);return r.isInsertedInRecovery=!0,r},t.prototype.canTokenTypeBeInsertedInRecovery=function(e){return!0},t.prototype.tryInRepetitionRecovery=function(e,r,n,i){for(var a=this,o=this.findReSyncTokenType(),s=this.exportLexerState(),c=[],f=!1,p=this.LA(1),l=this.LA(1),m=function(){var v=a.LA(0),u=a.errorMessageProvider.buildMismatchTokenMessage({expected:i,actual:p,previous:v,ruleName:a.getCurrRuleFullName()}),d=new Ns.MismatchedTokenException(u,p,a.LA(0));d.resyncedTokens=de.dropRight(c),a.SAVE_ERROR(d)};!f;)if(this.tokenMatcher(l,i)){m();return}else if(n.call(this)){m(),e.apply(this,r);return}else this.tokenMatcher(l,o)?f=!0:(l=this.SKIP_TOKEN(),this.addToResyncTokens(l,c));this.importLexerState(s)},t.prototype.shouldInRepetitionRecoveryBeTried=function(e,r,n){return!(n===!1||e===void 0||r===void 0||this.tokenMatcher(this.LA(1),e)||this.isBackTracking()||this.canPerformInRuleRecovery(e,this.getFollowsForInRuleRecovery(e,r)))},t.prototype.getFollowsForInRuleRecovery=function(e,r){var n=this.getCurrentGrammarPath(e,r),i=this.getNextPossibleTokenTypes(n);return i},t.prototype.tryInRuleRecovery=function(e,r){if(this.canRecoverWithSingleTokenInsertion(e,r)){var n=this.getTokenToInsert(e);return n}if(this.canRecoverWithSingleTokenDeletion(e)){var i=this.SKIP_TOKEN();return this.consumeToken(),i}throw new Kr("sad sad panda")},t.prototype.canPerformInRuleRecovery=function(e,r){return this.canRecoverWithSingleTokenInsertion(e,r)||this.canRecoverWithSingleTokenDeletion(e)},t.prototype.canRecoverWithSingleTokenInsertion=function(e,r){var n=this;if(!this.canTokenTypeBeInsertedInRecovery(e)||de.isEmpty(r))return!1;var i=this.LA(1),a=de.find(r,function(o){return n.tokenMatcher(i,o)})!==void 0;return a},t.prototype.canRecoverWithSingleTokenDeletion=function(e){var r=this.tokenMatcher(this.LA(2),e);return r},t.prototype.isInCurrentRuleReSyncSet=function(e){var r=this.getCurrFollowKey(),n=this.getFollowSetFromFollowKey(r);return de.contains(n,e)},t.prototype.findReSyncTokenType=function(){for(var e=this.flattenFollowSet(),r=this.LA(1),n=2;;){var i=r.tokenType;if(de.contains(e,i))return i;r=this.LA(n),n++}},t.prototype.getCurrFollowKey=function(){if(this.RULE_STACK.length===1)return $.EOF_FOLLOW_KEY;var e=this.getLastExplicitRuleShortName(),r=this.getLastExplicitRuleOccurrenceIndex(),n=this.getPreviousExplicitRuleShortName();return{ruleName:this.shortRuleNameToFullName(e),idxInCallingRule:r,inRule:this.shortRuleNameToFullName(n)}},t.prototype.buildFullFollowKeyStack=function(){var e=this,r=this.RULE_STACK,n=this.RULE_OCCURRENCE_STACK;return de.map(r,function(i,a){return a===0?$.EOF_FOLLOW_KEY:{ruleName:e.shortRuleNameToFullName(i),idxInCallingRule:n[a],inRule:e.shortRuleNameToFullName(r[a-1])}})},t.prototype.flattenFollowSet=function(){var e=this,r=de.map(this.buildFullFollowKeyStack(),function(n){return e.getFollowSetFromFollowKey(n)});return de.flatten(r)},t.prototype.getFollowSetFromFollowKey=function(e){if(e===$.EOF_FOLLOW_KEY)return[Ht.EOF];var r=e.ruleName+e.idxInCallingRule+Is.IN+e.inRule;return this.resyncFollows[r]},t.prototype.addToResyncTokens=function(e,r){return this.tokenMatcher(e,Ht.EOF)||r.push(e),r},t.prototype.reSyncTo=function(e){for(var r=[],n=this.LA(1);this.tokenMatcher(n,e)===!1;)n=this.SKIP_TOKEN(),this.addToResyncTokens(n,r);return de.dropRight(r)},t.prototype.attemptInRepetitionRecovery=function(e,r,n,i,a,o,s){},t.prototype.getCurrentGrammarPath=function(e,r){var n=this.getHumanReadableRuleStack(),i=de.cloneArr(this.RULE_OCCURRENCE_STACK),a={ruleStack:n,occurrenceStack:i,lastTok:e,lastTokOccurrence:r};return a},t.prototype.getHumanReadableRuleStack=function(){var e=this;return de.map(this.RULE_STACK,function(r){return e.shortRuleNameToFullName(r)})},t}();$.Recoverable=Ps;function Vi(t,e,r,n,i,a,o){var s=this.getKeyForAutomaticLookahead(n,i),c=this.firstAfterRepMap[s];if(c===void 0){var f=this.getCurrRuleFullName(),p=this.getGAstProductions()[f],l=new a(p,i);c=l.startWalking(),this.firstAfterRepMap[s]=c}var m=c.token,v=c.occurrence,u=c.isEndOfRule;this.RULE_STACK.length===1&&u&&m===void 0&&(m=Ht.EOF,v=1),this.shouldInRepetitionRecoveryBeTried(m,v,o)&&this.tryInRepetitionRecovery(t,e,r,m)}$.attemptInRepetitionRecovery=Vi});var Yt=R(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});P.getKeyForAutomaticLookahead=P.AT_LEAST_ONE_SEP_IDX=P.MANY_SEP_IDX=P.AT_LEAST_ONE_IDX=P.MANY_IDX=P.OPTION_IDX=P.OR_IDX=P.BITS_FOR_ALT_IDX=P.BITS_FOR_RULE_IDX=P.BITS_FOR_OCCURRENCE_IDX=P.BITS_FOR_METHOD_TYPE=void 0;P.BITS_FOR_METHOD_TYPE=4;P.BITS_FOR_OCCURRENCE_IDX=8;P.BITS_FOR_RULE_IDX=12;P.BITS_FOR_ALT_IDX=8;P.OR_IDX=1<{"use strict";Object.defineProperty(Xt,"__esModule",{value:!0});Xt.LooksAhead=void 0;var be=yt(),ge=k(),Ki=ce(),Fe=Yt(),Ve=vt(),xs=function(){function t(){}return t.prototype.initLooksAhead=function(e){this.dynamicTokensEnabled=ge.has(e,"dynamicTokensEnabled")?e.dynamicTokensEnabled:Ki.DEFAULT_PARSER_CONFIG.dynamicTokensEnabled,this.maxLookahead=ge.has(e,"maxLookahead")?e.maxLookahead:Ki.DEFAULT_PARSER_CONFIG.maxLookahead,this.lookAheadFuncsCache=ge.isES2015MapSupported()?new Map:[],ge.isES2015MapSupported()?(this.getLaFuncFromCache=this.getLaFuncFromMap,this.setLaFuncCache=this.setLaFuncCacheUsingMap):(this.getLaFuncFromCache=this.getLaFuncFromObj,this.setLaFuncCache=this.setLaFuncUsingObj)},t.prototype.preComputeLookaheadFunctions=function(e){var r=this;ge.forEach(e,function(n){r.TRACE_INIT(n.name+" Rule Lookahead",function(){var i=Ve.collectMethods(n),a=i.alternation,o=i.repetition,s=i.option,c=i.repetitionMandatory,f=i.repetitionMandatoryWithSeparator,p=i.repetitionWithSeparator;ge.forEach(a,function(l){var m=l.idx===0?"":l.idx;r.TRACE_INIT(""+Ve.getProductionDslName(l)+m,function(){var v=be.buildLookaheadFuncForOr(l.idx,n,l.maxLookahead||r.maxLookahead,l.hasPredicates,r.dynamicTokensEnabled,r.lookAheadBuilderForAlternatives),u=Fe.getKeyForAutomaticLookahead(r.fullRuleNameToShort[n.name],Fe.OR_IDX,l.idx);r.setLaFuncCache(u,v)})}),ge.forEach(o,function(l){r.computeLookaheadFunc(n,l.idx,Fe.MANY_IDX,be.PROD_TYPE.REPETITION,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(s,function(l){r.computeLookaheadFunc(n,l.idx,Fe.OPTION_IDX,be.PROD_TYPE.OPTION,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(c,function(l){r.computeLookaheadFunc(n,l.idx,Fe.AT_LEAST_ONE_IDX,be.PROD_TYPE.REPETITION_MANDATORY,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(f,function(l){r.computeLookaheadFunc(n,l.idx,Fe.AT_LEAST_ONE_SEP_IDX,be.PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(p,function(l){r.computeLookaheadFunc(n,l.idx,Fe.MANY_SEP_IDX,be.PROD_TYPE.REPETITION_WITH_SEPARATOR,l.maxLookahead,Ve.getProductionDslName(l))})})})},t.prototype.computeLookaheadFunc=function(e,r,n,i,a,o){var s=this;this.TRACE_INIT(""+o+(r===0?"":r),function(){var c=be.buildLookaheadFuncForOptionalProd(r,e,a||s.maxLookahead,s.dynamicTokensEnabled,i,s.lookAheadBuilderForOptional),f=Fe.getKeyForAutomaticLookahead(s.fullRuleNameToShort[e.name],n,r);s.setLaFuncCache(f,c)})},t.prototype.lookAheadBuilderForOptional=function(e,r,n){return be.buildSingleAlternativeLookaheadFunction(e,r,n)},t.prototype.lookAheadBuilderForAlternatives=function(e,r,n,i){return be.buildAlternativesLookAheadFunc(e,r,n,i)},t.prototype.getKeyForAutomaticLookahead=function(e,r){var n=this.getLastExplicitRuleShortName();return Fe.getKeyForAutomaticLookahead(n,e,r)},t.prototype.getLaFuncFromCache=function(e){},t.prototype.getLaFuncFromMap=function(e){return this.lookAheadFuncsCache.get(e)},t.prototype.getLaFuncFromObj=function(e){return this.lookAheadFuncsCache[e]},t.prototype.setLaFuncCache=function(e,r){},t.prototype.setLaFuncCacheUsingMap=function(e,r){this.lookAheadFuncsCache.set(e,r)},t.prototype.setLaFuncUsingObj=function(e,r){this.lookAheadFuncsCache[e]=r},t}();Xt.LooksAhead=xs});var Hi=R(ke=>{"use strict";Object.defineProperty(ke,"__esModule",{value:!0});ke.addNoneTerminalToCst=ke.addTerminalToCst=ke.setNodeLocationFull=ke.setNodeLocationOnlyOffset=void 0;function Cs(t,e){isNaN(t.startOffset)===!0?(t.startOffset=e.startOffset,t.endOffset=e.endOffset):t.endOffset{"use strict";Object.defineProperty(Be,"__esModule",{value:!0});Be.defineNameProp=Be.functionName=Be.classNameFromInstance=void 0;var Fs=k();function ws(t){return Yi(t.constructor)}Be.classNameFromInstance=ws;var Xi="name";function Yi(t){var e=t.name;return e||"anonymous"}Be.functionName=Yi;function Ds(t,e){var r=Object.getOwnPropertyDescriptor(t,Xi);return Fs.isUndefined(r)||r.configurable?(Object.defineProperty(t,Xi,{enumerable:!1,configurable:!0,writable:!1,value:e}),!0):!1}Be.defineNameProp=Ds});var ea=R(Y=>{"use strict";Object.defineProperty(Y,"__esModule",{value:!0});Y.validateRedundantMethods=Y.validateMissingCstMethods=Y.validateVisitor=Y.CstVisitorDefinitionError=Y.createBaseVisitorConstructorWithDefaults=Y.createBaseSemanticVisitorConstructor=Y.defaultVisit=void 0;var ve=k(),At=Hr();function $i(t,e){for(var r=ve.keys(t),n=r.length,i=0;i: + `+(""+a.join(` + +`).replace(/\n/g,` + `)))}}};return r.prototype=n,r.prototype.constructor=r,r._RULE_NAMES=e,r}Y.createBaseSemanticVisitorConstructor=Us;function Gs(t,e,r){var n=function(){};At.defineNameProp(n,t+"BaseSemanticsWithDefaults");var i=Object.create(r.prototype);return ve.forEach(e,function(a){i[a]=$i}),n.prototype=i,n.prototype.constructor=n,n}Y.createBaseVisitorConstructorWithDefaults=Gs;var Yr;(function(t){t[t.REDUNDANT_METHOD=0]="REDUNDANT_METHOD",t[t.MISSING_METHOD=1]="MISSING_METHOD"})(Yr=Y.CstVisitorDefinitionError||(Y.CstVisitorDefinitionError={}));function Zi(t,e){var r=Qi(t,e),n=Ji(t,e);return r.concat(n)}Y.validateVisitor=Zi;function Qi(t,e){var r=ve.map(e,function(n){if(!ve.isFunction(t[n]))return{msg:"Missing visitor method: <"+n+"> on "+At.functionName(t.constructor)+" CST Visitor.",type:Yr.MISSING_METHOD,methodName:n}});return ve.compact(r)}Y.validateMissingCstMethods=Qi;var Ws=["constructor","visit","validateVisitor"];function Ji(t,e){var r=[];for(var n in t)ve.isFunction(t[n])&&!ve.contains(Ws,n)&&!ve.contains(e,n)&&r.push({msg:"Redundant visitor method: <"+n+"> on "+At.functionName(t.constructor)+` CST Visitor +There is no Grammar Rule corresponding to this method's name. +`,type:Yr.REDUNDANT_METHOD,methodName:n});return r}Y.validateRedundantMethods=Ji});var ra=R($t=>{"use strict";Object.defineProperty($t,"__esModule",{value:!0});$t.TreeBuilder=void 0;var tt=Hi(),K=k(),ta=ea(),Bs=ce(),qs=function(){function t(){}return t.prototype.initTreeBuilder=function(e){if(this.CST_STACK=[],this.outputCst=e.outputCst,this.nodeLocationTracking=K.has(e,"nodeLocationTracking")?e.nodeLocationTracking:Bs.DEFAULT_PARSER_CONFIG.nodeLocationTracking,!this.outputCst)this.cstInvocationStateUpdate=K.NOOP,this.cstFinallyStateUpdate=K.NOOP,this.cstPostTerminal=K.NOOP,this.cstPostNonTerminal=K.NOOP,this.cstPostRule=K.NOOP;else if(/full/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=tt.setNodeLocationFull,this.setNodeLocationFromNode=tt.setNodeLocationFull,this.cstPostRule=K.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationFullRecovery):(this.setNodeLocationFromToken=K.NOOP,this.setNodeLocationFromNode=K.NOOP,this.cstPostRule=this.cstPostRuleFull,this.setInitialNodeLocation=this.setInitialNodeLocationFullRegular);else if(/onlyOffset/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=tt.setNodeLocationOnlyOffset,this.setNodeLocationFromNode=tt.setNodeLocationOnlyOffset,this.cstPostRule=K.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRecovery):(this.setNodeLocationFromToken=K.NOOP,this.setNodeLocationFromNode=K.NOOP,this.cstPostRule=this.cstPostRuleOnlyOffset,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRegular);else if(/none/i.test(this.nodeLocationTracking))this.setNodeLocationFromToken=K.NOOP,this.setNodeLocationFromNode=K.NOOP,this.cstPostRule=K.NOOP,this.setInitialNodeLocation=K.NOOP;else throw Error('Invalid config option: "'+e.nodeLocationTracking+'"')},t.prototype.setInitialNodeLocationOnlyOffsetRecovery=function(e){e.location={startOffset:NaN,endOffset:NaN}},t.prototype.setInitialNodeLocationOnlyOffsetRegular=function(e){e.location={startOffset:this.LA(1).startOffset,endOffset:NaN}},t.prototype.setInitialNodeLocationFullRecovery=function(e){e.location={startOffset:NaN,startLine:NaN,startColumn:NaN,endOffset:NaN,endLine:NaN,endColumn:NaN}},t.prototype.setInitialNodeLocationFullRegular=function(e){var r=this.LA(1);e.location={startOffset:r.startOffset,startLine:r.startLine,startColumn:r.startColumn,endOffset:NaN,endLine:NaN,endColumn:NaN}},t.prototype.cstInvocationStateUpdate=function(e,r){var n={name:e,children:{}};this.setInitialNodeLocation(n),this.CST_STACK.push(n)},t.prototype.cstFinallyStateUpdate=function(){this.CST_STACK.pop()},t.prototype.cstPostRuleFull=function(e){var r=this.LA(0),n=e.location;n.startOffset<=r.startOffset?(n.endOffset=r.endOffset,n.endLine=r.endLine,n.endColumn=r.endColumn):(n.startOffset=NaN,n.startLine=NaN,n.startColumn=NaN)},t.prototype.cstPostRuleOnlyOffset=function(e){var r=this.LA(0),n=e.location;n.startOffset<=r.startOffset?n.endOffset=r.endOffset:n.startOffset=NaN},t.prototype.cstPostTerminal=function(e,r){var n=this.CST_STACK[this.CST_STACK.length-1];tt.addTerminalToCst(n,r,e),this.setNodeLocationFromToken(n.location,r)},t.prototype.cstPostNonTerminal=function(e,r){var n=this.CST_STACK[this.CST_STACK.length-1];tt.addNoneTerminalToCst(n,r,e),this.setNodeLocationFromNode(n.location,e.location)},t.prototype.getBaseCstVisitorConstructor=function(){if(K.isUndefined(this.baseCstVisitorConstructor)){var e=ta.createBaseSemanticVisitorConstructor(this.className,K.keys(this.gastProductionsCache));return this.baseCstVisitorConstructor=e,e}return this.baseCstVisitorConstructor},t.prototype.getBaseCstVisitorConstructorWithDefaults=function(){if(K.isUndefined(this.baseCstVisitorWithDefaultsConstructor)){var e=ta.createBaseVisitorConstructorWithDefaults(this.className,K.keys(this.gastProductionsCache),this.getBaseCstVisitorConstructor());return this.baseCstVisitorWithDefaultsConstructor=e,e}return this.baseCstVisitorWithDefaultsConstructor},t.prototype.getLastExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-1]},t.prototype.getPreviousExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-2]},t.prototype.getLastExplicitRuleOccurrenceIndex=function(){var e=this.RULE_OCCURRENCE_STACK;return e[e.length-1]},t}();$t.TreeBuilder=qs});var ia=R(Zt=>{"use strict";Object.defineProperty(Zt,"__esModule",{value:!0});Zt.LexerAdapter=void 0;var na=ce(),js=function(){function t(){}return t.prototype.initLexerAdapter=function(){this.tokVector=[],this.tokVectorLength=0,this.currIdx=-1},Object.defineProperty(t.prototype,"input",{get:function(){return this.tokVector},set:function(e){if(this.selfAnalysisDone!==!0)throw Error("Missing invocation at the end of the Parser's constructor.");this.reset(),this.tokVector=e,this.tokVectorLength=e.length},enumerable:!1,configurable:!0}),t.prototype.SKIP_TOKEN=function(){return this.currIdx<=this.tokVector.length-2?(this.consumeToken(),this.LA(1)):na.END_OF_FILE},t.prototype.LA=function(e){var r=this.currIdx+e;return r<0||this.tokVectorLength<=r?na.END_OF_FILE:this.tokVector[r]},t.prototype.consumeToken=function(){this.currIdx++},t.prototype.exportLexerState=function(){return this.currIdx},t.prototype.importLexerState=function(e){this.currIdx=e},t.prototype.resetLexerState=function(){this.currIdx=-1},t.prototype.moveToTerminatedState=function(){this.currIdx=this.tokVector.length-1},t.prototype.getLexerPosition=function(){return this.exportLexerState()},t}();Zt.LexerAdapter=js});var oa=R(Qt=>{"use strict";Object.defineProperty(Qt,"__esModule",{value:!0});Qt.RecognizerApi=void 0;var aa=k(),Vs=et(),Xr=ce(),Ks=mt(),zs=jr(),Hs=ne(),Ys=function(){function t(){}return t.prototype.ACTION=function(e){return e.call(this)},t.prototype.consume=function(e,r,n){return this.consumeInternal(r,e,n)},t.prototype.subrule=function(e,r,n){return this.subruleInternal(r,e,n)},t.prototype.option=function(e,r){return this.optionInternal(r,e)},t.prototype.or=function(e,r){return this.orInternal(r,e)},t.prototype.many=function(e,r){return this.manyInternal(e,r)},t.prototype.atLeastOne=function(e,r){return this.atLeastOneInternal(e,r)},t.prototype.CONSUME=function(e,r){return this.consumeInternal(e,0,r)},t.prototype.CONSUME1=function(e,r){return this.consumeInternal(e,1,r)},t.prototype.CONSUME2=function(e,r){return this.consumeInternal(e,2,r)},t.prototype.CONSUME3=function(e,r){return this.consumeInternal(e,3,r)},t.prototype.CONSUME4=function(e,r){return this.consumeInternal(e,4,r)},t.prototype.CONSUME5=function(e,r){return this.consumeInternal(e,5,r)},t.prototype.CONSUME6=function(e,r){return this.consumeInternal(e,6,r)},t.prototype.CONSUME7=function(e,r){return this.consumeInternal(e,7,r)},t.prototype.CONSUME8=function(e,r){return this.consumeInternal(e,8,r)},t.prototype.CONSUME9=function(e,r){return this.consumeInternal(e,9,r)},t.prototype.SUBRULE=function(e,r){return this.subruleInternal(e,0,r)},t.prototype.SUBRULE1=function(e,r){return this.subruleInternal(e,1,r)},t.prototype.SUBRULE2=function(e,r){return this.subruleInternal(e,2,r)},t.prototype.SUBRULE3=function(e,r){return this.subruleInternal(e,3,r)},t.prototype.SUBRULE4=function(e,r){return this.subruleInternal(e,4,r)},t.prototype.SUBRULE5=function(e,r){return this.subruleInternal(e,5,r)},t.prototype.SUBRULE6=function(e,r){return this.subruleInternal(e,6,r)},t.prototype.SUBRULE7=function(e,r){return this.subruleInternal(e,7,r)},t.prototype.SUBRULE8=function(e,r){return this.subruleInternal(e,8,r)},t.prototype.SUBRULE9=function(e,r){return this.subruleInternal(e,9,r)},t.prototype.OPTION=function(e){return this.optionInternal(e,0)},t.prototype.OPTION1=function(e){return this.optionInternal(e,1)},t.prototype.OPTION2=function(e){return this.optionInternal(e,2)},t.prototype.OPTION3=function(e){return this.optionInternal(e,3)},t.prototype.OPTION4=function(e){return this.optionInternal(e,4)},t.prototype.OPTION5=function(e){return this.optionInternal(e,5)},t.prototype.OPTION6=function(e){return this.optionInternal(e,6)},t.prototype.OPTION7=function(e){return this.optionInternal(e,7)},t.prototype.OPTION8=function(e){return this.optionInternal(e,8)},t.prototype.OPTION9=function(e){return this.optionInternal(e,9)},t.prototype.OR=function(e){return this.orInternal(e,0)},t.prototype.OR1=function(e){return this.orInternal(e,1)},t.prototype.OR2=function(e){return this.orInternal(e,2)},t.prototype.OR3=function(e){return this.orInternal(e,3)},t.prototype.OR4=function(e){return this.orInternal(e,4)},t.prototype.OR5=function(e){return this.orInternal(e,5)},t.prototype.OR6=function(e){return this.orInternal(e,6)},t.prototype.OR7=function(e){return this.orInternal(e,7)},t.prototype.OR8=function(e){return this.orInternal(e,8)},t.prototype.OR9=function(e){return this.orInternal(e,9)},t.prototype.MANY=function(e){this.manyInternal(0,e)},t.prototype.MANY1=function(e){this.manyInternal(1,e)},t.prototype.MANY2=function(e){this.manyInternal(2,e)},t.prototype.MANY3=function(e){this.manyInternal(3,e)},t.prototype.MANY4=function(e){this.manyInternal(4,e)},t.prototype.MANY5=function(e){this.manyInternal(5,e)},t.prototype.MANY6=function(e){this.manyInternal(6,e)},t.prototype.MANY7=function(e){this.manyInternal(7,e)},t.prototype.MANY8=function(e){this.manyInternal(8,e)},t.prototype.MANY9=function(e){this.manyInternal(9,e)},t.prototype.MANY_SEP=function(e){this.manySepFirstInternal(0,e)},t.prototype.MANY_SEP1=function(e){this.manySepFirstInternal(1,e)},t.prototype.MANY_SEP2=function(e){this.manySepFirstInternal(2,e)},t.prototype.MANY_SEP3=function(e){this.manySepFirstInternal(3,e)},t.prototype.MANY_SEP4=function(e){this.manySepFirstInternal(4,e)},t.prototype.MANY_SEP5=function(e){this.manySepFirstInternal(5,e)},t.prototype.MANY_SEP6=function(e){this.manySepFirstInternal(6,e)},t.prototype.MANY_SEP7=function(e){this.manySepFirstInternal(7,e)},t.prototype.MANY_SEP8=function(e){this.manySepFirstInternal(8,e)},t.prototype.MANY_SEP9=function(e){this.manySepFirstInternal(9,e)},t.prototype.AT_LEAST_ONE=function(e){this.atLeastOneInternal(0,e)},t.prototype.AT_LEAST_ONE1=function(e){return this.atLeastOneInternal(1,e)},t.prototype.AT_LEAST_ONE2=function(e){this.atLeastOneInternal(2,e)},t.prototype.AT_LEAST_ONE3=function(e){this.atLeastOneInternal(3,e)},t.prototype.AT_LEAST_ONE4=function(e){this.atLeastOneInternal(4,e)},t.prototype.AT_LEAST_ONE5=function(e){this.atLeastOneInternal(5,e)},t.prototype.AT_LEAST_ONE6=function(e){this.atLeastOneInternal(6,e)},t.prototype.AT_LEAST_ONE7=function(e){this.atLeastOneInternal(7,e)},t.prototype.AT_LEAST_ONE8=function(e){this.atLeastOneInternal(8,e)},t.prototype.AT_LEAST_ONE9=function(e){this.atLeastOneInternal(9,e)},t.prototype.AT_LEAST_ONE_SEP=function(e){this.atLeastOneSepFirstInternal(0,e)},t.prototype.AT_LEAST_ONE_SEP1=function(e){this.atLeastOneSepFirstInternal(1,e)},t.prototype.AT_LEAST_ONE_SEP2=function(e){this.atLeastOneSepFirstInternal(2,e)},t.prototype.AT_LEAST_ONE_SEP3=function(e){this.atLeastOneSepFirstInternal(3,e)},t.prototype.AT_LEAST_ONE_SEP4=function(e){this.atLeastOneSepFirstInternal(4,e)},t.prototype.AT_LEAST_ONE_SEP5=function(e){this.atLeastOneSepFirstInternal(5,e)},t.prototype.AT_LEAST_ONE_SEP6=function(e){this.atLeastOneSepFirstInternal(6,e)},t.prototype.AT_LEAST_ONE_SEP7=function(e){this.atLeastOneSepFirstInternal(7,e)},t.prototype.AT_LEAST_ONE_SEP8=function(e){this.atLeastOneSepFirstInternal(8,e)},t.prototype.AT_LEAST_ONE_SEP9=function(e){this.atLeastOneSepFirstInternal(9,e)},t.prototype.RULE=function(e,r,n){if(n===void 0&&(n=Xr.DEFAULT_RULE_CONFIG),aa.contains(this.definedRulesNames,e)){var i=Ks.defaultGrammarValidatorErrorProvider.buildDuplicateRuleNameError({topLevelRule:e,grammarName:this.className}),a={message:i,type:Xr.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:e};this.definitionErrors.push(a)}this.definedRulesNames.push(e);var o=this.defineRule(e,r,n);return this[e]=o,o},t.prototype.OVERRIDE_RULE=function(e,r,n){n===void 0&&(n=Xr.DEFAULT_RULE_CONFIG);var i=[];i=i.concat(zs.validateRuleIsOverridden(e,this.definedRulesNames,this.className)),this.definitionErrors=this.definitionErrors.concat(i);var a=this.defineRule(e,r,n);return this[e]=a,a},t.prototype.BACKTRACK=function(e,r){return function(){this.isBackTrackingStack.push(1);var n=this.saveRecogState();try{return e.apply(this,r),!0}catch(i){if(Vs.isRecognitionException(i))return!1;throw i}finally{this.reloadRecogState(n),this.isBackTrackingStack.pop()}}},t.prototype.getGAstProductions=function(){return this.gastProductionsCache},t.prototype.getSerializedGastProductions=function(){return Hs.serializeGrammar(aa.values(this.gastProductionsCache))},t}();Qt.RecognizerApi=Ys});var la=R(Jt=>{"use strict";Object.defineProperty(Jt,"__esModule",{value:!0});Jt.RecognizerEngine=void 0;var q=k(),le=Yt(),er=et(),sa=yt(),rt=Tt(),ua=ce(),Xs=zr(),ca=Ue(),Rt=Xe(),$s=Hr(),Zs=function(){function t(){}return t.prototype.initRecognizerEngine=function(e,r){if(this.className=$s.classNameFromInstance(this),this.shortRuleNameToFull={},this.fullRuleNameToShort={},this.ruleShortNameIdx=256,this.tokenMatcher=Rt.tokenStructuredMatcherNoCategories,this.definedRulesNames=[],this.tokensMap={},this.isBackTrackingStack=[],this.RULE_STACK=[],this.RULE_OCCURRENCE_STACK=[],this.gastProductionsCache={},q.has(r,"serializedGrammar"))throw Error(`The Parser's configuration can no longer contain a property. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0 + For Further details.`);if(q.isArray(e)){if(q.isEmpty(e))throw Error(`A Token Vocabulary cannot be empty. + Note that the first argument for the parser constructor + is no longer a Token vector (since v4.0).`);if(typeof e[0].startOffset=="number")throw Error(`The Parser constructor no longer accepts a token vector as the first argument. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0 + For Further details.`)}if(q.isArray(e))this.tokensMap=q.reduce(e,function(o,s){return o[s.name]=s,o},{});else if(q.has(e,"modes")&&q.every(q.flatten(q.values(e.modes)),Rt.isTokenType)){var n=q.flatten(q.values(e.modes)),i=q.uniq(n);this.tokensMap=q.reduce(i,function(o,s){return o[s.name]=s,o},{})}else if(q.isObject(e))this.tokensMap=q.cloneObj(e);else throw new Error(" argument must be An Array of Token constructors, A dictionary of Token constructors or an IMultiModeLexerDefinition");this.tokensMap.EOF=ca.EOF;var a=q.every(q.values(e),function(o){return q.isEmpty(o.categoryMatches)});this.tokenMatcher=a?Rt.tokenStructuredMatcherNoCategories:Rt.tokenStructuredMatcher,Rt.augmentTokenTypes(q.values(this.tokensMap))},t.prototype.defineRule=function(e,r,n){if(this.selfAnalysisDone)throw Error("Grammar rule <"+e+`> may not be defined after the 'performSelfAnalysis' method has been called' +Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`);var i=q.has(n,"resyncEnabled")?n.resyncEnabled:ua.DEFAULT_RULE_CONFIG.resyncEnabled,a=q.has(n,"recoveryValueFunc")?n.recoveryValueFunc:ua.DEFAULT_RULE_CONFIG.recoveryValueFunc,o=this.ruleShortNameIdx<r},t.prototype.orInternal=function(e,r){var n=this.getKeyForAutomaticLookahead(le.OR_IDX,r),i=q.isArray(e)?e:e.DEF,a=this.getLaFuncFromCache(n),o=a.call(this,i);if(o!==void 0){var s=i[o];return s.ALT.call(this)}this.raiseNoAltException(r,e.ERR_MSG)},t.prototype.ruleFinallyStateUpdate=function(){if(this.RULE_STACK.pop(),this.RULE_OCCURRENCE_STACK.pop(),this.cstFinallyStateUpdate(),this.RULE_STACK.length===0&&this.isAtEndOfInput()===!1){var e=this.LA(1),r=this.errorMessageProvider.buildNotAllInputParsedMessage({firstRedundant:e,ruleName:this.getCurrRuleFullName()});this.SAVE_ERROR(new er.NotAllInputParsedException(r,e))}},t.prototype.subruleInternal=function(e,r,n){var i;try{var a=n!==void 0?n.ARGS:void 0;return i=e.call(this,r,a),this.cstPostNonTerminal(i,n!==void 0&&n.LABEL!==void 0?n.LABEL:e.ruleName),i}catch(o){this.subruleInternalError(o,n,e.ruleName)}},t.prototype.subruleInternalError=function(e,r,n){throw er.isRecognitionException(e)&&e.partialCstResult!==void 0&&(this.cstPostNonTerminal(e.partialCstResult,r!==void 0&&r.LABEL!==void 0?r.LABEL:n),delete e.partialCstResult),e},t.prototype.consumeInternal=function(e,r,n){var i;try{var a=this.LA(1);this.tokenMatcher(a,e)===!0?(this.consumeToken(),i=a):this.consumeInternalError(e,a,n)}catch(o){i=this.consumeInternalRecovery(e,r,o)}return this.cstPostTerminal(n!==void 0&&n.LABEL!==void 0?n.LABEL:e.name,i),i},t.prototype.consumeInternalError=function(e,r,n){var i,a=this.LA(0);throw n!==void 0&&n.ERR_MSG?i=n.ERR_MSG:i=this.errorMessageProvider.buildMismatchTokenMessage({expected:e,actual:r,previous:a,ruleName:this.getCurrRuleFullName()}),this.SAVE_ERROR(new er.MismatchedTokenException(i,r,a))},t.prototype.consumeInternalRecovery=function(e,r,n){if(this.recoveryEnabled&&n.name==="MismatchedTokenException"&&!this.isBackTracking()){var i=this.getFollowsForInRuleRecovery(e,r);try{return this.tryInRuleRecovery(e,i)}catch(a){throw a.name===Xs.IN_RULE_RECOVERY_EXCEPTION?n:a}}else throw n},t.prototype.saveRecogState=function(){var e=this.errors,r=q.cloneArr(this.RULE_STACK);return{errors:e,lexerState:this.exportLexerState(),RULE_STACK:r,CST_STACK:this.CST_STACK}},t.prototype.reloadRecogState=function(e){this.errors=e.errors,this.importLexerState(e.lexerState),this.RULE_STACK=e.RULE_STACK},t.prototype.ruleInvocationStateUpdate=function(e,r,n){this.RULE_OCCURRENCE_STACK.push(n),this.RULE_STACK.push(e),this.cstInvocationStateUpdate(r,e)},t.prototype.isBackTracking=function(){return this.isBackTrackingStack.length!==0},t.prototype.getCurrRuleFullName=function(){var e=this.getLastExplicitRuleShortName();return this.shortRuleNameToFull[e]},t.prototype.shortRuleNameToFullName=function(e){return this.shortRuleNameToFull[e]},t.prototype.isAtEndOfInput=function(){return this.tokenMatcher(this.LA(1),ca.EOF)},t.prototype.reset=function(){this.resetLexerState(),this.isBackTrackingStack=[],this.errors=[],this.RULE_STACK=[],this.CST_STACK=[],this.RULE_OCCURRENCE_STACK=[]},t}();Jt.RecognizerEngine=Zs});var pa=R(tr=>{"use strict";Object.defineProperty(tr,"__esModule",{value:!0});tr.ErrorHandler=void 0;var $r=et(),Zr=k(),fa=yt(),Qs=ce(),Js=function(){function t(){}return t.prototype.initErrorHandler=function(e){this._errors=[],this.errorMessageProvider=Zr.has(e,"errorMessageProvider")?e.errorMessageProvider:Qs.DEFAULT_PARSER_CONFIG.errorMessageProvider},t.prototype.SAVE_ERROR=function(e){if($r.isRecognitionException(e))return e.context={ruleStack:this.getHumanReadableRuleStack(),ruleOccurrenceStack:Zr.cloneArr(this.RULE_OCCURRENCE_STACK)},this._errors.push(e),e;throw Error("Trying to save an Error which is not a RecognitionException")},Object.defineProperty(t.prototype,"errors",{get:function(){return Zr.cloneArr(this._errors)},set:function(e){this._errors=e},enumerable:!1,configurable:!0}),t.prototype.raiseEarlyExitException=function(e,r,n){for(var i=this.getCurrRuleFullName(),a=this.getGAstProductions()[i],o=fa.getLookaheadPathsForOptionalProd(e,a,r,this.maxLookahead),s=o[0],c=[],f=1;f<=this.maxLookahead;f++)c.push(this.LA(f));var p=this.errorMessageProvider.buildEarlyExitMessage({expectedIterationPaths:s,actual:c,previous:this.LA(0),customUserDescription:n,ruleName:i});throw this.SAVE_ERROR(new $r.EarlyExitException(p,this.LA(1),this.LA(0)))},t.prototype.raiseNoAltException=function(e,r){for(var n=this.getCurrRuleFullName(),i=this.getGAstProductions()[n],a=fa.getLookaheadPathsForOr(e,i,this.maxLookahead),o=[],s=1;s<=this.maxLookahead;s++)o.push(this.LA(s));var c=this.LA(0),f=this.errorMessageProvider.buildNoViableAltMessage({expectedPathsPerAlt:a,actual:o,previous:c,customUserDescription:r,ruleName:this.getCurrRuleFullName()});throw this.SAVE_ERROR(new $r.NoViableAltException(f,this.LA(1),c))},t}();tr.ErrorHandler=Js});var va=R(rr=>{"use strict";Object.defineProperty(rr,"__esModule",{value:!0});rr.ContentAssist=void 0;var ha=Tt(),da=k(),eu=function(){function t(){}return t.prototype.initContentAssist=function(){},t.prototype.computeContentAssist=function(e,r){var n=this.gastProductionsCache[e];if(da.isUndefined(n))throw Error("Rule ->"+e+"<- does not exist in this grammar.");return ha.nextPossibleTokensAfter([n],r,this.tokenMatcher,this.maxLookahead)},t.prototype.getNextPossibleTokenTypes=function(e){var r=da.first(e.ruleStack),n=this.getGAstProductions(),i=n[r],a=new ha.NextAfterTokenWalker(i,e).startWalking();return a},t}();rr.ContentAssist=eu});var Ra=R(nr=>{"use strict";Object.defineProperty(nr,"__esModule",{value:!0});nr.GastRecorder=void 0;var oe=k(),Pe=ne(),tu=ft(),ma=Xe(),Ea=Ue(),ru=ce(),nu=Yt(),ir={description:"This Object indicates the Parser is during Recording Phase"};Object.freeze(ir);var Ta=!0,ya=Math.pow(2,nu.BITS_FOR_OCCURRENCE_IDX)-1,_a=Ea.createToken({name:"RECORDING_PHASE_TOKEN",pattern:tu.Lexer.NA});ma.augmentTokenTypes([_a]);var ga=Ea.createTokenInstance(_a,`This IToken indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,-1,-1,-1,-1,-1,-1);Object.freeze(ga);var iu={name:`This CSTNode indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,children:{}},ou=function(){function t(){}return t.prototype.initGastRecorder=function(e){this.recordingProdStack=[],this.RECORDING_PHASE=!1},t.prototype.enableRecording=function(){var e=this;this.RECORDING_PHASE=!0,this.TRACE_INIT("Enable Recording",function(){for(var r=function(i){var a=i>0?i:"";e["CONSUME"+a]=function(o,s){return this.consumeInternalRecord(o,i,s)},e["SUBRULE"+a]=function(o,s){return this.subruleInternalRecord(o,i,s)},e["OPTION"+a]=function(o){return this.optionInternalRecord(o,i)},e["OR"+a]=function(o){return this.orInternalRecord(o,i)},e["MANY"+a]=function(o){this.manyInternalRecord(i,o)},e["MANY_SEP"+a]=function(o){this.manySepFirstInternalRecord(i,o)},e["AT_LEAST_ONE"+a]=function(o){this.atLeastOneInternalRecord(i,o)},e["AT_LEAST_ONE_SEP"+a]=function(o){this.atLeastOneSepFirstInternalRecord(i,o)}},n=0;n<10;n++)r(n);e.consume=function(i,a,o){return this.consumeInternalRecord(a,i,o)},e.subrule=function(i,a,o){return this.subruleInternalRecord(a,i,o)},e.option=function(i,a){return this.optionInternalRecord(a,i)},e.or=function(i,a){return this.orInternalRecord(a,i)},e.many=function(i,a){this.manyInternalRecord(i,a)},e.atLeastOne=function(i,a){this.atLeastOneInternalRecord(i,a)},e.ACTION=e.ACTION_RECORD,e.BACKTRACK=e.BACKTRACK_RECORD,e.LA=e.LA_RECORD})},t.prototype.disableRecording=function(){var e=this;this.RECORDING_PHASE=!1,this.TRACE_INIT("Deleting Recording methods",function(){for(var r=0;r<10;r++){var n=r>0?r:"";delete e["CONSUME"+n],delete e["SUBRULE"+n],delete e["OPTION"+n],delete e["OR"+n],delete e["MANY"+n],delete e["MANY_SEP"+n],delete e["AT_LEAST_ONE"+n],delete e["AT_LEAST_ONE_SEP"+n]}delete e.consume,delete e.subrule,delete e.option,delete e.or,delete e.many,delete e.atLeastOne,delete e.ACTION,delete e.BACKTRACK,delete e.LA})},t.prototype.ACTION_RECORD=function(e){},t.prototype.BACKTRACK_RECORD=function(e,r){return function(){return!0}},t.prototype.LA_RECORD=function(e){return ru.END_OF_FILE},t.prototype.topLevelRuleRecord=function(e,r){try{var n=new Pe.Rule({definition:[],name:e});return n.name=e,this.recordingProdStack.push(n),r.call(this),this.recordingProdStack.pop(),n}catch(i){if(i.KNOWN_RECORDER_ERROR!==!0)try{i.message=i.message+` + This error was thrown during the "grammar recording phase" For more info see: + https://chevrotain.io/docs/guide/internals.html#grammar-recording`}catch(a){throw i}throw i}},t.prototype.optionInternalRecord=function(e,r){return Ot.call(this,Pe.Option,e,r)},t.prototype.atLeastOneInternalRecord=function(e,r){Ot.call(this,Pe.RepetitionMandatory,r,e)},t.prototype.atLeastOneSepFirstInternalRecord=function(e,r){Ot.call(this,Pe.RepetitionMandatoryWithSeparator,r,e,Ta)},t.prototype.manyInternalRecord=function(e,r){Ot.call(this,Pe.Repetition,r,e)},t.prototype.manySepFirstInternalRecord=function(e,r){Ot.call(this,Pe.RepetitionWithSeparator,r,e,Ta)},t.prototype.orInternalRecord=function(e,r){return au.call(this,e,r)},t.prototype.subruleInternalRecord=function(e,r,n){if(ar(r),!e||oe.has(e,"ruleName")===!1){var i=new Error(" argument is invalid"+(" expecting a Parser method reference but got: <"+JSON.stringify(e)+">")+(` + inside top level rule: <`+this.recordingProdStack[0].name+">"));throw i.KNOWN_RECORDER_ERROR=!0,i}var a=oe.peek(this.recordingProdStack),o=e.ruleName,s=new Pe.NonTerminal({idx:r,nonTerminalName:o,referencedRule:void 0});return a.definition.push(s),this.outputCst?iu:ir},t.prototype.consumeInternalRecord=function(e,r,n){if(ar(r),!ma.hasShortKeyProperty(e)){var i=new Error(" argument is invalid"+(" expecting a TokenType reference but got: <"+JSON.stringify(e)+">")+(` + inside top level rule: <`+this.recordingProdStack[0].name+">"));throw i.KNOWN_RECORDER_ERROR=!0,i}var a=oe.peek(this.recordingProdStack),o=new Pe.Terminal({idx:r,terminalType:e});return a.definition.push(o),ga},t}();nr.GastRecorder=ou;function Ot(t,e,r,n){n===void 0&&(n=!1),ar(r);var i=oe.peek(this.recordingProdStack),a=oe.isFunction(e)?e:e.DEF,o=new t({definition:[],idx:r});return n&&(o.separator=e.SEP),oe.has(e,"MAX_LOOKAHEAD")&&(o.maxLookahead=e.MAX_LOOKAHEAD),this.recordingProdStack.push(o),a.call(this),i.definition.push(o),this.recordingProdStack.pop(),ir}function au(t,e){var r=this;ar(e);var n=oe.peek(this.recordingProdStack),i=oe.isArray(t)===!1,a=i===!1?t:t.DEF,o=new Pe.Alternation({definition:[],idx:e,ignoreAmbiguities:i&&t.IGNORE_AMBIGUITIES===!0});oe.has(t,"MAX_LOOKAHEAD")&&(o.maxLookahead=t.MAX_LOOKAHEAD);var s=oe.some(a,function(c){return oe.isFunction(c.GATE)});return o.hasPredicates=s,n.definition.push(o),oe.forEach(a,function(c){var f=new Pe.Alternative({definition:[]});o.definition.push(f),oe.has(c,"IGNORE_AMBIGUITIES")?f.ignoreAmbiguities=c.IGNORE_AMBIGUITIES:oe.has(c,"GATE")&&(f.ignoreAmbiguities=!0),r.recordingProdStack.push(f),c.ALT.call(r),r.recordingProdStack.pop()}),ir}function Aa(t){return t===0?"":""+t}function ar(t){if(t<0||t>ya){var e=new Error("Invalid DSL Method idx value: <"+t+`> + `+("Idx value must be a none negative value smaller than "+(ya+1)));throw e.KNOWN_RECORDER_ERROR=!0,e}}});var Na=R(or=>{"use strict";Object.defineProperty(or,"__esModule",{value:!0});or.PerformanceTracer=void 0;var Oa=k(),su=ce(),uu=function(){function t(){}return t.prototype.initPerformanceTracer=function(e){if(Oa.has(e,"traceInitPerf")){var r=e.traceInitPerf,n=typeof r=="number";this.traceInitMaxIdent=n?r:Infinity,this.traceInitPerf=n?r>0:r}else this.traceInitMaxIdent=0,this.traceInitPerf=su.DEFAULT_PARSER_CONFIG.traceInitPerf;this.traceInitIndent=-1},t.prototype.TRACE_INIT=function(e,r){if(this.traceInitPerf===!0){this.traceInitIndent++;var n=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <"+e+">");var i=Oa.timer(r),a=i.time,o=i.value,s=a>10?console.warn:console.log;return this.traceInitIndent time: "+a+"ms"),this.traceInitIndent--,o}else return r()},t}();or.PerformanceTracer=uu});var Ia=R(sr=>{"use strict";Object.defineProperty(sr,"__esModule",{value:!0});sr.applyMixins=void 0;function cu(t,e){e.forEach(function(r){var n=r.prototype;Object.getOwnPropertyNames(n).forEach(function(i){if(i!=="constructor"){var a=Object.getOwnPropertyDescriptor(n,i);a&&(a.get||a.set)?Object.defineProperty(t.prototype,i,a):t.prototype[i]=r.prototype[i]}})})}sr.applyMixins=cu});var ce=R(U=>{"use strict";var ka=U&&U.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(U,"__esModule",{value:!0});U.EmbeddedActionsParser=U.CstParser=U.Parser=U.EMPTY_ALT=U.ParserDefinitionErrorType=U.DEFAULT_RULE_CONFIG=U.DEFAULT_PARSER_CONFIG=U.END_OF_FILE=void 0;var ee=k(),lu=pi(),Pa=Ue(),Sa=mt(),xa=Ui(),fu=zr(),pu=zi(),hu=ra(),du=ia(),vu=oa(),mu=la(),Eu=pa(),Tu=va(),yu=Ra(),_u=Na(),gu=Ia();U.END_OF_FILE=Pa.createTokenInstance(Pa.EOF,"",NaN,NaN,NaN,NaN,NaN,NaN);Object.freeze(U.END_OF_FILE);U.DEFAULT_PARSER_CONFIG=Object.freeze({recoveryEnabled:!1,maxLookahead:3,dynamicTokensEnabled:!1,outputCst:!0,errorMessageProvider:Sa.defaultParserErrorProvider,nodeLocationTracking:"none",traceInitPerf:!1,skipValidations:!1});U.DEFAULT_RULE_CONFIG=Object.freeze({recoveryValueFunc:function(){},resyncEnabled:!0});var Au;(function(t){t[t.INVALID_RULE_NAME=0]="INVALID_RULE_NAME",t[t.DUPLICATE_RULE_NAME=1]="DUPLICATE_RULE_NAME",t[t.INVALID_RULE_OVERRIDE=2]="INVALID_RULE_OVERRIDE",t[t.DUPLICATE_PRODUCTIONS=3]="DUPLICATE_PRODUCTIONS",t[t.UNRESOLVED_SUBRULE_REF=4]="UNRESOLVED_SUBRULE_REF",t[t.LEFT_RECURSION=5]="LEFT_RECURSION",t[t.NONE_LAST_EMPTY_ALT=6]="NONE_LAST_EMPTY_ALT",t[t.AMBIGUOUS_ALTS=7]="AMBIGUOUS_ALTS",t[t.CONFLICT_TOKENS_RULES_NAMESPACE=8]="CONFLICT_TOKENS_RULES_NAMESPACE",t[t.INVALID_TOKEN_NAME=9]="INVALID_TOKEN_NAME",t[t.NO_NON_EMPTY_LOOKAHEAD=10]="NO_NON_EMPTY_LOOKAHEAD",t[t.AMBIGUOUS_PREFIX_ALTS=11]="AMBIGUOUS_PREFIX_ALTS",t[t.TOO_MANY_ALTS=12]="TOO_MANY_ALTS"})(Au=U.ParserDefinitionErrorType||(U.ParserDefinitionErrorType={}));function Ru(t){return t===void 0&&(t=void 0),function(){return t}}U.EMPTY_ALT=Ru;var ur=function(){function t(e,r){this.definitionErrors=[],this.selfAnalysisDone=!1;var n=this;if(n.initErrorHandler(r),n.initLexerAdapter(),n.initLooksAhead(r),n.initRecognizerEngine(e,r),n.initRecoverable(r),n.initTreeBuilder(r),n.initContentAssist(),n.initGastRecorder(r),n.initPerformanceTracer(r),ee.has(r,"ignoredIssues"))throw new Error(`The IParserConfig property has been deprecated. + Please use the flag on the relevant DSL method instead. + See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES + For further details.`);this.skipValidations=ee.has(r,"skipValidations")?r.skipValidations:U.DEFAULT_PARSER_CONFIG.skipValidations}return t.performSelfAnalysis=function(e){throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.")},t.prototype.performSelfAnalysis=function(){var e=this;this.TRACE_INIT("performSelfAnalysis",function(){var r;e.selfAnalysisDone=!0;var n=e.className;e.TRACE_INIT("toFastProps",function(){ee.toFastProperties(e)}),e.TRACE_INIT("Grammar Recording",function(){try{e.enableRecording(),ee.forEach(e.definedRulesNames,function(a){var o=e[a],s=o.originalGrammarAction,c=void 0;e.TRACE_INIT(a+" Rule",function(){c=e.topLevelRuleRecord(a,s)}),e.gastProductionsCache[a]=c})}finally{e.disableRecording()}});var i=[];if(e.TRACE_INIT("Grammar Resolving",function(){i=xa.resolveGrammar({rules:ee.values(e.gastProductionsCache)}),e.definitionErrors=e.definitionErrors.concat(i)}),e.TRACE_INIT("Grammar Validations",function(){if(ee.isEmpty(i)&&e.skipValidations===!1){var a=xa.validateGrammar({rules:ee.values(e.gastProductionsCache),maxLookahead:e.maxLookahead,tokenTypes:ee.values(e.tokensMap),errMsgProvider:Sa.defaultGrammarValidatorErrorProvider,grammarName:n});e.definitionErrors=e.definitionErrors.concat(a)}}),ee.isEmpty(e.definitionErrors)&&(e.recoveryEnabled&&e.TRACE_INIT("computeAllProdsFollows",function(){var a=lu.computeAllProdsFollows(ee.values(e.gastProductionsCache));e.resyncFollows=a}),e.TRACE_INIT("ComputeLookaheadFunctions",function(){e.preComputeLookaheadFunctions(ee.values(e.gastProductionsCache))})),!t.DEFER_DEFINITION_ERRORS_HANDLING&&!ee.isEmpty(e.definitionErrors))throw r=ee.map(e.definitionErrors,function(a){return a.message}),new Error(`Parser Definition Errors detected: + `+r.join(` +------------------------------- +`))})},t.DEFER_DEFINITION_ERRORS_HANDLING=!1,t}();U.Parser=ur;gu.applyMixins(ur,[fu.Recoverable,pu.LooksAhead,hu.TreeBuilder,du.LexerAdapter,mu.RecognizerEngine,vu.RecognizerApi,Eu.ErrorHandler,Tu.ContentAssist,yu.GastRecorder,_u.PerformanceTracer]);var Ou=function(t){ka(e,t);function e(r,n){n===void 0&&(n=U.DEFAULT_PARSER_CONFIG);var i=this,a=ee.cloneObj(n);return a.outputCst=!0,i=t.call(this,r,a)||this,i}return e}(ur);U.CstParser=Ou;var Nu=function(t){ka(e,t);function e(r,n){n===void 0&&(n=U.DEFAULT_PARSER_CONFIG);var i=this,a=ee.cloneObj(n);return a.outputCst=!1,i=t.call(this,r,a)||this,i}return e}(ur);U.EmbeddedActionsParser=Nu});var La=R(cr=>{"use strict";Object.defineProperty(cr,"__esModule",{value:!0});cr.createSyntaxDiagramsCode=void 0;var Ca=Er();function Iu(t,e){var r=e===void 0?{}:e,n=r.resourceBase,i=n===void 0?"https://unpkg.com/chevrotain@"+Ca.VERSION+"/diagrams/":n,a=r.css,o=a===void 0?"https://unpkg.com/chevrotain@"+Ca.VERSION+"/diagrams/diagrams.css":a,s=` + + + + + +`,c=` + +`,f=` + + + + +`,p=` +
+`,l=` + +`,m=` + +`;return s+c+f+p+l+m}cr.createSyntaxDiagramsCode=Iu});var Fa=R(E=>{"use strict";Object.defineProperty(E,"__esModule",{value:!0});E.Parser=E.createSyntaxDiagramsCode=E.clearCache=E.GAstVisitor=E.serializeProduction=E.serializeGrammar=E.Terminal=E.Rule=E.RepetitionWithSeparator=E.RepetitionMandatoryWithSeparator=E.RepetitionMandatory=E.Repetition=E.Option=E.NonTerminal=E.Alternative=E.Alternation=E.defaultLexerErrorProvider=E.NoViableAltException=E.NotAllInputParsedException=E.MismatchedTokenException=E.isRecognitionException=E.EarlyExitException=E.defaultParserErrorProvider=E.tokenName=E.tokenMatcher=E.tokenLabel=E.EOF=E.createTokenInstance=E.createToken=E.LexerDefinitionErrorType=E.Lexer=E.EMPTY_ALT=E.ParserDefinitionErrorType=E.EmbeddedActionsParser=E.CstParser=E.VERSION=void 0;var ku=Er();Object.defineProperty(E,"VERSION",{enumerable:!0,get:function(){return ku.VERSION}});var lr=ce();Object.defineProperty(E,"CstParser",{enumerable:!0,get:function(){return lr.CstParser}});Object.defineProperty(E,"EmbeddedActionsParser",{enumerable:!0,get:function(){return lr.EmbeddedActionsParser}});Object.defineProperty(E,"ParserDefinitionErrorType",{enumerable:!0,get:function(){return lr.ParserDefinitionErrorType}});Object.defineProperty(E,"EMPTY_ALT",{enumerable:!0,get:function(){return lr.EMPTY_ALT}});var Ma=ft();Object.defineProperty(E,"Lexer",{enumerable:!0,get:function(){return Ma.Lexer}});Object.defineProperty(E,"LexerDefinitionErrorType",{enumerable:!0,get:function(){return Ma.LexerDefinitionErrorType}});var nt=Ue();Object.defineProperty(E,"createToken",{enumerable:!0,get:function(){return nt.createToken}});Object.defineProperty(E,"createTokenInstance",{enumerable:!0,get:function(){return nt.createTokenInstance}});Object.defineProperty(E,"EOF",{enumerable:!0,get:function(){return nt.EOF}});Object.defineProperty(E,"tokenLabel",{enumerable:!0,get:function(){return nt.tokenLabel}});Object.defineProperty(E,"tokenMatcher",{enumerable:!0,get:function(){return nt.tokenMatcher}});Object.defineProperty(E,"tokenName",{enumerable:!0,get:function(){return nt.tokenName}});var Pu=mt();Object.defineProperty(E,"defaultParserErrorProvider",{enumerable:!0,get:function(){return Pu.defaultParserErrorProvider}});var Nt=et();Object.defineProperty(E,"EarlyExitException",{enumerable:!0,get:function(){return Nt.EarlyExitException}});Object.defineProperty(E,"isRecognitionException",{enumerable:!0,get:function(){return Nt.isRecognitionException}});Object.defineProperty(E,"MismatchedTokenException",{enumerable:!0,get:function(){return Nt.MismatchedTokenException}});Object.defineProperty(E,"NotAllInputParsedException",{enumerable:!0,get:function(){return Nt.NotAllInputParsedException}});Object.defineProperty(E,"NoViableAltException",{enumerable:!0,get:function(){return Nt.NoViableAltException}});var Su=kr();Object.defineProperty(E,"defaultLexerErrorProvider",{enumerable:!0,get:function(){return Su.defaultLexerErrorProvider}});var Se=ne();Object.defineProperty(E,"Alternation",{enumerable:!0,get:function(){return Se.Alternation}});Object.defineProperty(E,"Alternative",{enumerable:!0,get:function(){return Se.Alternative}});Object.defineProperty(E,"NonTerminal",{enumerable:!0,get:function(){return Se.NonTerminal}});Object.defineProperty(E,"Option",{enumerable:!0,get:function(){return Se.Option}});Object.defineProperty(E,"Repetition",{enumerable:!0,get:function(){return Se.Repetition}});Object.defineProperty(E,"RepetitionMandatory",{enumerable:!0,get:function(){return Se.RepetitionMandatory}});Object.defineProperty(E,"RepetitionMandatoryWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionMandatoryWithSeparator}});Object.defineProperty(E,"RepetitionWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionWithSeparator}});Object.defineProperty(E,"Rule",{enumerable:!0,get:function(){return Se.Rule}});Object.defineProperty(E,"Terminal",{enumerable:!0,get:function(){return Se.Terminal}});var ba=ne();Object.defineProperty(E,"serializeGrammar",{enumerable:!0,get:function(){return ba.serializeGrammar}});Object.defineProperty(E,"serializeProduction",{enumerable:!0,get:function(){return ba.serializeProduction}});var xu=$e();Object.defineProperty(E,"GAstVisitor",{enumerable:!0,get:function(){return xu.GAstVisitor}});function Cu(){console.warn(`The clearCache function was 'soft' removed from the Chevrotain API. + It performs no action other than printing this message. + Please avoid using it as it will be completely removed in the future`)}E.clearCache=Cu;var Lu=La();Object.defineProperty(E,"createSyntaxDiagramsCode",{enumerable:!0,get:function(){return Lu.createSyntaxDiagramsCode}});var Mu=function(){function t(){throw new Error(`The Parser class has been deprecated, use CstParser or EmbeddedActionsParser instead. +See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0`)}return t}();E.Parser=Mu});export default Fa(); diff --git a/jsm/libs/ecsy.module.js b/jsm/libs/ecsy.module.js new file mode 100644 index 0000000..6f67599 --- /dev/null +++ b/jsm/libs/ecsy.module.js @@ -0,0 +1,1792 @@ +/** + * Return the name of a component + * @param {Component} Component + * @private + */ + +/** + * Get a key from a list of components + * @param {Array(Component)} Components Array of components to generate the key + * @private + */ +function queryKey(Components) { + var ids = []; + for (var n = 0; n < Components.length; n++) { + var T = Components[n]; + + if (!componentRegistered(T)) { + throw new Error(`Tried to create a query with an unregistered component`); + } + + if (typeof T === "object") { + var operator = T.operator === "not" ? "!" : T.operator; + ids.push(operator + T.Component._typeId); + } else { + ids.push(T._typeId); + } + } + + return ids.sort().join("-"); +} + +// Detector for browser's "window" +const hasWindow = typeof window !== "undefined"; + +// performance.now() "polyfill" +const now = + hasWindow && typeof window.performance !== "undefined" + ? performance.now.bind(performance) + : Date.now.bind(Date); + +function componentRegistered(T) { + return ( + (typeof T === "object" && T.Component._typeId !== undefined) || + (T.isComponent && T._typeId !== undefined) + ); +} + +class SystemManager { + constructor(world) { + this._systems = []; + this._executeSystems = []; // Systems that have `execute` method + this.world = world; + this.lastExecutedSystem = null; + } + + registerSystem(SystemClass, attributes) { + if (!SystemClass.isSystem) { + throw new Error( + `System '${SystemClass.name}' does not extend 'System' class` + ); + } + + if (this.getSystem(SystemClass) !== undefined) { + console.warn(`System '${SystemClass.getName()}' already registered.`); + return this; + } + + var system = new SystemClass(this.world, attributes); + if (system.init) system.init(attributes); + system.order = this._systems.length; + this._systems.push(system); + if (system.execute) { + this._executeSystems.push(system); + this.sortSystems(); + } + return this; + } + + unregisterSystem(SystemClass) { + let system = this.getSystem(SystemClass); + if (system === undefined) { + console.warn( + `Can unregister system '${SystemClass.getName()}'. It doesn't exist.` + ); + return this; + } + + this._systems.splice(this._systems.indexOf(system), 1); + + if (system.execute) { + this._executeSystems.splice(this._executeSystems.indexOf(system), 1); + } + + // @todo Add system.unregister() call to free resources + return this; + } + + sortSystems() { + this._executeSystems.sort((a, b) => { + return a.priority - b.priority || a.order - b.order; + }); + } + + getSystem(SystemClass) { + return this._systems.find((s) => s instanceof SystemClass); + } + + getSystems() { + return this._systems; + } + + removeSystem(SystemClass) { + var index = this._systems.indexOf(SystemClass); + if (!~index) return; + + this._systems.splice(index, 1); + } + + executeSystem(system, delta, time) { + if (system.initialized) { + if (system.canExecute()) { + let startTime = now(); + system.execute(delta, time); + system.executeTime = now() - startTime; + this.lastExecutedSystem = system; + system.clearEvents(); + } + } + } + + stop() { + this._executeSystems.forEach((system) => system.stop()); + } + + execute(delta, time, forcePlay) { + this._executeSystems.forEach( + (system) => + (forcePlay || system.enabled) && this.executeSystem(system, delta, time) + ); + } + + stats() { + var stats = { + numSystems: this._systems.length, + systems: {}, + }; + + for (var i = 0; i < this._systems.length; i++) { + var system = this._systems[i]; + var systemStats = (stats.systems[system.getName()] = { + queries: {}, + executeTime: system.executeTime, + }); + for (var name in system.ctx) { + systemStats.queries[name] = system.ctx[name].stats(); + } + } + + return stats; + } +} + +class ObjectPool { + // @todo Add initial size + constructor(T, initialSize) { + this.freeList = []; + this.count = 0; + this.T = T; + this.isObjectPool = true; + + if (typeof initialSize !== "undefined") { + this.expand(initialSize); + } + } + + acquire() { + // Grow the list by 20%ish if we're out + if (this.freeList.length <= 0) { + this.expand(Math.round(this.count * 0.2) + 1); + } + + var item = this.freeList.pop(); + + return item; + } + + release(item) { + item.reset(); + this.freeList.push(item); + } + + expand(count) { + for (var n = 0; n < count; n++) { + var clone = new this.T(); + clone._pool = this; + this.freeList.push(clone); + } + this.count += count; + } + + totalSize() { + return this.count; + } + + totalFree() { + return this.freeList.length; + } + + totalUsed() { + return this.count - this.freeList.length; + } +} + +/** + * @private + * @class EventDispatcher + */ +class EventDispatcher { + constructor() { + this._listeners = {}; + this.stats = { + fired: 0, + handled: 0, + }; + } + + /** + * Add an event listener + * @param {String} eventName Name of the event to listen + * @param {Function} listener Callback to trigger when the event is fired + */ + addEventListener(eventName, listener) { + let listeners = this._listeners; + if (listeners[eventName] === undefined) { + listeners[eventName] = []; + } + + if (listeners[eventName].indexOf(listener) === -1) { + listeners[eventName].push(listener); + } + } + + /** + * Check if an event listener is already added to the list of listeners + * @param {String} eventName Name of the event to check + * @param {Function} listener Callback for the specified event + */ + hasEventListener(eventName, listener) { + return ( + this._listeners[eventName] !== undefined && + this._listeners[eventName].indexOf(listener) !== -1 + ); + } + + /** + * Remove an event listener + * @param {String} eventName Name of the event to remove + * @param {Function} listener Callback for the specified event + */ + removeEventListener(eventName, listener) { + var listenerArray = this._listeners[eventName]; + if (listenerArray !== undefined) { + var index = listenerArray.indexOf(listener); + if (index !== -1) { + listenerArray.splice(index, 1); + } + } + } + + /** + * Dispatch an event + * @param {String} eventName Name of the event to dispatch + * @param {Entity} entity (Optional) Entity to emit + * @param {Component} component + */ + dispatchEvent(eventName, entity, component) { + this.stats.fired++; + + var listenerArray = this._listeners[eventName]; + if (listenerArray !== undefined) { + var array = listenerArray.slice(0); + + for (var i = 0; i < array.length; i++) { + array[i].call(this, entity, component); + } + } + } + + /** + * Reset stats counters + */ + resetCounters() { + this.stats.fired = this.stats.handled = 0; + } +} + +class Query { + /** + * @param {Array(Component)} Components List of types of components to query + */ + constructor(Components, manager) { + this.Components = []; + this.NotComponents = []; + + Components.forEach((component) => { + if (typeof component === "object") { + this.NotComponents.push(component.Component); + } else { + this.Components.push(component); + } + }); + + if (this.Components.length === 0) { + throw new Error("Can't create a query without components"); + } + + this.entities = []; + + this.eventDispatcher = new EventDispatcher(); + + // This query is being used by a reactive system + this.reactive = false; + + this.key = queryKey(Components); + + // Fill the query with the existing entities + for (var i = 0; i < manager._entities.length; i++) { + var entity = manager._entities[i]; + if (this.match(entity)) { + // @todo ??? this.addEntity(entity); => preventing the event to be generated + entity.queries.push(this); + this.entities.push(entity); + } + } + } + + /** + * Add entity to this query + * @param {Entity} entity + */ + addEntity(entity) { + entity.queries.push(this); + this.entities.push(entity); + + this.eventDispatcher.dispatchEvent(Query.prototype.ENTITY_ADDED, entity); + } + + /** + * Remove entity from this query + * @param {Entity} entity + */ + removeEntity(entity) { + let index = this.entities.indexOf(entity); + if (~index) { + this.entities.splice(index, 1); + + index = entity.queries.indexOf(this); + entity.queries.splice(index, 1); + + this.eventDispatcher.dispatchEvent( + Query.prototype.ENTITY_REMOVED, + entity + ); + } + } + + match(entity) { + return ( + entity.hasAllComponents(this.Components) && + !entity.hasAnyComponents(this.NotComponents) + ); + } + + toJSON() { + return { + key: this.key, + reactive: this.reactive, + components: { + included: this.Components.map((C) => C.name), + not: this.NotComponents.map((C) => C.name), + }, + numEntities: this.entities.length, + }; + } + + /** + * Return stats for this query + */ + stats() { + return { + numComponents: this.Components.length, + numEntities: this.entities.length, + }; + } +} + +Query.prototype.ENTITY_ADDED = "Query#ENTITY_ADDED"; +Query.prototype.ENTITY_REMOVED = "Query#ENTITY_REMOVED"; +Query.prototype.COMPONENT_CHANGED = "Query#COMPONENT_CHANGED"; + +/** + * @private + * @class QueryManager + */ +class QueryManager { + constructor(world) { + this._world = world; + + // Queries indexed by a unique identifier for the components it has + this._queries = {}; + } + + onEntityRemoved(entity) { + for (var queryName in this._queries) { + var query = this._queries[queryName]; + if (entity.queries.indexOf(query) !== -1) { + query.removeEntity(entity); + } + } + } + + /** + * Callback when a component is added to an entity + * @param {Entity} entity Entity that just got the new component + * @param {Component} Component Component added to the entity + */ + onEntityComponentAdded(entity, Component) { + // @todo Use bitmask for checking components? + + // Check each indexed query to see if we need to add this entity to the list + for (var queryName in this._queries) { + var query = this._queries[queryName]; + + if ( + !!~query.NotComponents.indexOf(Component) && + ~query.entities.indexOf(entity) + ) { + query.removeEntity(entity); + continue; + } + + // Add the entity only if: + // Component is in the query + // and Entity has ALL the components of the query + // and Entity is not already in the query + if ( + !~query.Components.indexOf(Component) || + !query.match(entity) || + ~query.entities.indexOf(entity) + ) + continue; + + query.addEntity(entity); + } + } + + /** + * Callback when a component is removed from an entity + * @param {Entity} entity Entity to remove the component from + * @param {Component} Component Component to remove from the entity + */ + onEntityComponentRemoved(entity, Component) { + for (var queryName in this._queries) { + var query = this._queries[queryName]; + + if ( + !!~query.NotComponents.indexOf(Component) && + !~query.entities.indexOf(entity) && + query.match(entity) + ) { + query.addEntity(entity); + continue; + } + + if ( + !!~query.Components.indexOf(Component) && + !!~query.entities.indexOf(entity) && + !query.match(entity) + ) { + query.removeEntity(entity); + continue; + } + } + } + + /** + * Get a query for the specified components + * @param {Component} Components Components that the query should have + */ + getQuery(Components) { + var key = queryKey(Components); + var query = this._queries[key]; + if (!query) { + this._queries[key] = query = new Query(Components, this._world); + } + return query; + } + + /** + * Return some stats from this class + */ + stats() { + var stats = {}; + for (var queryName in this._queries) { + stats[queryName] = this._queries[queryName].stats(); + } + return stats; + } +} + +class Component { + constructor(props) { + if (props !== false) { + const schema = this.constructor.schema; + + for (const key in schema) { + if (props && props.hasOwnProperty(key)) { + this[key] = props[key]; + } else { + const schemaProp = schema[key]; + if (schemaProp.hasOwnProperty("default")) { + this[key] = schemaProp.type.clone(schemaProp.default); + } else { + const type = schemaProp.type; + this[key] = type.clone(type.default); + } + } + } + + if ( props !== undefined) { + this.checkUndefinedAttributes(props); + } + } + + this._pool = null; + } + + copy(source) { + const schema = this.constructor.schema; + + for (const key in schema) { + const prop = schema[key]; + + if (source.hasOwnProperty(key)) { + this[key] = prop.type.copy(source[key], this[key]); + } + } + + // @DEBUG + { + this.checkUndefinedAttributes(source); + } + + return this; + } + + clone() { + return new this.constructor().copy(this); + } + + reset() { + const schema = this.constructor.schema; + + for (const key in schema) { + const schemaProp = schema[key]; + + if (schemaProp.hasOwnProperty("default")) { + this[key] = schemaProp.type.copy(schemaProp.default, this[key]); + } else { + const type = schemaProp.type; + this[key] = type.copy(type.default, this[key]); + } + } + } + + dispose() { + if (this._pool) { + this._pool.release(this); + } + } + + getName() { + return this.constructor.getName(); + } + + checkUndefinedAttributes(src) { + const schema = this.constructor.schema; + + // Check that the attributes defined in source are also defined in the schema + Object.keys(src).forEach((srcKey) => { + if (!schema.hasOwnProperty(srcKey)) { + console.warn( + `Trying to set attribute '${srcKey}' not defined in the '${this.constructor.name}' schema. Please fix the schema, the attribute value won't be set` + ); + } + }); + } +} + +Component.schema = {}; +Component.isComponent = true; +Component.getName = function () { + return this.displayName || this.name; +}; + +class SystemStateComponent extends Component {} + +SystemStateComponent.isSystemStateComponent = true; + +class EntityPool extends ObjectPool { + constructor(entityManager, entityClass, initialSize) { + super(entityClass, undefined); + this.entityManager = entityManager; + + if (typeof initialSize !== "undefined") { + this.expand(initialSize); + } + } + + expand(count) { + for (var n = 0; n < count; n++) { + var clone = new this.T(this.entityManager); + clone._pool = this; + this.freeList.push(clone); + } + this.count += count; + } +} + +/** + * @private + * @class EntityManager + */ +class EntityManager { + constructor(world) { + this.world = world; + this.componentsManager = world.componentsManager; + + // All the entities in this instance + this._entities = []; + this._nextEntityId = 0; + + this._entitiesByNames = {}; + + this._queryManager = new QueryManager(this); + this.eventDispatcher = new EventDispatcher(); + this._entityPool = new EntityPool( + this, + this.world.options.entityClass, + this.world.options.entityPoolSize + ); + + // Deferred deletion + this.entitiesWithComponentsToRemove = []; + this.entitiesToRemove = []; + this.deferredRemovalEnabled = true; + } + + getEntityByName(name) { + return this._entitiesByNames[name]; + } + + /** + * Create a new entity + */ + createEntity(name) { + var entity = this._entityPool.acquire(); + entity.alive = true; + entity.name = name || ""; + if (name) { + if (this._entitiesByNames[name]) { + console.warn(`Entity name '${name}' already exist`); + } else { + this._entitiesByNames[name] = entity; + } + } + + this._entities.push(entity); + this.eventDispatcher.dispatchEvent(ENTITY_CREATED, entity); + return entity; + } + + // COMPONENTS + + /** + * Add a component to an entity + * @param {Entity} entity Entity where the component will be added + * @param {Component} Component Component to be added to the entity + * @param {Object} values Optional values to replace the default attributes + */ + entityAddComponent(entity, Component, values) { + // @todo Probably define Component._typeId with a default value and avoid using typeof + if ( + typeof Component._typeId === "undefined" && + !this.world.componentsManager._ComponentsMap[Component._typeId] + ) { + throw new Error( + `Attempted to add unregistered component "${Component.getName()}"` + ); + } + + if (~entity._ComponentTypes.indexOf(Component)) { + { + console.warn( + "Component type already exists on entity.", + entity, + Component.getName() + ); + } + return; + } + + entity._ComponentTypes.push(Component); + + if (Component.__proto__ === SystemStateComponent) { + entity.numStateComponents++; + } + + var componentPool = this.world.componentsManager.getComponentsPool( + Component + ); + + var component = componentPool + ? componentPool.acquire() + : new Component(values); + + if (componentPool && values) { + component.copy(values); + } + + entity._components[Component._typeId] = component; + + this._queryManager.onEntityComponentAdded(entity, Component); + this.world.componentsManager.componentAddedToEntity(Component); + + this.eventDispatcher.dispatchEvent(COMPONENT_ADDED, entity, Component); + } + + /** + * Remove a component from an entity + * @param {Entity} entity Entity which will get removed the component + * @param {*} Component Component to remove from the entity + * @param {Bool} immediately If you want to remove the component immediately instead of deferred (Default is false) + */ + entityRemoveComponent(entity, Component, immediately) { + var index = entity._ComponentTypes.indexOf(Component); + if (!~index) return; + + this.eventDispatcher.dispatchEvent(COMPONENT_REMOVE, entity, Component); + + if (immediately) { + this._entityRemoveComponentSync(entity, Component, index); + } else { + if (entity._ComponentTypesToRemove.length === 0) + this.entitiesWithComponentsToRemove.push(entity); + + entity._ComponentTypes.splice(index, 1); + entity._ComponentTypesToRemove.push(Component); + + entity._componentsToRemove[Component._typeId] = + entity._components[Component._typeId]; + delete entity._components[Component._typeId]; + } + + // Check each indexed query to see if we need to remove it + this._queryManager.onEntityComponentRemoved(entity, Component); + + if (Component.__proto__ === SystemStateComponent) { + entity.numStateComponents--; + + // Check if the entity was a ghost waiting for the last system state component to be removed + if (entity.numStateComponents === 0 && !entity.alive) { + entity.remove(); + } + } + } + + _entityRemoveComponentSync(entity, Component, index) { + // Remove T listing on entity and property ref, then free the component. + entity._ComponentTypes.splice(index, 1); + var component = entity._components[Component._typeId]; + delete entity._components[Component._typeId]; + component.dispose(); + this.world.componentsManager.componentRemovedFromEntity(Component); + } + + /** + * Remove all the components from an entity + * @param {Entity} entity Entity from which the components will be removed + */ + entityRemoveAllComponents(entity, immediately) { + let Components = entity._ComponentTypes; + + for (let j = Components.length - 1; j >= 0; j--) { + if (Components[j].__proto__ !== SystemStateComponent) + this.entityRemoveComponent(entity, Components[j], immediately); + } + } + + /** + * Remove the entity from this manager. It will clear also its components + * @param {Entity} entity Entity to remove from the manager + * @param {Bool} immediately If you want to remove the component immediately instead of deferred (Default is false) + */ + removeEntity(entity, immediately) { + var index = this._entities.indexOf(entity); + + if (!~index) throw new Error("Tried to remove entity not in list"); + + entity.alive = false; + this.entityRemoveAllComponents(entity, immediately); + + if (entity.numStateComponents === 0) { + // Remove from entity list + this.eventDispatcher.dispatchEvent(ENTITY_REMOVED, entity); + this._queryManager.onEntityRemoved(entity); + if (immediately === true) { + this._releaseEntity(entity, index); + } else { + this.entitiesToRemove.push(entity); + } + } + } + + _releaseEntity(entity, index) { + this._entities.splice(index, 1); + + if (this._entitiesByNames[entity.name]) { + delete this._entitiesByNames[entity.name]; + } + entity._pool.release(entity); + } + + /** + * Remove all entities from this manager + */ + removeAllEntities() { + for (var i = this._entities.length - 1; i >= 0; i--) { + this.removeEntity(this._entities[i]); + } + } + + processDeferredRemoval() { + if (!this.deferredRemovalEnabled) { + return; + } + + for (let i = 0; i < this.entitiesToRemove.length; i++) { + let entity = this.entitiesToRemove[i]; + let index = this._entities.indexOf(entity); + this._releaseEntity(entity, index); + } + this.entitiesToRemove.length = 0; + + for (let i = 0; i < this.entitiesWithComponentsToRemove.length; i++) { + let entity = this.entitiesWithComponentsToRemove[i]; + while (entity._ComponentTypesToRemove.length > 0) { + let Component = entity._ComponentTypesToRemove.pop(); + + var component = entity._componentsToRemove[Component._typeId]; + delete entity._componentsToRemove[Component._typeId]; + component.dispose(); + this.world.componentsManager.componentRemovedFromEntity(Component); + + //this._entityRemoveComponentSync(entity, Component, index); + } + } + + this.entitiesWithComponentsToRemove.length = 0; + } + + /** + * Get a query based on a list of components + * @param {Array(Component)} Components List of components that will form the query + */ + queryComponents(Components) { + return this._queryManager.getQuery(Components); + } + + // EXTRAS + + /** + * Return number of entities + */ + count() { + return this._entities.length; + } + + /** + * Return some stats + */ + stats() { + var stats = { + numEntities: this._entities.length, + numQueries: Object.keys(this._queryManager._queries).length, + queries: this._queryManager.stats(), + numComponentPool: Object.keys(this.componentsManager._componentPool) + .length, + componentPool: {}, + eventDispatcher: this.eventDispatcher.stats, + }; + + for (var ecsyComponentId in this.componentsManager._componentPool) { + var pool = this.componentsManager._componentPool[ecsyComponentId]; + stats.componentPool[pool.T.getName()] = { + used: pool.totalUsed(), + size: pool.count, + }; + } + + return stats; + } +} + +const ENTITY_CREATED = "EntityManager#ENTITY_CREATE"; +const ENTITY_REMOVED = "EntityManager#ENTITY_REMOVED"; +const COMPONENT_ADDED = "EntityManager#COMPONENT_ADDED"; +const COMPONENT_REMOVE = "EntityManager#COMPONENT_REMOVE"; + +class ComponentManager { + constructor() { + this.Components = []; + this._ComponentsMap = {}; + + this._componentPool = {}; + this.numComponents = {}; + this.nextComponentId = 0; + } + + hasComponent(Component) { + return this.Components.indexOf(Component) !== -1; + } + + registerComponent(Component, objectPool) { + if (this.Components.indexOf(Component) !== -1) { + console.warn( + `Component type: '${Component.getName()}' already registered.` + ); + return; + } + + const schema = Component.schema; + + if (!schema) { + throw new Error( + `Component "${Component.getName()}" has no schema property.` + ); + } + + for (const propName in schema) { + const prop = schema[propName]; + + if (!prop.type) { + throw new Error( + `Invalid schema for component "${Component.getName()}". Missing type for "${propName}" property.` + ); + } + } + + Component._typeId = this.nextComponentId++; + this.Components.push(Component); + this._ComponentsMap[Component._typeId] = Component; + this.numComponents[Component._typeId] = 0; + + if (objectPool === undefined) { + objectPool = new ObjectPool(Component); + } else if (objectPool === false) { + objectPool = undefined; + } + + this._componentPool[Component._typeId] = objectPool; + } + + componentAddedToEntity(Component) { + this.numComponents[Component._typeId]++; + } + + componentRemovedFromEntity(Component) { + this.numComponents[Component._typeId]--; + } + + getComponentsPool(Component) { + return this._componentPool[Component._typeId]; + } +} + +const Version = "0.3.1"; + +const proxyMap = new WeakMap(); + +const proxyHandler = { + set(target, prop) { + throw new Error( + `Tried to write to "${target.constructor.getName()}#${String( + prop + )}" on immutable component. Use .getMutableComponent() to modify a component.` + ); + }, +}; + +function wrapImmutableComponent(T, component) { + if (component === undefined) { + return undefined; + } + + let wrappedComponent = proxyMap.get(component); + + if (!wrappedComponent) { + wrappedComponent = new Proxy(component, proxyHandler); + proxyMap.set(component, wrappedComponent); + } + + return wrappedComponent; +} + +class Entity { + constructor(entityManager) { + this._entityManager = entityManager || null; + + // Unique ID for this entity + this.id = entityManager._nextEntityId++; + + // List of components types the entity has + this._ComponentTypes = []; + + // Instance of the components + this._components = {}; + + this._componentsToRemove = {}; + + // Queries where the entity is added + this.queries = []; + + // Used for deferred removal + this._ComponentTypesToRemove = []; + + this.alive = false; + + //if there are state components on a entity, it can't be removed completely + this.numStateComponents = 0; + } + + // COMPONENTS + + getComponent(Component, includeRemoved) { + var component = this._components[Component._typeId]; + + if (!component && includeRemoved === true) { + component = this._componentsToRemove[Component._typeId]; + } + + return wrapImmutableComponent(Component, component) + ; + } + + getRemovedComponent(Component) { + const component = this._componentsToRemove[Component._typeId]; + + return wrapImmutableComponent(Component, component) + ; + } + + getComponents() { + return this._components; + } + + getComponentsToRemove() { + return this._componentsToRemove; + } + + getComponentTypes() { + return this._ComponentTypes; + } + + getMutableComponent(Component) { + var component = this._components[Component._typeId]; + + if (!component) { + return; + } + + for (var i = 0; i < this.queries.length; i++) { + var query = this.queries[i]; + // @todo accelerate this check. Maybe having query._Components as an object + // @todo add Not components + if (query.reactive && query.Components.indexOf(Component) !== -1) { + query.eventDispatcher.dispatchEvent( + Query.prototype.COMPONENT_CHANGED, + this, + component + ); + } + } + return component; + } + + addComponent(Component, values) { + this._entityManager.entityAddComponent(this, Component, values); + return this; + } + + removeComponent(Component, forceImmediate) { + this._entityManager.entityRemoveComponent(this, Component, forceImmediate); + return this; + } + + hasComponent(Component, includeRemoved) { + return ( + !!~this._ComponentTypes.indexOf(Component) || + (includeRemoved === true && this.hasRemovedComponent(Component)) + ); + } + + hasRemovedComponent(Component) { + return !!~this._ComponentTypesToRemove.indexOf(Component); + } + + hasAllComponents(Components) { + for (var i = 0; i < Components.length; i++) { + if (!this.hasComponent(Components[i])) return false; + } + return true; + } + + hasAnyComponents(Components) { + for (var i = 0; i < Components.length; i++) { + if (this.hasComponent(Components[i])) return true; + } + return false; + } + + removeAllComponents(forceImmediate) { + return this._entityManager.entityRemoveAllComponents(this, forceImmediate); + } + + copy(src) { + // TODO: This can definitely be optimized + for (var ecsyComponentId in src._components) { + var srcComponent = src._components[ecsyComponentId]; + this.addComponent(srcComponent.constructor); + var component = this.getComponent(srcComponent.constructor); + component.copy(srcComponent); + } + + return this; + } + + clone() { + return new Entity(this._entityManager).copy(this); + } + + reset() { + this.id = this._entityManager._nextEntityId++; + this._ComponentTypes.length = 0; + this.queries.length = 0; + + for (var ecsyComponentId in this._components) { + delete this._components[ecsyComponentId]; + } + } + + remove(forceImmediate) { + return this._entityManager.removeEntity(this, forceImmediate); + } +} + +const DEFAULT_OPTIONS = { + entityPoolSize: 0, + entityClass: Entity, +}; + +class World { + constructor(options = {}) { + this.options = Object.assign({}, DEFAULT_OPTIONS, options); + + this.componentsManager = new ComponentManager(this); + this.entityManager = new EntityManager(this); + this.systemManager = new SystemManager(this); + + this.enabled = true; + + this.eventQueues = {}; + + if (hasWindow && typeof CustomEvent !== "undefined") { + var event = new CustomEvent("ecsy-world-created", { + detail: { world: this, version: Version }, + }); + window.dispatchEvent(event); + } + + this.lastTime = now() / 1000; + } + + registerComponent(Component, objectPool) { + this.componentsManager.registerComponent(Component, objectPool); + return this; + } + + registerSystem(System, attributes) { + this.systemManager.registerSystem(System, attributes); + return this; + } + + hasRegisteredComponent(Component) { + return this.componentsManager.hasComponent(Component); + } + + unregisterSystem(System) { + this.systemManager.unregisterSystem(System); + return this; + } + + getSystem(SystemClass) { + return this.systemManager.getSystem(SystemClass); + } + + getSystems() { + return this.systemManager.getSystems(); + } + + execute(delta, time) { + if (!delta) { + time = now() / 1000; + delta = time - this.lastTime; + this.lastTime = time; + } + + if (this.enabled) { + this.systemManager.execute(delta, time); + this.entityManager.processDeferredRemoval(); + } + } + + stop() { + this.enabled = false; + } + + play() { + this.enabled = true; + } + + createEntity(name) { + return this.entityManager.createEntity(name); + } + + stats() { + var stats = { + entities: this.entityManager.stats(), + system: this.systemManager.stats(), + }; + + return stats; + } +} + +class System { + canExecute() { + if (this._mandatoryQueries.length === 0) return true; + + for (let i = 0; i < this._mandatoryQueries.length; i++) { + var query = this._mandatoryQueries[i]; + if (query.entities.length === 0) { + return false; + } + } + + return true; + } + + getName() { + return this.constructor.getName(); + } + + constructor(world, attributes) { + this.world = world; + this.enabled = true; + + // @todo Better naming :) + this._queries = {}; + this.queries = {}; + + this.priority = 0; + + // Used for stats + this.executeTime = 0; + + if (attributes && attributes.priority) { + this.priority = attributes.priority; + } + + this._mandatoryQueries = []; + + this.initialized = true; + + if (this.constructor.queries) { + for (var queryName in this.constructor.queries) { + var queryConfig = this.constructor.queries[queryName]; + var Components = queryConfig.components; + if (!Components || Components.length === 0) { + throw new Error("'components' attribute can't be empty in a query"); + } + + // Detect if the components have already been registered + let unregisteredComponents = Components.filter( + (Component) => !componentRegistered(Component) + ); + + if (unregisteredComponents.length > 0) { + throw new Error( + `Tried to create a query '${ + this.constructor.name + }.${queryName}' with unregistered components: [${unregisteredComponents + .map((c) => c.getName()) + .join(", ")}]` + ); + } + + var query = this.world.entityManager.queryComponents(Components); + + this._queries[queryName] = query; + if (queryConfig.mandatory === true) { + this._mandatoryQueries.push(query); + } + this.queries[queryName] = { + results: query.entities, + }; + + // Reactive configuration added/removed/changed + var validEvents = ["added", "removed", "changed"]; + + const eventMapping = { + added: Query.prototype.ENTITY_ADDED, + removed: Query.prototype.ENTITY_REMOVED, + changed: Query.prototype.COMPONENT_CHANGED, // Query.prototype.ENTITY_CHANGED + }; + + if (queryConfig.listen) { + validEvents.forEach((eventName) => { + if (!this.execute) { + console.warn( + `System '${this.getName()}' has defined listen events (${validEvents.join( + ", " + )}) for query '${queryName}' but it does not implement the 'execute' method.` + ); + } + + // Is the event enabled on this system's query? + if (queryConfig.listen[eventName]) { + let event = queryConfig.listen[eventName]; + + if (eventName === "changed") { + query.reactive = true; + if (event === true) { + // Any change on the entity from the components in the query + let eventList = (this.queries[queryName][eventName] = []); + query.eventDispatcher.addEventListener( + Query.prototype.COMPONENT_CHANGED, + (entity) => { + // Avoid duplicates + if (eventList.indexOf(entity) === -1) { + eventList.push(entity); + } + } + ); + } else if (Array.isArray(event)) { + let eventList = (this.queries[queryName][eventName] = []); + query.eventDispatcher.addEventListener( + Query.prototype.COMPONENT_CHANGED, + (entity, changedComponent) => { + // Avoid duplicates + if ( + event.indexOf(changedComponent.constructor) !== -1 && + eventList.indexOf(entity) === -1 + ) { + eventList.push(entity); + } + } + ); + } + } else { + let eventList = (this.queries[queryName][eventName] = []); + + query.eventDispatcher.addEventListener( + eventMapping[eventName], + (entity) => { + // @fixme overhead? + if (eventList.indexOf(entity) === -1) + eventList.push(entity); + } + ); + } + } + }); + } + } + } + } + + stop() { + this.executeTime = 0; + this.enabled = false; + } + + play() { + this.enabled = true; + } + + // @question rename to clear queues? + clearEvents() { + for (let queryName in this.queries) { + var query = this.queries[queryName]; + if (query.added) { + query.added.length = 0; + } + if (query.removed) { + query.removed.length = 0; + } + if (query.changed) { + if (Array.isArray(query.changed)) { + query.changed.length = 0; + } else { + for (let name in query.changed) { + query.changed[name].length = 0; + } + } + } + } + } + + toJSON() { + var json = { + name: this.getName(), + enabled: this.enabled, + executeTime: this.executeTime, + priority: this.priority, + queries: {}, + }; + + if (this.constructor.queries) { + var queries = this.constructor.queries; + for (let queryName in queries) { + let query = this.queries[queryName]; + let queryDefinition = queries[queryName]; + let jsonQuery = (json.queries[queryName] = { + key: this._queries[queryName].key, + }); + + jsonQuery.mandatory = queryDefinition.mandatory === true; + jsonQuery.reactive = + queryDefinition.listen && + (queryDefinition.listen.added === true || + queryDefinition.listen.removed === true || + queryDefinition.listen.changed === true || + Array.isArray(queryDefinition.listen.changed)); + + if (jsonQuery.reactive) { + jsonQuery.listen = {}; + + const methods = ["added", "removed", "changed"]; + methods.forEach((method) => { + if (query[method]) { + jsonQuery.listen[method] = { + entities: query[method].length, + }; + } + }); + } + } + } + + return json; + } +} + +System.isSystem = true; +System.getName = function () { + return this.displayName || this.name; +}; + +function Not(Component) { + return { + operator: "not", + Component: Component, + }; +} + +class TagComponent extends Component { + constructor() { + super(false); + } +} + +TagComponent.isTagComponent = true; + +const copyValue = (src) => src; + +const cloneValue = (src) => src; + +const copyArray = (src, dest) => { + if (!src) { + return src; + } + + if (!dest) { + return src.slice(); + } + + dest.length = 0; + + for (let i = 0; i < src.length; i++) { + dest.push(src[i]); + } + + return dest; +}; + +const cloneArray = (src) => src && src.slice(); + +const copyJSON = (src) => JSON.parse(JSON.stringify(src)); + +const cloneJSON = (src) => JSON.parse(JSON.stringify(src)); + +const copyCopyable = (src, dest) => { + if (!src) { + return src; + } + + if (!dest) { + return src.clone(); + } + + return dest.copy(src); +}; + +const cloneClonable = (src) => src && src.clone(); + +function createType(typeDefinition) { + var mandatoryProperties = ["name", "default", "copy", "clone"]; + + var undefinedProperties = mandatoryProperties.filter((p) => { + return !typeDefinition.hasOwnProperty(p); + }); + + if (undefinedProperties.length > 0) { + throw new Error( + `createType expects a type definition with the following properties: ${undefinedProperties.join( + ", " + )}` + ); + } + + typeDefinition.isType = true; + + return typeDefinition; +} + +/** + * Standard types + */ +const Types = { + Number: createType({ + name: "Number", + default: 0, + copy: copyValue, + clone: cloneValue, + }), + + Boolean: createType({ + name: "Boolean", + default: false, + copy: copyValue, + clone: cloneValue, + }), + + String: createType({ + name: "String", + default: "", + copy: copyValue, + clone: cloneValue, + }), + + Array: createType({ + name: "Array", + default: [], + copy: copyArray, + clone: cloneArray, + }), + + Ref: createType({ + name: "Ref", + default: undefined, + copy: copyValue, + clone: cloneValue, + }), + + JSON: createType({ + name: "JSON", + default: null, + copy: copyJSON, + clone: cloneJSON, + }), +}; + +function generateId(length) { + var result = ""; + var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +function injectScript(src, onLoad) { + var script = document.createElement("script"); + // @todo Use link to the ecsy-devtools repo? + script.src = src; + script.onload = onLoad; + (document.head || document.documentElement).appendChild(script); +} + +/* global Peer */ + +function hookConsoleAndErrors(connection) { + var wrapFunctions = ["error", "warning", "log"]; + wrapFunctions.forEach((key) => { + if (typeof console[key] === "function") { + var fn = console[key].bind(console); + console[key] = (...args) => { + connection.send({ + method: "console", + type: key, + args: JSON.stringify(args), + }); + return fn.apply(null, args); + }; + } + }); + + window.addEventListener("error", (error) => { + connection.send({ + method: "error", + error: JSON.stringify({ + message: error.error.message, + stack: error.error.stack, + }), + }); + }); +} + +function includeRemoteIdHTML(remoteId) { + let infoDiv = document.createElement("div"); + infoDiv.style.cssText = ` + align-items: center; + background-color: #333; + color: #aaa; + display:flex; + font-family: Arial; + font-size: 1.1em; + height: 40px; + justify-content: center; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + text-align: center; + top: 0; + `; + + infoDiv.innerHTML = `Open ECSY devtools to connect to this page using the code: ${remoteId} `; + document.body.appendChild(infoDiv); + + return infoDiv; +} + +function enableRemoteDevtools(remoteId) { + if (!hasWindow) { + console.warn("Remote devtools not available outside the browser"); + return; + } + + window.generateNewCode = () => { + window.localStorage.clear(); + remoteId = generateId(6); + window.localStorage.setItem("ecsyRemoteId", remoteId); + window.location.reload(false); + }; + + remoteId = remoteId || window.localStorage.getItem("ecsyRemoteId"); + if (!remoteId) { + remoteId = generateId(6); + window.localStorage.setItem("ecsyRemoteId", remoteId); + } + + let infoDiv = includeRemoteIdHTML(remoteId); + + window.__ECSY_REMOTE_DEVTOOLS_INJECTED = true; + window.__ECSY_REMOTE_DEVTOOLS = {}; + + let Version = ""; + + // This is used to collect the worlds created before the communication is being established + let worldsBeforeLoading = []; + let onWorldCreated = (e) => { + var world = e.detail.world; + Version = e.detail.version; + worldsBeforeLoading.push(world); + }; + window.addEventListener("ecsy-world-created", onWorldCreated); + + let onLoaded = () => { + // var peer = new Peer(remoteId); + var peer = new Peer(remoteId, { + host: "peerjs.ecsy.io", + secure: true, + port: 443, + config: { + iceServers: [ + { url: "stun:stun.l.google.com:19302" }, + { url: "stun:stun1.l.google.com:19302" }, + { url: "stun:stun2.l.google.com:19302" }, + { url: "stun:stun3.l.google.com:19302" }, + { url: "stun:stun4.l.google.com:19302" }, + ], + }, + debug: 3, + }); + + peer.on("open", (/* id */) => { + peer.on("connection", (connection) => { + window.__ECSY_REMOTE_DEVTOOLS.connection = connection; + connection.on("open", function () { + // infoDiv.style.visibility = "hidden"; + infoDiv.innerHTML = "Connected"; + + // Receive messages + connection.on("data", function (data) { + if (data.type === "init") { + var script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.onload = () => { + script.parentNode.removeChild(script); + + // Once the script is injected we don't need to listen + window.removeEventListener( + "ecsy-world-created", + onWorldCreated + ); + worldsBeforeLoading.forEach((world) => { + var event = new CustomEvent("ecsy-world-created", { + detail: { world: world, version: Version }, + }); + window.dispatchEvent(event); + }); + }; + script.innerHTML = data.script; + (document.head || document.documentElement).appendChild(script); + script.onload(); + + hookConsoleAndErrors(connection); + } else if (data.type === "executeScript") { + let value = eval(data.script); + if (data.returnEval) { + connection.send({ + method: "evalReturn", + value: value, + }); + } + } + }); + }); + }); + }); + }; + + // Inject PeerJS script + injectScript( + "https://cdn.jsdelivr.net/npm/peerjs@0.3.20/dist/peer.min.js", + onLoaded + ); +} + +if (hasWindow) { + const urlParams = new URLSearchParams(window.location.search); + + // @todo Provide a way to disable it if needed + if (urlParams.has("enable-remote-devtools")) { + enableRemoteDevtools(); + } +} + +export { Component, Not, ObjectPool, System, SystemStateComponent, TagComponent, Types, Version, World, Entity as _Entity, cloneArray, cloneClonable, cloneJSON, cloneValue, copyArray, copyCopyable, copyJSON, copyValue, createType, enableRemoteDevtools }; diff --git a/jsm/libs/fflate.module.js b/jsm/libs/fflate.module.js new file mode 100644 index 0000000..808000a --- /dev/null +++ b/jsm/libs/fflate.module.js @@ -0,0 +1,2474 @@ +/*! +fflate - fast JavaScript compression/decompression + +Licensed under MIT. https://github.com/101arrowz/fflate/blob/master/LICENSE +version 0.6.9 +*/ + +// DEFLATE is a complex format; to read this code, you should probably check the RFC first: +// https://tools.ietf.org/html/rfc1951 +// You may also wish to take a look at the guide I made about this program: +// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad +// Some of the following code is similar to that of UZIP.js: +// https://github.com/photopea/UZIP.js +// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size. +// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint +// is better for memory in most engines (I *think*). +var ch2 = {}; +var durl = function (c) { return URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); }; +var cwk = function (u) { return new Worker(u); }; +try { + URL.revokeObjectURL(durl('')); +} +catch (e) { + // We're in Deno or a very old browser + durl = function (c) { return 'data:application/javascript;charset=UTF-8,' + encodeURI(c); }; + // If Deno, this is necessary; if not, this changes nothing + cwk = function (u) { return new Worker(u, { type: 'module' }); }; +} +var wk = (function (c, id, msg, transfer, cb) { + var w = cwk(ch2[id] || (ch2[id] = durl(c))); + w.onerror = function (e) { return cb(e.error, null); }; + w.onmessage = function (e) { return cb(null, e.data); }; + w.postMessage(msg, transfer); + return w; +}); + +// aliases for shorter compressed code (most minifers don't do this) +var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; +// fixed length extra bits +var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); +// fixed distance extra bits +// see fleb note +var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); +// code length index map +var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); +// get base, reverse index map from extra bits +var freb = function (eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; + } + // numbers here are at max 18 bits + var r = new u32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = ((j - b[i]) << 5) | i; + } + } + return [b, r]; +}; +var _a = freb(fleb, 2), fl = _a[0], revfl = _a[1]; +// we can ignore the fact that the other numbers are wrong; they never happen anyway +fl[28] = 258, revfl[258] = 28; +var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1]; +// map of value to reverse (assuming 16 bits) +var rev = new u16(32768); +for (var i = 0; i < 32768; ++i) { + // reverse table algorithm from SO + var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1; +} +// create huffman tree from u8 "map": index -> code length for code index +// mb (max bits) must be at most 15 +// TODO: optimize/split up? +var hMap = (function (cd, mb, r) { + var s = cd.length; + // index + var i = 0; + // u16 "map": index -> # of codes with bit length = index + var l = new u16(mb); + // length of cd must be 288 (total # of codes) + for (; i < s; ++i) + ++l[cd[i] - 1]; + // u16 "map": index -> minimum code for bit length = index + var le = new u16(mb); + for (i = 0; i < mb; ++i) { + le[i] = (le[i - 1] + l[i - 1]) << 1; + } + var co; + if (r) { + // u16 "map": index -> number of actual bits, symbol for code + co = new u16(1 << mb); + // bits to remove for reverser + var rvb = 15 - mb; + for (i = 0; i < s; ++i) { + // ignore 0 lengths + if (cd[i]) { + // num encoding both symbol and bits read + var sv = (i << 4) | cd[i]; + // free bits + var r_1 = mb - cd[i]; + // start value + var v = le[cd[i] - 1]++ << r_1; + // m is end value + for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { + // every 16 bit value starting with the code yields the same result + co[rev[v] >>> rvb] = sv; + } + } + } + } + else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]); + } + } + } + return co; +}); +// fixed length tree +var flt = new u8(288); +for (var i = 0; i < 144; ++i) + flt[i] = 8; +for (var i = 144; i < 256; ++i) + flt[i] = 9; +for (var i = 256; i < 280; ++i) + flt[i] = 7; +for (var i = 280; i < 288; ++i) + flt[i] = 8; +// fixed distance tree +var fdt = new u8(32); +for (var i = 0; i < 32; ++i) + fdt[i] = 5; +// fixed length map +var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1); +// fixed distance map +var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1); +// find max of array +var max = function (a) { + var m = a[0]; + for (var i = 1; i < a.length; ++i) { + if (a[i] > m) + m = a[i]; + } + return m; +}; +// read d, starting at bit p and mask with m +var bits = function (d, p, m) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m; +}; +// read d, starting at bit p continuing for at least 16 bits +var bits16 = function (d, p) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); +}; +// get end of byte +var shft = function (p) { return ((p / 8) | 0) + (p & 7 && 1); }; +// typed array slice - allows garbage collector to free original reference, +// while being more compatible than .slice +var slc = function (v, s, e) { + if (s == null || s < 0) + s = 0; + if (e == null || e > v.length) + e = v.length; + // can't use .constructor in case user-supplied + var n = new (v instanceof u16 ? u16 : v instanceof u32 ? u32 : u8)(e - s); + n.set(v.subarray(s, e)); + return n; +}; +// expands raw DEFLATE data +var inflt = function (dat, buf, st) { + // source length + var sl = dat.length; + if (!sl || (st && !st.l && sl < 5)) + return buf || new u8(0); + // have to estimate size + var noBuf = !buf || st; + // no state + var noSt = !st || st.i; + if (!st) + st = {}; + // Assumes roughly 33% compression ratio average + if (!buf) + buf = new u8(sl * 3); + // ensure buffer can fit at least l elements + var cbuf = function (l) { + var bl = buf.length; + // need to increase size to fit + if (l > bl) { + // Double or set to necessary, whichever is greater + var nbuf = new u8(Math.max(bl * 2, l)); + nbuf.set(buf); + buf = nbuf; + } + }; + // last chunk bitpos bytes + var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; + // total bits + var tbts = sl * 8; + do { + if (!lm) { + // BFINAL - this is only 1 when last chunk is next + st.f = final = bits(dat, pos, 1); + // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman + var type = bits(dat, pos + 1, 3); + pos += 3; + if (!type) { + // go to end of byte boundary + var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; + if (t > sl) { + if (noSt) + throw 'unexpected EOF'; + break; + } + // ensure size + if (noBuf) + cbuf(bt + l); + // Copy over uncompressed data + buf.set(dat.subarray(s, t), bt); + // Get new bitpos, update byte count + st.b = bt += l, st.p = pos = t * 8; + continue; + } + else if (type == 1) + lm = flrm, dm = fdrm, lbt = 9, dbt = 5; + else if (type == 2) { + // literal lengths + var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; + var tl = hLit + bits(dat, pos + 5, 31) + 1; + pos += 14; + // length+distance tree + var ldt = new u8(tl); + // code length tree + var clt = new u8(19); + for (var i = 0; i < hcLen; ++i) { + // use index map to get real code + clt[clim[i]] = bits(dat, pos + i * 3, 7); + } + pos += hcLen * 3; + // code lengths bits + var clb = max(clt), clbmsk = (1 << clb) - 1; + // code lengths map + var clm = hMap(clt, clb, 1); + for (var i = 0; i < tl;) { + var r = clm[bits(dat, pos, clbmsk)]; + // bits read + pos += r & 15; + // symbol + var s = r >>> 4; + // code length to copy + if (s < 16) { + ldt[i++] = s; + } + else { + // copy count + var c = 0, n = 0; + if (s == 16) + n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; + else if (s == 17) + n = 3 + bits(dat, pos, 7), pos += 3; + else if (s == 18) + n = 11 + bits(dat, pos, 127), pos += 7; + while (n--) + ldt[i++] = c; + } + } + // length tree distance tree + var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); + // max length bits + lbt = max(lt); + // max dist bits + dbt = max(dt); + lm = hMap(lt, lbt, 1); + dm = hMap(dt, dbt, 1); + } + else + throw 'invalid block type'; + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + } + // Make sure the buffer can hold this + the largest possible addition + // Maximum chunk size (practically, theoretically infinite) is 2^17; + if (noBuf) + cbuf(bt + 131072); + var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; + var lpos = pos; + for (;; lpos = pos) { + // bits read, code + var c = lm[bits16(dat, pos) & lms], sym = c >>> 4; + pos += c & 15; + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + if (!c) + throw 'invalid length/literal'; + if (sym < 256) + buf[bt++] = sym; + else if (sym == 256) { + lpos = pos, lm = null; + break; + } + else { + var add = sym - 254; + // no extra bits needed if less + if (sym > 264) { + // index + var i = sym - 257, b = fleb[i]; + add = bits(dat, pos, (1 << b) - 1) + fl[i]; + pos += b; + } + // dist + var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4; + if (!d) + throw 'invalid distance'; + pos += d & 15; + var dt = fd[dsym]; + if (dsym > 3) { + var b = fdeb[dsym]; + dt += bits16(dat, pos) & ((1 << b) - 1), pos += b; + } + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + if (noBuf) + cbuf(bt + 131072); + var end = bt + add; + for (; bt < end; bt += 4) { + buf[bt] = buf[bt - dt]; + buf[bt + 1] = buf[bt + 1 - dt]; + buf[bt + 2] = buf[bt + 2 - dt]; + buf[bt + 3] = buf[bt + 3 - dt]; + } + bt = end; + } + } + st.l = lm, st.p = lpos, st.b = bt; + if (lm) + final = 1, st.m = lbt, st.d = dm, st.n = dbt; + } while (!final); + return bt == buf.length ? buf : slc(buf, 0, bt); +}; +// starting at p, write the minimum number of bits that can hold v to d +var wbits = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; +}; +// starting at p, write the minimum number of bits (>8) that can hold v to d +var wbits16 = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; + d[o + 2] |= v >>> 16; +}; +// creates code lengths from a frequency table +var hTree = function (d, mb) { + // Need extra info to make a tree + var t = []; + for (var i = 0; i < d.length; ++i) { + if (d[i]) + t.push({ s: i, f: d[i] }); + } + var s = t.length; + var t2 = t.slice(); + if (!s) + return [et, 0]; + if (s == 1) { + var v = new u8(t[0].s + 1); + v[t[0].s] = 1; + return [v, 1]; + } + t.sort(function (a, b) { return a.f - b.f; }); + // after i2 reaches last ind, will be stopped + // freq must be greater than largest possible number of symbols + t.push({ s: -1, f: 25001 }); + var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; + t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; + // efficient algorithm from UZIP.js + // i0 is lookbehind, i2 is lookahead - after processing two low-freq + // symbols that combined have high freq, will start processing i2 (high-freq, + // non-composite) symbols instead + // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ + while (i1 != s - 1) { + l = t[t[i0].f < t[i2].f ? i0++ : i2++]; + r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; + t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; + } + var maxSym = t2[0].s; + for (var i = 1; i < s; ++i) { + if (t2[i].s > maxSym) + maxSym = t2[i].s; + } + // code lengths + var tr = new u16(maxSym + 1); + // max bits in tree + var mbt = ln(t[i1 - 1], tr, 0); + if (mbt > mb) { + // more algorithms from UZIP.js + // TODO: find out how this code works (debt) + // ind debt + var i = 0, dt = 0; + // left cost + var lft = mbt - mb, cst = 1 << lft; + t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); + for (; i < s; ++i) { + var i2_1 = t2[i].s; + if (tr[i2_1] > mb) { + dt += cst - (1 << (mbt - tr[i2_1])); + tr[i2_1] = mb; + } + else + break; + } + dt >>>= lft; + while (dt > 0) { + var i2_2 = t2[i].s; + if (tr[i2_2] < mb) + dt -= 1 << (mb - tr[i2_2]++ - 1); + else + ++i; + } + for (; i >= 0 && dt; --i) { + var i2_3 = t2[i].s; + if (tr[i2_3] == mb) { + --tr[i2_3]; + ++dt; + } + } + mbt = mb; + } + return [new u8(tr), mbt]; +}; +// get the max length and assign length codes +var ln = function (n, l, d) { + return n.s == -1 + ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) + : (l[n.s] = d); +}; +// length codes generation +var lc = function (c) { + var s = c.length; + // Note that the semicolon was intentional + while (s && !c[--s]) + ; + var cl = new u16(++s); + // ind num streak + var cli = 0, cln = c[0], cls = 1; + var w = function (v) { cl[cli++] = v; }; + for (var i = 1; i <= s; ++i) { + if (c[i] == cln && i != s) + ++cls; + else { + if (!cln && cls > 2) { + for (; cls > 138; cls -= 138) + w(32754); + if (cls > 2) { + w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); + cls = 0; + } + } + else if (cls > 3) { + w(cln), --cls; + for (; cls > 6; cls -= 6) + w(8304); + if (cls > 2) + w(((cls - 3) << 5) | 8208), cls = 0; + } + while (cls--) + w(cln); + cls = 1; + cln = c[i]; + } + } + return [cl.subarray(0, cli), s]; +}; +// calculate the length of output from tree, code lengths +var clen = function (cf, cl) { + var l = 0; + for (var i = 0; i < cl.length; ++i) + l += cf[i] * cl[i]; + return l; +}; +// writes a fixed block +// returns the new bit pos +var wfblk = function (out, pos, dat) { + // no need to write 00 as type: TypedArray defaults to 0 + var s = dat.length; + var o = shft(pos + 2); + out[o] = s & 255; + out[o + 1] = s >>> 8; + out[o + 2] = out[o] ^ 255; + out[o + 3] = out[o + 1] ^ 255; + for (var i = 0; i < s; ++i) + out[o + i + 4] = dat[i]; + return (o + 4 + s) * 8; +}; +// writes a block +var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { + wbits(out, p++, final); + ++lf[256]; + var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1]; + var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1]; + var _c = lc(dlt), lclt = _c[0], nlc = _c[1]; + var _d = lc(ddt), lcdt = _d[0], ndc = _d[1]; + var lcfreq = new u16(19); + for (var i = 0; i < lclt.length; ++i) + lcfreq[lclt[i] & 31]++; + for (var i = 0; i < lcdt.length; ++i) + lcfreq[lcdt[i] & 31]++; + var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1]; + var nlcc = 19; + for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) + ; + var flen = (bl + 5) << 3; + var ftlen = clen(lf, flt) + clen(df, fdt) + eb; + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]); + if (flen <= ftlen && flen <= dtlen) + return wfblk(out, p, dat.subarray(bs, bs + bl)); + var lm, ll, dm, dl; + wbits(out, p, 1 + (dtlen < ftlen)), p += 2; + if (dtlen < ftlen) { + lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; + var llm = hMap(lct, mlcb, 0); + wbits(out, p, nlc - 257); + wbits(out, p + 5, ndc - 1); + wbits(out, p + 10, nlcc - 4); + p += 14; + for (var i = 0; i < nlcc; ++i) + wbits(out, p + 3 * i, lct[clim[i]]); + p += 3 * nlcc; + var lcts = [lclt, lcdt]; + for (var it = 0; it < 2; ++it) { + var clct = lcts[it]; + for (var i = 0; i < clct.length; ++i) { + var len = clct[i] & 31; + wbits(out, p, llm[len]), p += lct[len]; + if (len > 15) + wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12; + } + } + } + else { + lm = flm, ll = flt, dm = fdm, dl = fdt; + } + for (var i = 0; i < li; ++i) { + if (syms[i] > 255) { + var len = (syms[i] >>> 18) & 31; + wbits16(out, p, lm[len + 257]), p += ll[len + 257]; + if (len > 7) + wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len]; + var dst = syms[i] & 31; + wbits16(out, p, dm[dst]), p += dl[dst]; + if (dst > 3) + wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst]; + } + else { + wbits16(out, p, lm[syms[i]]), p += ll[syms[i]]; + } + } + wbits16(out, p, lm[256]); + return p + ll[256]; +}; +// deflate options (nice << 13) | chain +var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); +// empty +var et = /*#__PURE__*/ new u8(0); +// compresses data into a raw DEFLATE buffer +var dflt = function (dat, lvl, plvl, pre, post, lst) { + var s = dat.length; + var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); + // writing to this writes to the output buffer + var w = o.subarray(pre, o.length - post); + var pos = 0; + if (!lvl || s < 8) { + for (var i = 0; i <= s; i += 65535) { + // end + var e = i + 65535; + if (e < s) { + // write full block + pos = wfblk(w, pos, dat.subarray(i, e)); + } + else { + // write final block + w[i] = lst; + pos = wfblk(w, pos, dat.subarray(i, s)); + } + } + } + else { + var opt = deo[lvl - 1]; + var n = opt >>> 13, c = opt & 8191; + var msk_1 = (1 << plvl) - 1; + // prev 2-byte val map curr 2-byte val map + var prev = new u16(32768), head = new u16(msk_1 + 1); + var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; + var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; + // 24576 is an arbitrary number of maximum symbols per block + // 424 buffer for last block + var syms = new u32(25000); + // length/literal freq distance freq + var lf = new u16(288), df = new u16(32); + // l/lcnt exbits index l/lind waitdx bitpos + var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0; + for (; i < s; ++i) { + // hash value + // deopt when i > s - 3 - at end, deopt acceptable + var hv = hsh(i); + // index mod 32768 previous index mod + var imod = i & 32767, pimod = head[hv]; + prev[imod] = pimod; + head[hv] = imod; + // We always should modify head and prev, but only add symbols if + // this data is not yet processed ("wait" for wait index) + if (wi <= i) { + // bytes remaining + var rem = s - i; + if ((lc_1 > 7000 || li > 24576) && rem > 423) { + pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); + li = lc_1 = eb = 0, bs = i; + for (var j = 0; j < 286; ++j) + lf[j] = 0; + for (var j = 0; j < 30; ++j) + df[j] = 0; + } + // len dist chain + var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767; + if (rem > 2 && hv == hsh(i - dif)) { + var maxn = Math.min(n, rem) - 1; + var maxd = Math.min(32767, i); + // max possible length + // not capped at dif because decompressors implement "rolling" index population + var ml = Math.min(258, rem); + while (dif <= maxd && --ch_1 && imod != pimod) { + if (dat[i + l] == dat[i + l - dif]) { + var nl = 0; + for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) + ; + if (nl > l) { + l = nl, d = dif; + // break out early when we reach "nice" (we are satisfied enough) + if (nl > maxn) + break; + // now, find the rarest 2-byte sequence within this + // length of literals and search for that instead. + // Much faster than just using the start + var mmd = Math.min(dif, nl - 2); + var md = 0; + for (var j = 0; j < mmd; ++j) { + var ti = (i - dif + j + 32768) & 32767; + var pti = prev[ti]; + var cd = (ti - pti + 32768) & 32767; + if (cd > md) + md = cd, pimod = ti; + } + } + } + // check the previous match + imod = pimod, pimod = prev[imod]; + dif += (imod - pimod + 32768) & 32767; + } + } + // d will be nonzero only when a match was found + if (d) { + // store both dist and len data in one Uint32 + // Make sure this is recognized as a len/dist with 28th bit (2^28) + syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; + var lin = revfl[l] & 31, din = revfd[d] & 31; + eb += fleb[lin] + fdeb[din]; + ++lf[257 + lin]; + ++df[din]; + wi = i + l; + ++lc_1; + } + else { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + } + } + pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); + // this is the easiest way to avoid needing to maintain state + if (!lst && pos & 7) + pos = wfblk(w, pos + 1, et); + } + return slc(o, 0, pre + shft(pos) + post); +}; +// CRC32 table +var crct = /*#__PURE__*/ (function () { + var t = new u32(256); + for (var i = 0; i < 256; ++i) { + var c = i, k = 9; + while (--k) + c = ((c & 1) && 0xEDB88320) ^ (c >>> 1); + t[i] = c; + } + return t; +})(); +// CRC32 +var crc = function () { + var c = -1; + return { + p: function (d) { + // closures have awful performance + var cr = c; + for (var i = 0; i < d.length; ++i) + cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8); + c = cr; + }, + d: function () { return ~c; } + }; +}; +// Alder32 +var adler = function () { + var a = 1, b = 0; + return { + p: function (d) { + // closures have awful performance + var n = a, m = b; + var l = d.length; + for (var i = 0; i != l;) { + var e = Math.min(i + 2655, l); + for (; i < e; ++i) + m += n += d[i]; + n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); + } + a = n, b = m; + }, + d: function () { + a %= 65521, b %= 65521; + return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8); + } + }; +}; +; +// deflate with opts +var dopt = function (dat, opt, pre, post, st) { + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st); +}; +// Walmart object spread +var mrg = function (a, b) { + var o = {}; + for (var k in a) + o[k] = a[k]; + for (var k in b) + o[k] = b[k]; + return o; +}; +// worker clone +// This is possibly the craziest part of the entire codebase, despite how simple it may seem. +// The only parameter to this function is a closure that returns an array of variables outside of the function scope. +// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization. +// We will return an object mapping of true variable name to value (basically, the current scope as a JS object). +// The reason we can't just use the original variable names is minifiers mangling the toplevel scope. +// This took me three weeks to figure out how to do. +var wcln = function (fn, fnStr, td) { + var dt = fn(); + var st = fn.toString(); + var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/ /g, '').split(','); + for (var i = 0; i < dt.length; ++i) { + var v = dt[i], k = ks[i]; + if (typeof v == 'function') { + fnStr += ';' + k + '='; + var st_1 = v.toString(); + if (v.prototype) { + // for global objects + if (st_1.indexOf('[native code]') != -1) { + var spInd = st_1.indexOf(' ', 8) + 1; + fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd)); + } + else { + fnStr += st_1; + for (var t in v.prototype) + fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString(); + } + } + else + fnStr += st_1; + } + else + td[k] = v; + } + return [fnStr, td]; +}; +var ch = []; +// clone bufs +var cbfs = function (v) { + var tl = []; + for (var k in v) { + if (v[k] instanceof u8 || v[k] instanceof u16 || v[k] instanceof u32) + tl.push((v[k] = new v[k].constructor(v[k])).buffer); + } + return tl; +}; +// use a worker to execute code +var wrkr = function (fns, init, id, cb) { + var _a; + if (!ch[id]) { + var fnStr = '', td_1 = {}, m = fns.length - 1; + for (var i = 0; i < m; ++i) + _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1]; + ch[id] = wcln(fns[m], fnStr, td_1); + } + var td = mrg({}, ch[id][1]); + return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); +}; +// base async inflate fn +var bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, hMap, max, bits, bits16, shft, slc, inflt, inflateSync, pbf, gu8]; }; +var bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; +// gzip extra +var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; }; +// gunzip extra +var guze = function () { return [gzs, gzl]; }; +// zlib extra +var zle = function () { return [zlh, wbytes, adler]; }; +// unzlib extra +var zule = function () { return [zlv]; }; +// post buf +var pbf = function (msg) { return postMessage(msg, [msg.buffer]); }; +// get u8 +var gu8 = function (o) { return o && o.size && new u8(o.size); }; +// async helper +var cbify = function (dat, opts, fns, init, id, cb) { + var w = wrkr(fns, init, id, function (err, dat) { + w.terminate(); + cb(err, dat); + }); + w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []); + return function () { w.terminate(); }; +}; +// auto stream +var astrm = function (strm) { + strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); }; + return function (ev) { return strm.push(ev.data[0], ev.data[1]); }; +}; +// async stream attach +var astrmify = function (fns, strm, opts, init, id) { + var t; + var w = wrkr(fns, init, id, function (err, dat) { + if (err) + w.terminate(), strm.ondata.call(strm, err); + else { + if (dat[1]) + w.terminate(); + strm.ondata.call(strm, err, dat[0], dat[1]); + } + }); + w.postMessage(opts); + strm.push = function (d, f) { + if (t) + throw 'stream finished'; + if (!strm.ondata) + throw 'no stream handler'; + w.postMessage([d, t = f], [d.buffer]); + }; + strm.terminate = function () { w.terminate(); }; +}; +// read 2 bytes +var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; +// read 4 bytes +var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; }; +var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); }; +// write bytes +var wbytes = function (d, b, v) { + for (; v; ++b) + d[b] = v, v >>>= 8; +}; +// gzip header +var gzh = function (c, o) { + var fn = o.filename; + c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix + if (o.mtime != 0) + wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000)); + if (fn) { + c[3] = 8; + for (var i = 0; i <= fn.length; ++i) + c[i + 10] = fn.charCodeAt(i); + } +}; +// gzip footer: -8 to -4 = CRC, -4 to -0 is length +// gzip start +var gzs = function (d) { + if (d[0] != 31 || d[1] != 139 || d[2] != 8) + throw 'invalid gzip data'; + var flg = d[3]; + var st = 10; + if (flg & 4) + st += d[10] | (d[11] << 8) + 2; + for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) + ; + return st + (flg & 2); +}; +// gzip length +var gzl = function (d) { + var l = d.length; + return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0; +}; +// gzip header length +var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); }; +// zlib header +var zlh = function (c, o) { + var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; + c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1); +}; +// zlib valid +var zlv = function (d) { + if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) + throw 'invalid zlib data'; + if (d[1] & 32) + throw 'invalid zlib data: preset dictionaries not supported'; +}; +function AsyncCmpStrm(opts, cb) { + if (!cb && typeof opts == 'function') + cb = opts, opts = {}; + this.ondata = cb; + return opts; +} +// zlib footer: -4 to -0 is Adler32 +/** + * Streaming DEFLATE compression + */ +var Deflate = /*#__PURE__*/ (function () { + function Deflate(opts, cb) { + if (!cb && typeof opts == 'function') + cb = opts, opts = {}; + this.ondata = cb; + this.o = opts || {}; + } + Deflate.prototype.p = function (c, f) { + this.ondata(dopt(c, this.o, 0, 0, !f), f); + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Deflate.prototype.push = function (chunk, final) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + this.d = final; + this.p(chunk, final || false); + }; + return Deflate; +}()); +export { Deflate }; +/** + * Asynchronous streaming DEFLATE compression + */ +var AsyncDeflate = /*#__PURE__*/ (function () { + function AsyncDeflate(opts, cb) { + astrmify([ + bDflt, + function () { return [astrm, Deflate]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Deflate(ev.data); + onmessage = astrm(strm); + }, 6); + } + return AsyncDeflate; +}()); +export { AsyncDeflate }; +export function deflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb); +} +/** + * Compresses data with DEFLATE without any wrapper + * @param data The data to compress + * @param opts The compression options + * @returns The deflated version of the data + */ +export function deflateSync(data, opts) { + return dopt(data, opts || {}, 0, 0); +} +/** + * Streaming DEFLATE decompression + */ +var Inflate = /*#__PURE__*/ (function () { + /** + * Creates an inflation stream + * @param cb The callback to call whenever data is inflated + */ + function Inflate(cb) { + this.s = {}; + this.p = new u8(0); + this.ondata = cb; + } + Inflate.prototype.e = function (c) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + var l = this.p.length; + var n = new u8(l + c.length); + n.set(this.p), n.set(c, l), this.p = n; + }; + Inflate.prototype.c = function (final) { + this.d = this.s.i = final || false; + var bts = this.s.b; + var dt = inflt(this.p, this.o, this.s); + this.ondata(slc(dt, bts, this.s.b), this.d); + this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length; + this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7; + }; + /** + * Pushes a chunk to be inflated + * @param chunk The chunk to push + * @param final Whether this is the final chunk + */ + Inflate.prototype.push = function (chunk, final) { + this.e(chunk), this.c(final); + }; + return Inflate; +}()); +export { Inflate }; +/** + * Asynchronous streaming DEFLATE decompression + */ +var AsyncInflate = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous inflation stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncInflate(cb) { + this.ondata = cb; + astrmify([ + bInflt, + function () { return [astrm, Inflate]; } + ], this, 0, function () { + var strm = new Inflate(); + onmessage = astrm(strm); + }, 7); + } + return AsyncInflate; +}()); +export { AsyncInflate }; +export function inflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt + ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb); +} +/** + * Expands DEFLATE data with no wrapper + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +export function inflateSync(data, out) { + return inflt(data, out); +} +// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize. +/** + * Streaming GZIP compression + */ +var Gzip = /*#__PURE__*/ (function () { + function Gzip(opts, cb) { + this.c = crc(); + this.l = 0; + this.v = 1; + Deflate.call(this, opts, cb); + } + /** + * Pushes a chunk to be GZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Gzip.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Gzip.prototype.p = function (c, f) { + this.c.p(c); + this.l += c.length; + var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f); + if (this.v) + gzh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l); + this.ondata(raw, f); + }; + return Gzip; +}()); +export { Gzip }; +/** + * Asynchronous streaming GZIP compression + */ +var AsyncGzip = /*#__PURE__*/ (function () { + function AsyncGzip(opts, cb) { + astrmify([ + bDflt, + gze, + function () { return [astrm, Deflate, Gzip]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Gzip(ev.data); + onmessage = astrm(strm); + }, 8); + } + return AsyncGzip; +}()); +export { AsyncGzip }; +export function gzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + gze, + function () { return [gzipSync]; } + ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb); +} +/** + * Compresses data with GZIP + * @param data The data to compress + * @param opts The compression options + * @returns The gzipped version of the data + */ +export function gzipSync(data, opts) { + if (!opts) + opts = {}; + var c = crc(), l = data.length; + c.p(data); + var d = dopt(data, opts, gzhl(opts), 8), s = d.length; + return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d; +} +/** + * Streaming GZIP decompression + */ +var Gunzip = /*#__PURE__*/ (function () { + /** + * Creates a GUNZIP stream + * @param cb The callback to call whenever data is inflated + */ + function Gunzip(cb) { + this.v = 1; + Inflate.call(this, cb); + } + /** + * Pushes a chunk to be GUNZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Gunzip.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + var s = this.p.length > 3 ? gzs(this.p) : 4; + if (s >= this.p.length && !final) + return; + this.p = this.p.subarray(s), this.v = 0; + } + if (final) { + if (this.p.length < 8) + throw 'invalid gzip stream'; + this.p = this.p.subarray(0, -8); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Gunzip; +}()); +export { Gunzip }; +/** + * Asynchronous streaming GZIP decompression + */ +var AsyncGunzip = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous GUNZIP stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncGunzip(cb) { + this.ondata = cb; + astrmify([ + bInflt, + guze, + function () { return [astrm, Inflate, Gunzip]; } + ], this, 0, function () { + var strm = new Gunzip(); + onmessage = astrm(strm); + }, 9); + } + return AsyncGunzip; +}()); +export { AsyncGunzip }; +export function gunzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt, + guze, + function () { return [gunzipSync]; } + ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb); +} +/** + * Expands GZIP data + * @param data The data to decompress + * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory. + * @returns The decompressed version of the data + */ +export function gunzipSync(data, out) { + return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data))); +} +/** + * Streaming Zlib compression + */ +var Zlib = /*#__PURE__*/ (function () { + function Zlib(opts, cb) { + this.c = adler(); + this.v = 1; + Deflate.call(this, opts, cb); + } + /** + * Pushes a chunk to be zlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Zlib.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Zlib.prototype.p = function (c, f) { + this.c.p(c); + var raw = dopt(c, this.o, this.v && 2, f && 4, !f); + if (this.v) + zlh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 4, this.c.d()); + this.ondata(raw, f); + }; + return Zlib; +}()); +export { Zlib }; +/** + * Asynchronous streaming Zlib compression + */ +var AsyncZlib = /*#__PURE__*/ (function () { + function AsyncZlib(opts, cb) { + astrmify([ + bDflt, + zle, + function () { return [astrm, Deflate, Zlib]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Zlib(ev.data); + onmessage = astrm(strm); + }, 10); + } + return AsyncZlib; +}()); +export { AsyncZlib }; +export function zlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + zle, + function () { return [zlibSync]; } + ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb); +} +/** + * Compress data with Zlib + * @param data The data to compress + * @param opts The compression options + * @returns The zlib-compressed version of the data + */ +export function zlibSync(data, opts) { + if (!opts) + opts = {}; + var a = adler(); + a.p(data); + var d = dopt(data, opts, 2, 4); + return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; +} +/** + * Streaming Zlib decompression + */ +var Unzlib = /*#__PURE__*/ (function () { + /** + * Creates a Zlib decompression stream + * @param cb The callback to call whenever data is inflated + */ + function Unzlib(cb) { + this.v = 1; + Inflate.call(this, cb); + } + /** + * Pushes a chunk to be unzlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Unzlib.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + if (this.p.length < 2 && !final) + return; + this.p = this.p.subarray(2), this.v = 0; + } + if (final) { + if (this.p.length < 4) + throw 'invalid zlib stream'; + this.p = this.p.subarray(0, -4); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Unzlib; +}()); +export { Unzlib }; +/** + * Asynchronous streaming Zlib decompression + */ +var AsyncUnzlib = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous Zlib decompression stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncUnzlib(cb) { + this.ondata = cb; + astrmify([ + bInflt, + zule, + function () { return [astrm, Inflate, Unzlib]; } + ], this, 0, function () { + var strm = new Unzlib(); + onmessage = astrm(strm); + }, 11); + } + return AsyncUnzlib; +}()); +export { AsyncUnzlib }; +export function unzlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt, + zule, + function () { return [unzlibSync]; } + ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb); +} +/** + * Expands Zlib data + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +export function unzlibSync(data, out) { + return inflt((zlv(data), data.subarray(2, -4)), out); +} +// Default algorithm for compression (used because having a known output size allows faster decompression) +export { gzip as compress, AsyncGzip as AsyncCompress }; +// Default algorithm for compression (used because having a known output size allows faster decompression) +export { gzipSync as compressSync, Gzip as Compress }; +/** + * Streaming GZIP, Zlib, or raw DEFLATE decompression + */ +var Decompress = /*#__PURE__*/ (function () { + /** + * Creates a decompression stream + * @param cb The callback to call whenever data is decompressed + */ + function Decompress(cb) { + this.G = Gunzip; + this.I = Inflate; + this.Z = Unzlib; + this.ondata = cb; + } + /** + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Decompress.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no stream handler'; + if (!this.s) { + if (this.p && this.p.length) { + var n = new u8(this.p.length + chunk.length); + n.set(this.p), n.set(chunk, this.p.length); + } + else + this.p = chunk; + if (this.p.length > 2) { + var _this_1 = this; + var cb = function () { _this_1.ondata.apply(_this_1, arguments); }; + this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8) + ? new this.G(cb) + : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31)) + ? new this.I(cb) + : new this.Z(cb); + this.s.push(this.p, final); + this.p = null; + } + } + else + this.s.push(chunk, final); + }; + return Decompress; +}()); +export { Decompress }; +/** + * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression + */ +var AsyncDecompress = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous decompression stream + * @param cb The callback to call whenever data is decompressed + */ + function AsyncDecompress(cb) { + this.G = AsyncGunzip; + this.I = AsyncInflate; + this.Z = AsyncUnzlib; + this.ondata = cb; + } + /** + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + AsyncDecompress.prototype.push = function (chunk, final) { + Decompress.prototype.push.call(this, chunk, final); + }; + return AsyncDecompress; +}()); +export { AsyncDecompress }; +export function decompress(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzip(data, opts, cb) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflate(data, opts, cb) + : unzlib(data, opts, cb); +} +/** + * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +export function decompressSync(data, out) { + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzipSync(data, out) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflateSync(data, out) + : unzlibSync(data, out); +} +// flatten a directory structure +var fltn = function (d, p, t, o) { + for (var k in d) { + var val = d[k], n = p + k; + if (val instanceof u8) + t[n] = [val, o]; + else if (Array.isArray(val)) + t[n] = [val[0], mrg(o, val[1])]; + else + fltn(val, n + '/', t, o); + } +}; +// text encoder +var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder(); +// text decoder +var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); +// text decoder stream +var tds = 0; +try { + td.decode(et, { stream: true }); + tds = 1; +} +catch (e) { } +// decode UTF8 +var dutf8 = function (d) { + for (var r = '', i = 0;;) { + var c = d[i++]; + var eb = (c > 127) + (c > 223) + (c > 239); + if (i + eb > d.length) + return [r, slc(d, i - 1)]; + if (!eb) + r += String.fromCharCode(c); + else if (eb == 3) { + c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536, + r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023)); + } + else if (eb & 1) + r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63)); + else + r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)); + } +}; +/** + * Streaming UTF-8 decoding + */ +var DecodeUTF8 = /*#__PURE__*/ (function () { + /** + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is decoded + */ + function DecodeUTF8(cb) { + this.ondata = cb; + if (tds) + this.t = new TextDecoder(); + else + this.p = et; + } + /** + * Pushes a chunk to be decoded from UTF-8 binary + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + DecodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + final = !!final; + if (this.t) { + this.ondata(this.t.decode(chunk, { stream: true }), final); + if (final) { + if (this.t.decode().length) + throw 'invalid utf-8 data'; + this.t = null; + } + return; + } + if (!this.p) + throw 'stream finished'; + var dat = new u8(this.p.length + chunk.length); + dat.set(this.p); + dat.set(chunk, this.p.length); + var _a = dutf8(dat), ch = _a[0], np = _a[1]; + if (final) { + if (np.length) + throw 'invalid utf-8 data'; + this.p = null; + } + else + this.p = np; + this.ondata(ch, final); + }; + return DecodeUTF8; +}()); +export { DecodeUTF8 }; +/** + * Streaming UTF-8 encoding + */ +var EncodeUTF8 = /*#__PURE__*/ (function () { + /** + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is encoded + */ + function EncodeUTF8(cb) { + this.ondata = cb; + } + /** + * Pushes a chunk to be encoded to UTF-8 + * @param chunk The string data to push + * @param final Whether this is the last chunk + */ + EncodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + if (this.d) + throw 'stream finished'; + this.ondata(strToU8(chunk), this.d = final || false); + }; + return EncodeUTF8; +}()); +export { EncodeUTF8 }; +/** + * Converts a string into a Uint8Array for use with compression/decompression methods + * @param str The string to encode + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless decoding a binary string. + * @returns The string encoded in UTF-8/Latin-1 binary + */ +export function strToU8(str, latin1) { + if (latin1) { + var ar_1 = new u8(str.length); + for (var i = 0; i < str.length; ++i) + ar_1[i] = str.charCodeAt(i); + return ar_1; + } + if (te) + return te.encode(str); + var l = str.length; + var ar = new u8(str.length + (str.length >> 1)); + var ai = 0; + var w = function (v) { ar[ai++] = v; }; + for (var i = 0; i < l; ++i) { + if (ai + 5 > ar.length) { + var n = new u8(ai + 8 + ((l - i) << 1)); + n.set(ar); + ar = n; + } + var c = str.charCodeAt(i); + if (c < 128 || latin1) + w(c); + else if (c < 2048) + w(192 | (c >> 6)), w(128 | (c & 63)); + else if (c > 55295 && c < 57344) + c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023), + w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + else + w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + } + return slc(ar, 0, ai); +} +/** + * Converts a Uint8Array to a string + * @param dat The data to decode to string + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless encoding to binary string. + * @returns The original UTF-8/Latin-1 string + */ +export function strFromU8(dat, latin1) { + if (latin1) { + var r = ''; + for (var i = 0; i < dat.length; i += 16384) + r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); + return r; + } + else if (td) + return td.decode(dat); + else { + var _a = dutf8(dat), out = _a[0], ext = _a[1]; + if (ext.length) + throw 'invalid utf-8 data'; + return out; + } +} +; +// deflate bit flag +var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; }; +// skip local zip header +var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; +// read zip header +var zh = function (d, b, z) { + var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20); + var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2]; + return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off]; +}; +// read zip64 extra field +var z64e = function (d, b) { + for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) + ; + return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)]; +}; +// extra field length +var exfl = function (ex) { + var le = 0; + if (ex) { + for (var k in ex) { + var l = ex[k].length; + if (l > 65535) + throw 'extra field too long'; + le += l + 4; + } + } + return le; +}; +// write zip header +var wzh = function (d, b, f, fn, u, c, ce, co) { + var fl = fn.length, ex = f.extra, col = co && co.length; + var exl = exfl(ex); + wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4; + if (ce != null) + d[b++] = 20, d[b++] = f.os; + d[b] = 20, b += 2; // spec compliance? what's that? + d[b++] = (f.flag << 1) | (c == null && 8), d[b++] = u && 8; + d[b++] = f.compression & 255, d[b++] = f.compression >> 8; + var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; + if (y < 0 || y > 119) + throw 'date not in range 1980-2099'; + wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4; + if (c != null) { + wbytes(d, b, f.crc); + wbytes(d, b + 4, c); + wbytes(d, b + 8, f.size); + } + wbytes(d, b + 12, fl); + wbytes(d, b + 14, exl), b += 16; + if (ce != null) { + wbytes(d, b, col); + wbytes(d, b + 6, f.attrs); + wbytes(d, b + 10, ce), b += 14; + } + d.set(fn, b); + b += fl; + if (exl) { + for (var k in ex) { + var exf = ex[k], l = exf.length; + wbytes(d, b, +k); + wbytes(d, b + 2, l); + d.set(exf, b + 4), b += 4 + l; + } + } + if (col) + d.set(co, b), b += col; + return b; +}; +// write zip footer (end of central directory) +var wzf = function (o, b, c, d, e) { + wbytes(o, b, 0x6054B50); // skip disk + wbytes(o, b + 8, c); + wbytes(o, b + 10, c); + wbytes(o, b + 12, d); + wbytes(o, b + 16, e); +}; +/** + * A pass-through stream to keep data uncompressed in a ZIP archive. + */ +var ZipPassThrough = /*#__PURE__*/ (function () { + /** + * Creates a pass-through stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + */ + function ZipPassThrough(filename) { + this.filename = filename; + this.c = crc(); + this.size = 0; + this.compression = 0; + } + /** + * Processes a chunk and pushes to the output stream. You can override this + * method in a subclass for custom behavior, but by default this passes + * the data through. You must call this.ondata(err, chunk, final) at some + * point in this method. + * @param chunk The chunk to process + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.process = function (chunk, final) { + this.ondata(null, chunk, final); + }; + /** + * Pushes a chunk to be added. If you are subclassing this with a custom + * compression algorithm, note that you must push data from the source + * file only, pre-compression. + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback - add to ZIP archive before pushing'; + this.c.p(chunk); + this.size += chunk.length; + if (final) + this.crc = this.c.d(); + this.process(chunk, final || false); + }; + return ZipPassThrough; +}()); +export { ZipPassThrough }; +// I don't extend because TypeScript extension adds 1kB of runtime bloat +/** + * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate + * for better performance + */ +var ZipDeflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options + */ + function ZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new Deflate(opts, function (dat, final) { + _this_1.ondata(null, dat, final); + }); + this.compression = 8; + this.flag = dbf(opts.level); + } + ZipDeflate.prototype.process = function (chunk, final) { + try { + this.d.push(chunk, final); + } + catch (e) { + this.ondata(e, null, final); + } + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + ZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return ZipDeflate; +}()); +export { ZipDeflate }; +/** + * Asynchronous streaming DEFLATE compression for ZIP archives + */ +var AsyncZipDeflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options + */ + function AsyncZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new AsyncDeflate(opts, function (err, dat, final) { + _this_1.ondata(err, dat, final); + }); + this.compression = 8; + this.flag = dbf(opts.level); + this.terminate = this.d.terminate; + } + AsyncZipDeflate.prototype.process = function (chunk, final) { + this.d.push(chunk, final); + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + AsyncZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return AsyncZipDeflate; +}()); +export { AsyncZipDeflate }; +// TODO: Better tree shaking +/** + * A zippable archive to which files can incrementally be added + */ +var Zip = /*#__PURE__*/ (function () { + /** + * Creates an empty ZIP archive to which files can be added + * @param cb The callback to call whenever data for the generated ZIP archive + * is available + */ + function Zip(cb) { + this.ondata = cb; + this.u = []; + this.d = 1; + } + /** + * Adds a file to the ZIP archive + * @param file The file stream to add + */ + Zip.prototype.add = function (file) { + var _this_1 = this; + if (this.d & 2) + throw 'stream finished'; + var f = strToU8(file.filename), fl = f.length; + var com = file.comment, o = com && strToU8(com); + var u = fl != file.filename.length || (o && (com.length != o.length)); + var hl = fl + exfl(file.extra) + 30; + if (fl > 65535) + throw 'filename too long'; + var header = new u8(hl); + wzh(header, 0, file, f, u); + var chks = [header]; + var pAll = function () { + for (var _i = 0, chks_1 = chks; _i < chks_1.length; _i++) { + var chk = chks_1[_i]; + _this_1.ondata(null, chk, false); + } + chks = []; + }; + var tr = this.d; + this.d = 0; + var ind = this.u.length; + var uf = mrg(file, { + f: f, + u: u, + o: o, + t: function () { + if (file.terminate) + file.terminate(); + }, + r: function () { + pAll(); + if (tr) { + var nxt = _this_1.u[ind + 1]; + if (nxt) + nxt.r(); + else + _this_1.d = 1; + } + tr = 1; + } + }); + var cl = 0; + file.ondata = function (err, dat, final) { + if (err) { + _this_1.ondata(err, dat, final); + _this_1.terminate(); + } + else { + cl += dat.length; + chks.push(dat); + if (final) { + var dd = new u8(16); + wbytes(dd, 0, 0x8074B50); + wbytes(dd, 4, file.crc); + wbytes(dd, 8, cl); + wbytes(dd, 12, file.size); + chks.push(dd); + uf.c = cl, uf.b = hl + cl + 16, uf.crc = file.crc, uf.size = file.size; + if (tr) + uf.r(); + tr = 1; + } + else if (tr) + pAll(); + } + }; + this.u.push(uf); + }; + /** + * Ends the process of adding files and prepares to emit the final chunks. + * This *must* be called after adding all desired files for the resulting + * ZIP file to work properly. + */ + Zip.prototype.end = function () { + var _this_1 = this; + if (this.d & 2) { + if (this.d & 1) + throw 'stream finishing'; + throw 'stream finished'; + } + if (this.d) + this.e(); + else + this.u.push({ + r: function () { + if (!(_this_1.d & 1)) + return; + _this_1.u.splice(-1, 1); + _this_1.e(); + }, + t: function () { } + }); + this.d = 3; + }; + Zip.prototype.e = function () { + var bt = 0, l = 0, tl = 0; + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0); + } + var out = new u8(tl + 22); + for (var _b = 0, _c = this.u; _b < _c.length; _b++) { + var f = _c[_b]; + wzh(out, bt, f, f.f, f.u, f.c, l, f.o); + bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; + } + wzf(out, bt, this.u.length, tl, l); + this.ondata(null, out, true); + this.d = 2; + }; + /** + * A method to terminate any internal workers used by the stream. Subsequent + * calls to add() will fail. + */ + Zip.prototype.terminate = function () { + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + f.t(); + } + this.d = 2; + }; + return Zip; +}()); +export { Zip }; +export function zip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + var r = {}; + fltn(data, '', r, opts); + var k = Object.keys(r); + var lft = k.length, o = 0, tot = 0; + var slft = lft, files = new Array(lft); + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var cbf = function () { + var out = new u8(tot + 22), oe = o, cdl = tot - o; + tot = 0; + for (var i = 0; i < slft; ++i) { + var f = files[i]; + try { + var l = f.c.length; + wzh(out, tot, f, f.f, f.u, l); + var badd = 30 + f.f.length + exfl(f.extra); + var loc = tot + badd; + out.set(f.c, loc); + wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l; + } + catch (e) { + return cb(e, null); + } + } + wzf(out, o, files.length, cdl, oe); + cb(null, out); + }; + if (!lft) + cbf(); + var _loop_1 = function (i) { + var fn = k[i]; + var _a = r[fn], file = _a[0], p = _a[1]; + var c = crc(), size = file.length; + c.p(file); + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + var compression = p.level == 0 ? 0 : 8; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); + } + else { + var l = d.length; + files[i] = mrg(p, { + size: size, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + compression: compression + }); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + if (!--lft) + cbf(); + } + }; + if (s > 65535) + cbl('filename too long', null); + if (!compression) + cbl(null, file); + else if (size < 160000) { + try { + cbl(null, deflateSync(file, p)); + } + catch (e) { + cbl(e, null); + } + } + else + term.push(deflate(file, p, cbl)); + }; + // Cannot use lft because it can decrease + for (var i = 0; i < slft; ++i) { + _loop_1(i); + } + return tAll; +} +/** + * Synchronously creates a ZIP file. Prefer using `zip` for better performance + * with more than one file. + * @param data The directory structure for the ZIP archive + * @param opts The main options, merged with per-file options + * @returns The generated ZIP archive + */ +export function zipSync(data, opts) { + if (!opts) + opts = {}; + var r = {}; + var files = []; + fltn(data, '', r, opts); + var o = 0; + var tot = 0; + for (var fn in r) { + var _a = r[fn], file = _a[0], p = _a[1]; + var compression = p.level == 0 ? 0 : 8; + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + if (s > 65535) + throw 'filename too long'; + var d = compression ? deflateSync(file, p) : file, l = d.length; + var c = crc(); + c.p(file); + files.push(mrg(p, { + size: file.length, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + o: o, + compression: compression + })); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + } + var out = new u8(tot + 22), oe = o, cdl = tot - o; + for (var i = 0; i < files.length; ++i) { + var f = files[i]; + wzh(out, f.o, f, f.f, f.u, f.c.length); + var badd = 30 + f.f.length + exfl(f.extra); + out.set(f.c, f.o + badd); + wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0); + } + wzf(out, o, files.length, cdl, oe); + return out; +} +/** + * Streaming pass-through decompression for ZIP archives + */ +var UnzipPassThrough = /*#__PURE__*/ (function () { + function UnzipPassThrough() { + } + UnzipPassThrough.prototype.push = function (data, final) { + this.ondata(null, data, final); + }; + UnzipPassThrough.compression = 0; + return UnzipPassThrough; +}()); +export { UnzipPassThrough }; +/** + * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for + * better performance. + */ +var UnzipInflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE decompression that can be used in ZIP archives + */ + function UnzipInflate() { + var _this_1 = this; + this.i = new Inflate(function (dat, final) { + _this_1.ondata(null, dat, final); + }); + } + UnzipInflate.prototype.push = function (data, final) { + try { + this.i.push(data, final); + } + catch (e) { + this.ondata(e, data, final); + } + }; + UnzipInflate.compression = 8; + return UnzipInflate; +}()); +export { UnzipInflate }; +/** + * Asynchronous streaming DEFLATE decompression for ZIP archives + */ +var AsyncUnzipInflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE decompression that can be used in ZIP archives + */ + function AsyncUnzipInflate(_, sz) { + var _this_1 = this; + if (sz < 320000) { + this.i = new Inflate(function (dat, final) { + _this_1.ondata(null, dat, final); + }); + } + else { + this.i = new AsyncInflate(function (err, dat, final) { + _this_1.ondata(err, dat, final); + }); + this.terminate = this.i.terminate; + } + } + AsyncUnzipInflate.prototype.push = function (data, final) { + if (this.i.terminate) + data = slc(data, 0); + this.i.push(data, final); + }; + AsyncUnzipInflate.compression = 8; + return AsyncUnzipInflate; +}()); +export { AsyncUnzipInflate }; +/** + * A ZIP archive decompression stream that emits files as they are discovered + */ +var Unzip = /*#__PURE__*/ (function () { + /** + * Creates a ZIP decompression stream + * @param cb The callback to call whenever a file in the ZIP archive is found + */ + function Unzip(cb) { + this.onfile = cb; + this.k = []; + this.o = { + 0: UnzipPassThrough + }; + this.p = et; + } + /** + * Pushes a chunk to be unzipped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Unzip.prototype.push = function (chunk, final) { + var _this_1 = this; + if (!this.onfile) + throw 'no callback'; + if (!this.p) + throw 'stream finished'; + if (this.c > 0) { + var len = Math.min(this.c, chunk.length); + var toAdd = chunk.subarray(0, len); + this.c -= len; + if (this.d) + this.d.push(toAdd, !this.c); + else + this.k[0].push(toAdd); + chunk = chunk.subarray(len); + if (chunk.length) + return this.push(chunk, final); + } + else { + var f = 0, i = 0, is = void 0, buf = void 0; + if (!this.p.length) + buf = chunk; + else if (!chunk.length) + buf = this.p; + else { + buf = new u8(this.p.length + chunk.length); + buf.set(this.p), buf.set(chunk, this.p.length); + } + var l = buf.length, oc = this.c, add = oc && this.d; + var _loop_2 = function () { + var _a; + var sig = b4(buf, i); + if (sig == 0x4034B50) { + f = 1, is = i; + this_1.d = null; + this_1.c = 0; + var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28); + if (l > i + 30 + fnl + es) { + var chks_2 = []; + this_1.k.unshift(chks_2); + f = 2; + var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22); + var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u); + if (sc_1 == 4294967295) { + _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1]; + } + else if (dd) + sc_1 = -1; + i += es; + this_1.c = sc_1; + var d_1; + var file_1 = { + name: fn_1, + compression: cmp_1, + start: function () { + if (!file_1.ondata) + throw 'no callback'; + if (!sc_1) + file_1.ondata(null, et, true); + else { + var ctr = _this_1.o[cmp_1]; + if (!ctr) + throw 'unknown compression type ' + cmp_1; + d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1); + d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); }; + for (var _i = 0, chks_3 = chks_2; _i < chks_3.length; _i++) { + var dat = chks_3[_i]; + d_1.push(dat, false); + } + if (_this_1.k[0] == chks_2 && _this_1.c) + _this_1.d = d_1; + else + d_1.push(et, true); + } + }, + terminate: function () { + if (d_1 && d_1.terminate) + d_1.terminate(); + } + }; + if (sc_1 >= 0) + file_1.size = sc_1, file_1.originalSize = su_1; + this_1.onfile(file_1); + } + return "break"; + } + else if (oc) { + if (sig == 0x8074B50) { + is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0; + return "break"; + } + else if (sig == 0x2014B50) { + is = i -= 4, f = 3, this_1.c = 0; + return "break"; + } + } + }; + var this_1 = this; + for (; i < l - 4; ++i) { + var state_1 = _loop_2(); + if (state_1 === "break") + break; + } + this.p = et; + if (oc < 0) { + var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i); + if (add) + add.push(dat, !!f); + else + this.k[+(f == 2)].push(dat); + } + if (f & 2) + return this.push(buf.subarray(i), final); + this.p = buf.subarray(i); + } + if (final) { + if (this.c) + throw 'invalid zip file'; + this.p = null; + } + }; + /** + * Registers a decoder with the stream, allowing for files compressed with + * the compression type provided to be expanded correctly + * @param decoder The decoder constructor + */ + Unzip.prototype.register = function (decoder) { + this.o[decoder.compression] = decoder; + }; + return Unzip; +}()); +export { Unzip }; +/** + * Asynchronously decompresses a ZIP archive + * @param data The raw compressed ZIP file + * @param cb The callback to call with the decompressed files + * @returns A function that can be used to immediately terminate the unzipping + */ +export function unzip(data, cb) { + if (typeof cb != 'function') + throw 'no callback'; + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) { + cb('invalid zip file', null); + return; + } + } + ; + var lft = b2(data, e + 8); + if (!lft) + cb(null, {}); + var c = lft; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) { + cb('invalid zip file', null); + return; + } + c = lft = b4(data, e + 32); + o = b4(data, e + 48); + } + var _loop_3 = function (i) { + var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); + } + else { + files[fn] = d; + if (!--lft) + cb(null, files); + } + }; + if (!c_1) + cbl(null, slc(data, b, b + sc)); + else if (c_1 == 8) { + var infl = data.subarray(b, b + sc); + if (sc < 320000) { + try { + cbl(null, inflateSync(infl, new u8(su))); + } + catch (e) { + cbl(e, null); + } + } + else + term.push(inflate(infl, { size: su }, cbl)); + } + else + cbl('unknown compression type ' + c_1, null); + }; + for (var i = 0; i < c; ++i) { + _loop_3(i); + } + return tAll; +} +/** + * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better + * performance with more than one file. + * @param data The raw compressed ZIP file + * @returns The decompressed files + */ +export function unzipSync(data) { + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) + throw 'invalid zip file'; + } + ; + var c = b2(data, e + 8); + if (!c) + return {}; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) + throw 'invalid zip file'; + c = b4(data, e + 32); + o = b4(data, e + 48); + } + for (var i = 0; i < c; ++i) { + var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + if (!c_2) + files[fn] = slc(data, b, b + sc); + else if (c_2 == 8) + files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su)); + else + throw 'unknown compression type ' + c_2; + } + return files; +} diff --git a/jsm/libs/flow.module.js b/jsm/libs/flow.module.js new file mode 100644 index 0000000..2469b71 --- /dev/null +++ b/jsm/libs/flow.module.js @@ -0,0 +1,4102 @@ +/** + * https://github.com/sunag/flow + */ + +function __flow__addCSS( css ) { + + try { + + const style = document.createElement( 'style' ); + + style.setAttribute( 'type', 'text/css' ); + style.innerHTML = css; + document.head.appendChild( style ); + + } catch ( e ) {} + +} + +__flow__addCSS( `@keyframes f-animation-open { 0% { transform: scale(.5); opacity: 0; } 100% { transform: scale(1); opacity: 1; }}f-canvas,f-canvas canvas { position: absolute; top: 0; left: 0; margin: 0; padding: 0; width: 100%; height: 100%; -webkit-touch-callout: none; }f-canvas { overflow: auto; cursor: grab;}f-canvas canvas.front { z-index: 10;}body.dragging *:not(.drag) { pointer-events: none !important;}f-canvas.grabbing * { cursor: grabbing; user-select: none;}f-canvas canvas { position: fixed; overflow: hidden; pointer-events: none;}f-canvas::-webkit-scrollbar { width: 8px; height: 8px;}f-canvas::-webkit-scrollbar-thumb:hover{ background: #014fc5;}f-canvas::-webkit-scrollbar-track { background: #363636;}f-canvas::-webkit-scrollbar-thumb { background-color: #666666; border-radius: 10px; border: 0;}f-canvas f-content,f-canvas f-area { position: absolute; display: block;}f-node { position: absolute; margin: 0; padding: 0; user-select: none; width: 320px; z-index: 1; cursor: auto; filter: drop-shadow(0 0 10px #00000061); backdrop-filter: blur(4px);}f-node.selected { z-index: 2;}f-node.selected,f-canvas.dragging-rio f-node:hover,f-canvas.dragging-lio f-node:hover { filter: drop-shadow(0 0 10px #00000061) drop-shadow(0 0 8px #4444dd);}f-node.closed f-element:not(:first-child) { display: none;}f-node.center { top: 50%; left: 50%; transform: translate( -50%, -50% );}f-node.top-right { top: 0; right: 0;}f-node.top-center { top: 0; left: 50%; transform: translateX( -50% );}f-node.top-left { top: 0; left: 0;}f-node { transition: filter 0.2s ease;}f-node { animation: .2s f-animation-open 1 alternate ease-out;}f-tips,f-drop,f-menu,f-menu input,f-menu button,f-element,f-element input,f-element select,f-element button,f-element textarea { font-family: 'Open Sans', sans-serif; font-size: 13px; text-transform: capitalize; color: #eeeeee; outline: solid 0px #000; margin: 0; padding: 0; border: 0; user-select: none; -webkit-tap-highlight-color: transparent; transition: background 0.2s ease, filter 0.2s ease;}f-element input:read-only { color: #666;}f-element input,f-element textarea { text-transform: initial;}f-element input { transition: background 0.1s ease;}f-element input,f-element select,f-element button,f-element textarea { background-color: #232324d1;}f-element { position: relative; width: calc( 100% - 14px ); background: rgba(45, 45, 48, 0.95); pointer-events: auto; border-bottom: 2px solid #232323; display: flex; padding-left: 7px; padding-right: 7px; padding-top: 2px; padding-bottom: 2px;}f-element:after,f-element:before { transition: opacity .17s; opacity: 0; content: '';}f-element[tooltip]:hover:after,f-element[tooltip]:focus-within:after { font-size: 14px !important; display: flex; justify-content: center; position: fixed; margin-left: -7px; width: calc( 100% ); background: #1d1d1de8; border: 1px solid #444444a1; border-radius: 6px; color: #dadada; content: attr( tooltip ); margin-top: -41px; font-size: 16px; padding-top: 3px; padding-bottom: 3px; z-index: 10; opacity: 1; backdrop-filter: blur(4px); white-space: nowrap; overflow: hidden; text-shadow: 1px 1px 0px #0007;}f-element[tooltip]:hover:before,f-element[tooltip]:focus-within:before { border: solid; border-color: #1d1d1de8 transparent; border-width: 12px 6px 0 6px; left: calc( 50% - 6px ); bottom: 30px; position: absolute; opacity: 1; z-index: 11;}f-element[error] { background-color: #ff0000;}f-element[error]:hover:after,f-element[error]:focus-within:after { border: none; background-color: #ff0000bb; filter: drop-shadow( 2px 2px 5px #000 ); color: #fff;}f-element[error]:hover:before,f-element[error]:focus-within:before { border-color: #ff0000bb transparent;}f-element { height: 24px;}f-element input { margin-top: 2px; margin-bottom: 2px; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%); margin-left: 2px; margin-right: 2px; width: 100%; padding-left: 4px; padding-right: 4px;}f-element input.number { cursor: col-resize;}f-element input:focus[type='text'], f-element input:focus[type='range'], f-element input:focus[type='color'] { background: rgba( 0, 0, 0, 0.6 ); outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-element input[type='color'] { appearance: none; padding: 0; margin-left: 2px; margin-right: 2px; height: calc( 100% - 4px ); margin-top: 2px; border: none;}f-element input[type='color']::-webkit-color-swatch-wrapper { padding: 2px;}f-element input[type='color']::-webkit-color-swatch { border: none; cursor: alias;}f-element input[type='range'] { appearance: none; width: 100%; overflow: hidden; padding: 0; cursor: ew-resize;}f-element input[type='range']::-webkit-slider-runnable-track { appearance: none; height: 10px; color: #13bba4; margin: 0;}f-element input[type='range']::-webkit-slider-thumb { appearance: none; width: 0; background: #434343; box-shadow: -500px 0 0 500px rgba( 0, 120, 255, 0.98 ); border-radius: 50%; border: 0 !important;}f-element input[type='range']::-webkit-slider-runnable-track { margin-left: -4px; margin-right: -5px;}f-element input[type='checkbox'] { appearance: none; cursor: pointer;}f-element input[type='checkbox'].toggle { height: 20px; width: 45px; border-radius: 16px; display: inline-block; position: relative; margin: 0; margin-top: 2px; background: linear-gradient( 0deg, #292929 0%, #0a0a0ac2 100% ); transition: all 0.2s ease;}f-element input[type='checkbox'].toggle:after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: white; box-shadow: 0 1px 2px rgba(44, 44, 44, 0.2); transition: all 0.2s cubic-bezier(0.5, 0.1, 0.75, 1.35);}f-element input[type='checkbox'].toggle:checked { background: linear-gradient( 0deg, #0177fb 0%, #0177fb 100% );}f-element input[type='checkbox'].toggle:checked:after { transform: translatex(25px);}f-element.auto-height { display: table;}f-element textarea { width: calc( 100% - 18px ); padding-top: 1px; padding-bottom: 3px; padding-left: 3px; padding-right: 8px; margin-top: 2px; margin-left: 2px; height: calc( 100% - 8px ); max-height: 300px; border-radius: 2px; resize: none; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element.auto-height textarea { resize: auto;}f-element select { width: 100%; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px; cursor: pointer; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element f-toolbar { position: absolute; display: flex; top: 0; width: 100%; height: 100%; align-content: space-around;}f-element.input-right f-toolbar { right: 7px; float: right; justify-content: end;}f-element f-toolbar { margin-top: auto; margin-bottom: auto; margin-left: 3px; margin-right: 3px; font-size: 18px; line-height: 18px;}f-element f-toolbar button { opacity: .7; cursor: pointer; font-size: 14px; width: unset; height: unset; border-radius: unset; border: unset; outline: 0; background-color: unset; box-shadow: unset;}f-element f-toolbar button:hover,f-element f-toolbar button:active { opacity: 1; border: 0; background-color: unset;}f-element input.range-value { width: 60px; text-align: center;}f-menu.context button,f-element button { width: 100%; height: calc( 100% - 4px ); margin-left: 2px; margin-right: 2px; margin-top: 2px; border-radius: 3px; cursor: pointer;}f-element button { box-shadow: inset 1px 1px 1px 0 rgb(255 255 255 / 17%), inset -2px -2px 2px 0 rgb(0 0 0 / 26%);}f-element button:hover { color: #fff; background-color: #2a2a2a;}f-element button:active { border: 1px solid rgba( 0, 120, 255, 0.98 );}f-element f-inputs,f-element f-subinputs { display: flex; justify-content: flex-end; width: 100%;}f-element f-inputs { left: 100px; top: 50%; transform: translateY(-50%); position: absolute; width: calc( 100% - 106px ); height: calc( 100% - 4px ); z-index: 1;}f-element.inputs-disable f-inputs { filter: grayscale(100%); opacity: .5;}f-element.inputs-disable f-inputs input { pointer-events: none;}f-element f-label,f-element span { margin: auto; text-shadow: 1px 1px 0px #0007;}f-element f-label { padding-left: 4px; white-space: nowrap; position: absolute; top: 50%; transform: translateY(-50%); width: calc( 100% - 20px );}f-element.right f-label { text-align: right;}f-element.center f-label { text-align: center;}f-element f-label i { float: left; font-size: 18px; margin-right: 6px;}f-element f-label.center { width: 100%; text-align: center; display: block;}f-element.title { height: 29px; background-color: #3a3a3ab0; background-color: #3b3b43ed; cursor: all-scroll; border-top-left-radius: 6px; border-top-right-radius: 6px;}f-element.blue { background-color: #014fc5;}f-element.red { background-color: #bd0b0b;}f-element.green { background-color: #148d05;}f-element.yellow { background-color: #d6b100;}f-element.title.left { text-align: left; display: inline-grid; justify-content: start;}f-element.title span { text-align: center; font-size: 15px; padding-top: 2px;}f-element.title i { font-size: 18px; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: .5;}f-element.title f-toolbar i { font-size: 20px; right: unset; left: 0px;}f-element.input-right.title i { left: 10px; right: unset;}f-element.title.left span { text-align: left;}f-element f-io { border: 2px solid #dadada; width: 7px; height: 7px; position: absolute; background: #242427; border-radius: 8px; float: left; left: -7px; top: calc( 50% - 5px ); cursor: alias; box-shadow: 0 0 3px 2px #0000005e; z-index: 1;}f-element f-io.connect,f-canvas.dragging-rio f-element:hover f-io.lio,f-canvas.dragging-lio f-element:hover f-io.rio { zoom: 1.4;}f-node.io-connect f-io:not(.connect) { border: 2px solid #dadada !important; zoom: 1 !important;}f-element f-io.rio { float: right; right: -7px; left: unset;}f-element f-disconnect { position: absolute; left: -35px; top: 50%; font-size: 22px; transform: translateY( -50% ); filter: drop-shadow(0 0 5px #000); text-shadow: 0px 0px 5px black; cursor: pointer; transition: all .2s;}f-element.input-right f-disconnect { right: -35px; left: unset;}f-element f-disconnect:hover { color: #ff3300;}f-element textarea::-webkit-scrollbar { width: 6px;}f-element textarea::-webkit-scrollbar-track { background: #111; } f-element textarea::-webkit-scrollbar-thumb { background: #0177fb; }f-element textarea::-webkit-scrollbar-thumb:hover { background: #1187ff; }f-element.small { height: 18px;}f-element.large { height: 36px;}body.connecting f-node:not(.io-connect) f-element:hover,f-element.select { background-color: rgba(61, 70, 82, 0.98);}f-element.invalid > f-io { zoom: 1 !important;}f-element.invalid::after { font-size: 14px !important; display: flex; justify-content: center; align-items:center; margin: auto; position: absolute; width: 100%; height: 100%; background: #bd0b0b77; vertical-align: middle; color: #fff; content: 'Not Compatible'; opacity: .95; backdrop-filter: grayscale(100%); white-space: nowrap; overflow: hidden; left: 0; top: 0; text-transform: initial;}f-element.invalid > f-inputs,f-element.invalid > f-label { opacity: .1;}f-drop { width: 100%; height: 100%; position: sticky; left: 0; top: 0; background: #02358417; text-align: center; justify-content: center; align-items: center; display: flex; box-shadow: inset 0 0 20px 10px #464ace17; pointer-events: none; transition: all .07s; opacity: 0; visibility: hidden;}f-drop.visible { visibility: unset; opacity: unset; transition: all .23s;}f-drop span { opacity: .5; font-size: 40px; text-shadow: 0px 0px 5px #000; font-weight: bold;}f-tooltip { pointer-events: none;}f-tooltip { position: absolute; left: 0; top: 0; background: rgba(0,0,0,.8); backdrop-filter: blur(4px); font-size: 14px; padding: 7px; left: 50%; border-radius: 10px; transform: translateX(-50%); visibility: hidden; pointer-events: none; opacity: 0; transition: all 0.3s ease; z-index: 150; white-space: nowrap;}f-menu.context,f-menu.search { position: absolute;}f-menu.context { width: 170px; z-index: 110;}f-menu.search { bottom: 85px; left: 50%; transform: translateX(-50%); z-index: 10; width: 300px;}f-menu.context f-list { display: block; margin: 0; background: #171717e6; font-size: 12px; border-radius: 6px; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; box-shadow: 3px 3px 6px rgba(0,0,0,.2); transition: opacity 0.2s ease, transform 0.1s ease;}f-menu.search f-list { margin: 0 6px 0 6px; display: flex; flex-direction: column-reverse; margin-bottom: 5px;}f-menu.context.hidden { visibility: hidden; opacity: 0;}f-menu.context f-item,f-menu.search f-item { display: block; position: relative; margin: 0; padding: 0; white-space: nowrap;}f-menu.search f-item { opacity: 0;}f-menu.context f-item.submenu::after { content: ""; position: absolute; right: 6px; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); border: 5px solid transparent; border-left-color: #808080;}f-menu.context f-item:hover > f-menu,f-menu.context f-item.active > f-menu { visibility: unset; transform: unset; opacity: unset;}f-menu.context f-menu { top: 0px; left: calc( 100% - 4px );}f-menu.context f-item button,f-menu.search f-item button { overflow: visible; display: block; width: calc( 100% - 6px ); text-align: left; cursor: pointer; white-space: nowrap; padding: 6px 8px; border-radius: 3px; background: rgba(45, 45, 48, 0.95); border: 0; color: #ddd; margin: 3px; text-shadow: 1px 1px 0px #0007;}f-menu.context f-item button i,f-menu.search f-item button i { float: left; font-size: 16px;}f-menu.context f-item button span,f-menu.search f-item button span { margin-left: 6px;}f-menu.context f-item:hover > button,f-menu.search f-item:hover > button,f-menu.search f-item.active > button { color: #fff; background-color: rgba(61, 70, 82, 0.98);}f-menu.search f-item:hover,f-menu.search f-item.active { opacity: 1 !important;}f-menu.context f-item button:active { outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-menu.context f-item f-tooltip { margin-left: 85px; top: -50px;}f-menu.search f-item { display: none;}f-menu.search f-item:nth-child(1) { opacity: 1; display: unset;}f-menu.search f-item:nth-child(2) { opacity: .8; display: unset;}f-menu.search f-item:nth-child(3) { opacity: .6; display: unset;}f-menu.search f-item:nth-child(4) { opacity: .4; display: unset;}f-menu.search f-item button { border-radius: 14px;}f-tips { right: 10px; top: 10px; position: absolute; z-index: 100; pointer-events: none; display: flex; flex-direction: column;}f-tips f-tip { width: 450px; font-size: 13px; border-radius: 6px; text-align: center; display: block; height: auto; color: #ffffffe0; margin: 4px; padding: 4px; background: #17171794; border: 1px solid #7e7e7e38; line-height: 100%; backdrop-filter: blur(6px); transition: all 0.2s ease; text-transform: initial; opacity: 0;}f-tips f-tip:nth-child(1) { opacity: 1;}f-tips f-tip:nth-child(2) { opacity: .75;}f-tips f-tip:nth-child(3) { opacity: .25;}f-tips f-tip:nth-child(4) { opacity: .1;}f-tips f-tip.error { background: #b900005e;}f-menu.search input { width: calc( 100% - 28px ); height: 41px; position: absolute; z-index: 10; border-radius: 20px; padding-left: 14px; padding-right: 14px; font-size: 15px; background-color: #17171794; border: 1px solid #7e7e7e45; backdrop-filter: blur(6px); box-shadow: 3px 3px 6px rgb(0 0 0 / 20%); text-transform: initial;}f-menu.circle { position: absolute; left: 40px; bottom: 40px; z-index: 100;}f-menu.circle f-item { align-content: space-around; margin-right: 20px;}f-menu.circle f-item button { width: 47px; height: 47px; font-size: 22px; background: #17171794; border-radius: 50%; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; line-height: 100%; cursor: pointer; box-shadow: 3px 3px 6px rgba(0,0,0,.2);}f-menu.circle f-item f-tooltip { margin-top: -60px;}.f-rounded f-node f-element,.f-rounded f-node f-element.title.left { border-radius: 10px 5px 10px 5px;}.f-rounded f-node f-element input, .f-rounded f-node f-element select,.f-rounded f-node f-element button,.f-rounded f-node f-element textarea,.f-rounded f-node f-element input[type='checkbox'].toggle,.f-rounded f-node f-element input[type='checkbox'].toggle:after { border-radius: 20px 10px;}.f-rounded f-node f-element input { padding-left: 7px; padding-right: 7px;}.f-rounded f-menu.context,.f-rounded f-menu.context f-item button { border-radius: 20px 10px;}@media (hover: hover) and (pointer: fine) { f-node:not(.selected):hover { filter: drop-shadow(0 0 6px #66666630); } f-element f-toolbar { visibility: hidden; opacity: 0; transition: opacity 0.2s ease; } f-node:hover > f-element f-toolbar { visibility: visible; opacity: 1; } f-element f-io:hover { zoom: 1.4; } f-menu.circle f-item button:hover { background-color: #2a2a2a; } f-menu.search input:hover, f-menu.search input:focus { background-color: #1a1a1a; filter: drop-shadow(0 0 6px #66666630); } f-menu.search input:focus { filter: drop-shadow(0 0 8px #4444dd); } f-menu.circle f-item button:hover > f-tooltip, f-menu.context f-item button:hover > f-tooltip { visibility: visible; opacity: 1; } f-menu.circle f-item button:hover > f-tooltip { margin-top: -50px; } f-menu.context f-item button:hover > f-tooltip { top: -30px; } f-menu.circle f-item button:focus > f-tooltip, f-menu.context f-item button:focus > f-tooltip { visibility: hidden; opacity: 0; }}@media (hover: none) and (pointer: coarse) { body.dragging f-canvas, body.connecting f-canvas { overflow: hidden !important; }}f-canvas { will-change: top, left;}f-node { will-change: transform !important;}` ); + +const REVISION = '1'; + +const Styles = { + icons: { + close: '', + unlink: '' + } +}; + +let _id = 0; + +class Serializer extends EventTarget { + + constructor() { + + super(); + + this._id = _id ++; + + this._serializable = true; + + } + + get id() { + + return this._id; + + } + + setSerializable( value ) { + + this._serializable = value; + + return this; + + } + + getSerializable() { + + return this._serializable; + + } + + serialize( /*data*/ ) { + + console.warn( 'Serializer: Abstract function.' ); + + } + + deserialize( /*data*/ ) { + + console.warn( 'Serializer: Abstract function.' ); + + } + + toJSON( data = null ) { + + let object = null; + + const id = this.id; + + if ( data !== null ) { + + const objects = data.objects; + + object = objects[ id ]; + + if ( object === undefined ) { + + object = { objects }; + + this.serialize( object ); + + delete object.objects; + + objects[ id ] = object; + + } + + } else { + + object = { objects: {} }; + + this.serialize( object ); + + } + + object.id = id; + object.type = this.constructor.name; + + return object; + + } + +} + +class PointerMonitor { + + started = false; + + constructor() { + + this.x = 0; + this.y = 0; + + this._onMoveEvent = ( e ) => { + + const event = e.touches ? e.touches[ 0 ] : e; + + this.x = event.x; + this.y = event.y; + + }; + + } + + start() { + + if ( this.started ) return; + + this.started = true; + + window.addEventListener( 'wheel', this._onMoveEvent, true ); + + window.addEventListener( 'mousedown', this._onMoveEvent, true ); + window.addEventListener( 'touchstart', this._onMoveEvent, true ); + + window.addEventListener( 'mousemove', this._onMoveEvent, true ); + window.addEventListener( 'touchmove', this._onMoveEvent, true ); + + window.addEventListener( 'dragover', this._onMoveEvent, true ); + + return this; + + } + +} + +const pointer = new PointerMonitor().start(); + +const draggableDOM = ( dom, callback = null, className = 'dragging' ) => { + + let dragData = null; + + const getZoom = () => { + + let zoomDOM = dom; + + while ( zoomDOM && zoomDOM !== document ) { + + const zoom = zoomDOM.style.zoom; + + if ( zoom ) { + + return Number( zoom ); + + } + + zoomDOM = zoomDOM.parentNode; + + } + + return 1; + + }; + + const onMouseDown = ( e ) => { + + const event = e.touches ? e.touches[ 0 ] : e; + + e.stopImmediatePropagation(); + + dragData = { + client: { x: event.clientX, y: event.clientY }, + delta: { x: 0, y: 0 }, + start: { x: dom.offsetLeft, y: dom.offsetTop }, + dragging: false, + isTouch: !! e.touches + }; + + window.addEventListener( 'mousemove', onGlobalMouseMove ); + window.addEventListener( 'mouseup', onGlobalMouseUp ); + + window.addEventListener( 'touchmove', onGlobalMouseMove ); + window.addEventListener( 'touchend', onGlobalMouseUp ); + + }; + + const onGlobalMouseMove = ( e ) => { + + const { start, delta, client } = dragData; + + const event = e.touches ? e.touches[ 0 ] : e; + + const zoom = getZoom(); + + delta.x = ( event.clientX - client.x ) / zoom; + delta.y = ( event.clientY - client.y ) / zoom; + + dragData.x = start.x + delta.x; + dragData.y = start.y + delta.y; + + if ( dragData.dragging === true ) { + + if ( callback !== null ) { + + callback( dragData ); + + } else { + + dom.style.cssText += `; left: ${ dragData.x }px; top: ${ dragData.y }px;`; + + } + + e.stopImmediatePropagation(); + + } else { + + if ( Math.abs( delta.x ) > 2 || Math.abs( delta.y ) > 2 ) { + + dragData.dragging = true; + + dom.classList.add( 'drag' ); + + if ( className ) document.body.classList.add( className ); + + e.stopImmediatePropagation(); + + } + + } + + }; + + const onGlobalMouseUp = ( e ) => { + + e.stopImmediatePropagation(); + + dom.classList.remove( 'drag' ); + + if ( className ) document.body.classList.remove( className ); + + window.removeEventListener( 'mousemove', onGlobalMouseMove ); + window.removeEventListener( 'mouseup', onGlobalMouseUp ); + + window.removeEventListener( 'touchmove', onGlobalMouseMove ); + window.removeEventListener( 'touchend', onGlobalMouseUp ); + + if ( callback === null ) { + + dom.removeEventListener( 'mousedown', onMouseDown ); + dom.removeEventListener( 'touchstart', onMouseDown ); + + } + + dragData.dragging = false; + + if ( callback !== null ) { + + callback( dragData ); + + } + + }; + + if ( dom instanceof Event ) { + + const e = dom; + dom = e.target; + + onMouseDown( e ); + + } else { + + dom.addEventListener( 'mousedown', onMouseDown ); + dom.addEventListener( 'touchstart', onMouseDown ); + + } + +}; + +const dispatchEventList = ( list, ...params ) => { + + for ( const callback of list ) { + + if ( callback( ...params ) === false ) { + + return false; + + } + + } + + return true; + +}; + +const toPX = ( val ) => { + + if ( isNaN( val ) === false ) { + + val = `${ val }px`; + + } + + return val; + +}; + +const toHex = ( val ) => { + + if ( isNaN( val ) === false ) { + + val = `#${ val.toString( 16 ).padStart( 6, '0' ) }`; + + } + + return val; + +}; + +var Utils = /*#__PURE__*/Object.freeze( { + __proto__: null, + pointer: pointer, + draggableDOM: draggableDOM, + dispatchEventList: dispatchEventList, + toPX: toPX, + toHex: toHex +} ); + +class Link { + + constructor( inputElement = null, outputElement = null ) { + + this.inputElement = inputElement; + this.outputElement = outputElement; + + } + + get lioElement() { + + if ( Link.InputDirection === 'left' ) { + + return this.outputElement; + + } else { + + return this.inputElement; + + } + + } + + get rioElement() { + + if ( Link.InputDirection === 'left' ) { + + return this.inputElement; + + } else { + + return this.outputElement; + + } + + } + +} + +//Link.InputDirection = 'right'; +Link.InputDirection = 'left'; + +let selected = null; + +class Element extends Serializer { + + constructor( draggable = false ) { + + super(); + + const dom = document.createElement( 'f-element' ); + dom.element = this; + + const onSelect = ( e ) => { + + let element = this; + + if ( e.changedTouches && e.changedTouches.length > 0 ) { + + const touch = e.changedTouches[ 0 ]; + + let overDOM = document.elementFromPoint( touch.clientX, touch.clientY ); + + while ( overDOM && ( ! overDOM.element || ! overDOM.element.isElement ) ) { + + overDOM = overDOM.parentNode; + + } + + element = overDOM ? overDOM.element : null; + + } + + const type = e.type; + + if ( ( type === 'mouseout' ) && selected === element ) { + + selected = null; + + } else { + + selected = element; + + } + + }; + + if ( draggable === false ) { + + dom.ontouchstart = dom.onmousedown = ( e ) => { + + e.stopPropagation(); + + }; + + } + + dom.addEventListener( 'mouseup', onSelect, true ); + dom.addEventListener( 'mouseover', onSelect ); + dom.addEventListener( 'mouseout', onSelect ); + dom.addEventListener( 'touchmove', onSelect ); + dom.addEventListener( 'touchend', onSelect ); + + this.inputs = []; + + this.links = []; + + this.dom = dom; + + this.lioLength = 0; + this.rioLength = 0; + + this.events = { + 'connect': [], + 'connectChildren': [], + 'valid': [] + }; + + this.node = null; + + this.style = ''; + + this.objectCallback = null; + + this.enabledInputs = true; + + this.visible = true; + + this.inputsDOM = dom; + + this.disconnectDOM = null; + + this.lioDOM = this._createIO( 'lio' ); + this.rioDOM = this._createIO( 'rio' ); + + this.dom.classList.add( `input-${ Link.InputDirection }` ); + + this.dom.append( this.lioDOM ); + this.dom.append( this.rioDOM ); + + this.addEventListener( 'connect', ( ) => { + + dispatchEventList( this.events.connect, this ); + + } ); + + this.addEventListener( 'connectChildren', ( ) => { + + dispatchEventList( this.events.connectChildren, this ); + + } ); + + } + + setAttribute( name, value ) { + + this.dom.setAttribute( name, value ); + + return this; + + } + + onValid( callback ) { + + this.events.valid.push( callback ); + + return this; + + } + + onConnect( callback, childrens = false ) { + + this.events.connect.push( callback ); + + if ( childrens ) { + + this.events.connectChildren.push( callback ); + + } + + return this; + + } + + setObjectCallback( callback ) { + + this.objectCallback = callback; + + return this; + + } + + getObject( output = null ) { + + return this.objectCallback ? this.objectCallback( output ) : null; + + } + + setVisible( value ) { + + this.visible = value; + + this.dom.style.display = value ? '' : 'none'; + + return this; + + } + + getVisible() { + + return this.visible; + + } + + setEnabledInputs( value ) { + + const dom = this.dom; + + if ( ! this.enabledInputs ) dom.classList.remove( 'inputs-disable' ); + + if ( ! value ) dom.classList.add( 'inputs-disable' ); + + this.enabledInputs = value; + + return this; + + } + + getEnabledInputs() { + + return this.enabledInputs; + + } + + setColor( color ) { + + this.dom.style[ 'background-color' ] = toHex( color ); + + return this; + + } + + setStyle( style ) { + + const dom = this.dom; + + if ( this.style ) dom.classList.remove( this.style ); + + if ( style ) dom.classList.add( style ); + + this.style = style; + + return this; + + } + + setInput( length ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setLIO( length ); + + } else { + + return this.setRIO( length ); + + } + + } + + setInputColor( color ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setLIOColor( color ); + + } else { + + return this.setRIOColor( color ); + + } + + } + + setOutput( length ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setRIO( length ); + + } else { + + return this.setLIO( length ); + + } + + } + + setOutputColor( color ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setRIOColor( color ); + + } else { + + return this.setLIOColor( color ); + + } + + } + + get inputLength() { + + if ( Link.InputDirection === 'left' ) { + + return this.lioLength; + + } else { + + return this.rioLength; + + } + + } + + get outputLength() { + + if ( Link.InputDirection === 'left' ) { + + return this.rioLength; + + } else { + + return this.lioLength; + + } + + } + + setLIOColor( color ) { + + this.lioDOM.style[ 'border-color' ] = toHex( color ); + + return this; + + } + + setLIO( length ) { + + this.lioLength = length; + + this.lioDOM.style.visibility = length > 0 ? '' : 'hidden'; + + return this; + + } + + getLIOColor() { + + return this.lioDOM.style[ 'border-color' ]; + + } + + setRIOColor( color ) { + + this.rioDOM.style[ 'border-color' ] = toHex( color ); + + return this; + + } + + getRIOColor() { + + return this.rioDOM.style[ 'border-color' ]; + + } + + setRIO( length ) { + + this.rioLength = length; + + this.rioDOM.style.visibility = length > 0 ? '' : 'hidden'; + + return this; + + } + + add( input ) { + + this.inputs.push( input ); + + input.element = this; + + this.inputsDOM.append( input.dom ); + + return this; + + } + + setHeight( val ) { + + this.dom.style.height = toPX( val ); + + return this; + + } + + getHeight() { + + return this.dom.style.height; + + } + + connect( element = null ) { + + if ( this.disconnectDOM !== null ) { + + // remove the current input + + this.disconnectDOM.dispatchEvent( new Event( 'disconnect' ) ); + + } + + if ( element !== null ) { + + if ( dispatchEventList( this.events.valid, this, element, 'connect' ) === false ) { + + return false; + + } + + const link = new Link( this, element ); + + this.links.push( link ); + + if ( this.disconnectDOM === null ) { + + this.disconnectDOM = document.createElement( 'f-disconnect' ); + this.disconnectDOM.innerHTML = Styles.icons.unlink ? `` : '✖'; + + this.dom.append( this.disconnectDOM ); + + const onDisconnect = () => { + + this.links = []; + this.dom.removeChild( this.disconnectDOM ); + + this.disconnectDOM.removeEventListener( 'mousedown', onClick, true ); + this.disconnectDOM.removeEventListener( 'touchstart', onClick, true ); + this.disconnectDOM.removeEventListener( 'disconnect', onDisconnect, true ); + + element.removeEventListener( 'connect', onConnect ); + element.removeEventListener( 'connectChildren', onConnect ); + element.removeEventListener( 'nodeConnect', onConnect ); + element.removeEventListener( 'nodeConnectChildren', onConnect ); + element.removeEventListener( 'dispose', onDispose ); + + this.disconnectDOM = null; + + }; + + const onConnect = () => { + + this.dispatchEvent( new Event( 'connectChildren' ) ); + + }; + + const onDispose = () => { + + this.connect(); + + }; + + const onClick = ( e ) => { + + e.stopPropagation(); + + this.connect(); + + }; + + this.disconnectDOM.addEventListener( 'mousedown', onClick, true ); + this.disconnectDOM.addEventListener( 'touchstart', onClick, true ); + this.disconnectDOM.addEventListener( 'disconnect', onDisconnect, true ); + + element.addEventListener( 'connect', onConnect ); + element.addEventListener( 'connectChildren', onConnect ); + element.addEventListener( 'nodeConnect', onConnect ); + element.addEventListener( 'nodeConnectChildren', onConnect ); + element.addEventListener( 'dispose', onDispose ); + + } + + } + + this.dispatchEvent( new Event( 'connect' ) ); + + return true; + + } + + dispose() { + + this.dispatchEvent( new Event( 'dispose' ) ); + + } + + serialize( data ) { + + const height = this.getHeight(); + + const inputs = []; + const links = []; + + for ( const input of this.inputs ) { + + inputs.push( input.toJSON( data ).id ); + + } + + for ( const link of this.links ) { + + if ( link.inputElement !== null && link.outputElement !== null ) { + + links.push( link.outputElement.toJSON( data ).id ); + + } + + } + + if ( this.inputLength > 0 ) data.inputLength = this.inputLength; + if ( this.outputLength > 0 ) data.outputLength = this.outputLength; + + if ( inputs.length > 0 ) data.inputs = inputs; + if ( links.length > 0 ) data.links = links; + + if ( this.style !== '' ) { + + data.style = this.style; + + } + + if ( height !== '' ) { + + data.height = height; + + } + + } + + deserialize( data ) { + + if ( data.inputLength !== undefined ) this.setInput( data.inputLength ); + if ( data.outputLength !== undefined ) this.setOutput( data.outputLength ); + + if ( data.inputs !== undefined ) { + + const inputs = this.inputs; + + if ( inputs.length > 0 ) { + + let index = 0; + + for ( const id of data.inputs ) { + + data.objects[ id ] = inputs[ index ++ ]; + + } + + } else { + + for ( const id of data.inputs ) { + + this.add( data.objects[ id ] ); + + } + + } + + } + + if ( data.links !== undefined ) { + + for ( const id of data.links ) { + + this.connect( data.objects[ id ] ); + + } + + } + + if ( data.style !== undefined ) { + + this.setStyle( data.style ); + + } + + if ( data.height !== undefined ) { + + this.setHeight( data.height ); + + } + + } + + getLinkedObject( output = null ) { + + const linkedElement = this.getLinkedElement(); + + return linkedElement ? linkedElement.getObject( output ) : null; + + } + + getLinkedElement() { + + const link = this.getLink(); + + return link ? link.outputElement : null; + + } + + getLink() { + + return this.links[ 0 ]; + + } + + _createIO( type ) { + + const { dom } = this; + + const ioDOM = document.createElement( 'f-io' ); + ioDOM.style.visibility = 'hidden'; + ioDOM.className = type; + + const onConnectEvent = ( e ) => { + + e.preventDefault(); + + e.stopPropagation(); + + selected = null; + + const nodeDOM = this.node.dom; + + nodeDOM.classList.add( 'io-connect' ); + + ioDOM.classList.add( 'connect' ); + dom.classList.add( 'select' ); + + const defaultOutput = Link.InputDirection === 'left' ? 'lio' : 'rio'; + + const link = type === defaultOutput ? new Link( this ) : new Link( null, this ); + const previewLink = new Link( link.inputElement, link.outputElement ); + + this.links.push( link ); + + draggableDOM( e, ( data ) => { + + if ( previewLink.outputElement ) + previewLink.outputElement.dom.classList.remove( 'invalid' ); + + if ( previewLink.inputElement ) + previewLink.inputElement.dom.classList.remove( 'invalid' ); + + previewLink.inputElement = link.inputElement; + previewLink.outputElement = link.outputElement; + + if ( type === defaultOutput ) { + + previewLink.outputElement = selected; + + } else { + + previewLink.inputElement = selected; + + } + + const isInvalid = previewLink.inputElement !== null && previewLink.outputElement !== null && + previewLink.inputElement.inputLength > 0 && previewLink.outputElement.outputLength > 0 && + dispatchEventList( previewLink.inputElement.events.valid, previewLink.inputElement, previewLink.outputElement, data.dragging ? 'dragging' : 'dragged' ) === false; + + if ( data.dragging && isInvalid ) { + + if ( type === defaultOutput ) { + + if ( previewLink.outputElement ) + previewLink.outputElement.dom.classList.add( 'invalid' ); + + } else { + + if ( previewLink.inputElement ) + previewLink.inputElement.dom.classList.add( 'invalid' ); + + } + + return; + + } + + if ( ! data.dragging ) { + + nodeDOM.classList.remove( 'io-connect' ); + + ioDOM.classList.remove( 'connect' ); + dom.classList.remove( 'select' ); + + this.links.splice( this.links.indexOf( link ), 1 ); + + if ( selected !== null && ! isInvalid ) { + + link.inputElement = previewLink.inputElement; + link.outputElement = previewLink.outputElement; + + // check if is an is circular link + + if ( link.outputElement.node.isCircular( link.inputElement.node ) ) { + + return; + + } + + // + + if ( link.inputElement.inputLength > 0 && link.outputElement.outputLength > 0 ) { + + link.inputElement.connect( link.outputElement ); + + } + + } + + } + + }, 'connecting' ); + + }; + + ioDOM.addEventListener( 'mousedown', onConnectEvent, true ); + ioDOM.addEventListener( 'touchstart', onConnectEvent, true ); + + return ioDOM; + + } + +} + +Element.prototype.isElement = true; + +class Input extends Serializer { + + constructor( dom ) { + + super(); + + this.dom = dom; + + this.element = null; + + this.extra = null; + + this.tagColor = null; + + this.events = { + 'change': [], + 'click': [] + }; + + this.addEventListener( 'change', ( ) => { + + dispatchEventList( this.events.change, this ); + + } ); + + this.addEventListener( 'click', ( ) => { + + dispatchEventList( this.events.click, this ); + + } ); + + } + + setExtra( value ) { + + this.extra = value; + + return this; + + } + + getExtra() { + + return this.extra; + + } + + setTagColor( color ) { + + this.tagColor = color; + + this.dom.style[ 'border-left' ] = `2px solid ${color}`; + + return this; + + } + + getTagColor() { + + return this.tagColor; + + } + + setToolTip( text ) { + + const div = document.createElement( 'f-tooltip' ); + div.innerText = text; + + this.dom.append( div ); + + return this; + + } + + onChange( callback ) { + + this.events.change.push( callback ); + + return this; + + } + + onClick( callback ) { + + this.events.click.push( callback ); + + return this; + + } + + setReadOnly( value ) { + + this.dom.readOnly = value; + + return this; + + } + + getReadOnly() { + + return this.dom.readOnly; + + } + + setValue( value, dispatch = true ) { + + this.dom.value = value; + + if ( dispatch ) this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + getValue() { + + return this.dom.value; + + } + + serialize( data ) { + + data.value = this.getValue(); + + } + + deserialize( data ) { + + this.setValue( data.value ); + + } + +} + +Input.prototype.isInput = true; + +class Node extends Serializer { + + constructor() { + + super(); + + const dom = document.createElement( 'f-node' ); + + const onDown = () => { + + const canvas = this.canvas; + + if ( canvas !== null ) { + + canvas.select( this ); + + } + + }; + + dom.addEventListener( 'mousedown', onDown, true ); + dom.addEventListener( 'touchstart', onDown, true ); + + this._onConnect = ( e ) => { + + const { target } = e; + + for ( const element of this.elements ) { + + if ( element !== target ) { + + element.dispatchEvent( new Event( 'nodeConnect' ) ); + + } + + } + + }; + + this._onConnectChildren = ( e ) => { + + const { target } = e; + + for ( const element of this.elements ) { + + if ( element !== target ) { + + element.dispatchEvent( new Event( 'nodeConnectChildren' ) ); + + } + + } + + }; + + this.dom = dom; + + this.style = ''; + + this.canvas = null; + + this.elements = []; + + this.events = { + 'focus': [], + 'blur': [] + }; + + this.setWidth( 300 ).setPosition( 0, 0 ); + + } + + onFocus( callback ) { + + this.events.focus.push( callback ); + + return this; + + } + + onBlur( callback ) { + + this.events.blur.push( callback ); + + return this; + + } + + setStyle( style ) { + + const dom = this.dom; + + if ( this.style ) dom.classList.remove( this.style ); + + if ( style ) dom.classList.add( style ); + + this.style = style; + + return this; + + } + + setPosition( x, y ) { + + const dom = this.dom; + + dom.style.left = toPX( x ); + dom.style.top = toPX( y ); + + return this; + + } + + getPosition() { + + const dom = this.dom; + + return { + x: parseInt( dom.style.left ), + y: parseInt( dom.style.top ) + }; + + } + + setWidth( val ) { + + this.dom.style.width = toPX( val ); + + return this; + + } + + getWidth() { + + return parseInt( this.dom.style.width ); + + } + + add( element ) { + + this.elements.push( element ); + + element.node = this; + element.addEventListener( 'connect', this._onConnect ); + element.addEventListener( 'connectChildren', this._onConnectChildren ); + + this.dom.append( element.dom ); + + return this; + + } + + remove( element ) { + + this.elements.splice( this.elements.indexOf( element ), 1 ); + + element.node = null; + element.removeEventListener( 'connect', this._onConnect ); + element.removeEventListener( 'connectChildren', this._onConnectChildren ); + + this.dom.removeChild( element.dom ); + + return this; + + } + + dispose() { + + const canvas = this.canvas; + + if ( canvas !== null ) canvas.remove( this ); + + for ( const element of this.elements ) { + + element.dispose(); + + } + + this.dispatchEvent( new Event( 'dispose' ) ); + + } + + isCircular( node ) { + + if ( node === this ) return true; + + const links = this.getLinks(); + + for ( const link of links ) { + + if ( link.outputElement.node.isCircular( node ) ) { + + return true; + + } + + } + + return false; + + } + + getLinks() { + + const links = []; + + for ( const element of this.elements ) { + + links.push( ...element.links ); + + } + + return links; + + } + + serialize( data ) { + + const { x, y } = this.getPosition(); + + const elements = []; + + for ( const element of this.elements ) { + + elements.push( element.toJSON( data ).id ); + + } + + data.x = x; + data.y = y; + data.width = this.getWidth(); + data.elements = elements; + + if ( this.style !== '' ) { + + data.style = this.style; + + } + + } + + deserialize( data ) { + + this.setPosition( data.x, data.y ); + this.setWidth( data.width ); + + if ( data.style !== undefined ) { + + this.setStyle( data.style ); + + } + + const elements = this.elements; + + if ( elements.length > 0 ) { + + let index = 0; + + for ( const id of data.elements ) { + + data.objects[ id ] = elements[ index ++ ]; + + } + + } else { + + for ( const id of data.elements ) { + + this.add( data.objects[ id ] ); + + } + + } + + } + +} + +Node.prototype.isNode = true; + +class DraggableElement extends Element { + + constructor( draggable = true ) { + + super( true ); + + this.draggable = draggable; + + const onDrag = ( e ) => { + + e.preventDefault(); + + if ( this.draggable === true ) { + + draggableDOM( this.node.dom ); + + } + + }; + + const { dom } = this; + + dom.addEventListener( 'mousedown', onDrag, true ); + dom.addEventListener( 'touchstart', onDrag, true ); + + } + +} + +class TitleElement extends DraggableElement { + + constructor( title, draggable = true ) { + + super( draggable ); + + const { dom } = this; + + dom.className = 'title'; + + const spanDOM = document.createElement( 'span' ); + spanDOM.innerText = title; + + const iconDOM = document.createElement( 'i' ); + + const toolbarDOM = document.createElement( 'f-toolbar' ); + + this.buttons = []; + + this.spanDOM = spanDOM; + this.iconDOM = iconDOM; + this.toolbarDOM = toolbarDOM; + + dom.append( spanDOM ); + dom.append( iconDOM ); + dom.append( toolbarDOM ); + + } + + setIcon( value ) { + + this.iconDOM.className = value; + + return this; + + } + + getIcon() { + + return this.iconDOM.className; + + } + + setTitle( value ) { + + this.spanDOM.innerText = value; + + return this; + + } + + getTitle() { + + return this.spanDOM.innerText; + + } + + addButton( button ) { + + this.buttons.push( button ); + + this.toolbarDOM.append( button.dom ); + + return this; + + } + + serialize( data ) { + + super.serialize( data ); + + const title = this.getTitle(); + const icon = this.getIcon(); + + data.title = title; + + if ( icon !== '' ) { + + data.icon = icon; + + } + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.setTitle( data.title ); + + if ( data.icon !== undefined ) { + + this.setIcon( data.icon ); + + } + + } + +} + +const drawLine = ( p1x, p1y, p2x, p2y, invert, size, colorA, ctx, colorB = null ) => { + + const dx = p2x - p1x; + const dy = p2y - p1y; + const offset = Math.sqrt( ( dx * dx ) + ( dy * dy ) ) * ( invert ? - .3 : .3 ); + + ctx.beginPath(); + + ctx.moveTo( p1x, p1y ); + + ctx.bezierCurveTo( + p1x + offset, p1y, + p2x - offset, p2y, + p2x, p2y + ); + + if ( colorB !== null && colorA !== colorB ) { + + const gradient = ctx.createLinearGradient( p1x, p1y, p2x, p2y ); + gradient.addColorStop( 0, colorA ); + gradient.addColorStop( 1, colorB ); + + ctx.strokeStyle = gradient; + + } else { + + ctx.strokeStyle = colorA; + + } + + ctx.lineWidth = size; + ctx.stroke(); + +}; + +const colors = [ + '#ff4444', + '#44ff44', + '#4444ff' +]; + +const dropNode = new Node().add( new TitleElement( 'File' ) ).setWidth( 250 ); + +class Canvas extends Serializer { + + constructor() { + + super(); + + const dom = document.createElement( 'f-canvas' ); + const contentDOM = document.createElement( 'f-content' ); + const areaDOM = document.createElement( 'f-area' ); + const dropDOM = document.createElement( 'f-drop' ); + + const canvas = document.createElement( 'canvas' ); + const frontCanvas = document.createElement( 'canvas' ); + + const context = canvas.getContext( '2d' ); + const frontContext = frontCanvas.getContext( '2d' ); + + this.dom = dom; + + this.contentDOM = contentDOM; + this.areaDOM = areaDOM; + this.dropDOM = dropDOM; + + this.canvas = canvas; + this.frontCanvas = frontCanvas; + + this.context = context; + this.frontContext = frontContext; + + this.width = 10000; + this.height = 10000; + + this.clientX = 0; + this.clientY = 0; + + this.relativeClientX = 0; + this.relativeClientY = 0; + + this.zoom = 1; + + this.nodes = []; + + this.selected = null; + + this.updating = false; + + this.droppedItems = []; + + this.events = { + 'drop': [] + }; + + frontCanvas.className = 'front'; + + contentDOM.style.left = toPX( this.centerX ); + contentDOM.style.top = toPX( this.centerY ); + + areaDOM.style.width = `calc( 100% + ${ this.width }px )`; + areaDOM.style.height = `calc( 100% + ${ this.height }px )`; + + dropDOM.innerHTML = 'drop your file'; + + dom.append( dropDOM ); + dom.append( canvas ); + dom.append( frontCanvas ); + dom.append( contentDOM ); + dom.append( areaDOM ); + /* + let zoomTouchData = null; + + const onZoomStart = () => { + + zoomTouchData = null; + + }; +*/ + const onZoom = ( e ) => { + + if ( e.touches ) { + + if ( e.touches.length === 2 ) { + + e.preventDefault(); + + e.stopImmediatePropagation(); + /* + const clientX = ( e.touches[ 0 ].clientX + e.touches[ 1 ].clientX ) / 2; + const clientY = ( e.touches[ 0 ].clientY + e.touches[ 1 ].clientY ) / 2; + + const distance = Math.hypot( + e.touches[ 0 ].clientX - e.touches[ 1 ].clientX, + e.touches[ 0 ].clientY - e.touches[ 1 ].clientY + ); + + if ( zoomTouchData === null ) { + + zoomTouchData = { + distance + }; + + } + + const delta = ( zoomTouchData.distance - distance ); + zoomTouchData.distance = distance; + + let zoom = Math.min( Math.max( this.zoom - delta * .01, .5 ), 1.2 ); + + if ( zoom < .52 ) zoom = .5; + else if ( zoom > .98 ) zoom = 1; + + contentDOM.style.left = toPX( this.centerX / zoom ); + contentDOM.style.top = toPX( this.centerY / zoom ); + contentDOM.style.zoom = this.zoom = zoom; +*/ + + } + + } else { + + e.preventDefault(); + + e.stopImmediatePropagation(); + /* + const delta = e.deltaY / 100; + const zoom = Math.min( Math.max( this.zoom - delta * .1, .5 ), 1 ); + + contentDOM.style.left = toPX( this.centerX / zoom ); + contentDOM.style.top = toPX( this.centerY / zoom ); + contentDOM.style.zoom = this.zoom = zoom; +*/ + + } + + }; + + dom.addEventListener( 'wheel', onZoom ); + dom.addEventListener( 'touchmove', onZoom ); + //dom.addEventListener( 'touchstart', onZoomStart ); + + let dropEnterCount = 0; + + const dragState = ( enter ) => { + + if ( enter ) { + + if ( dropEnterCount ++ === 0 ) { + + this.droppedItems = []; + + dropDOM.classList.add( 'visible' ); + + this.add( dropNode ); + + } + + } else if ( -- dropEnterCount === 0 ) { + + dropDOM.classList.remove( 'visible' ); + + this.remove( dropNode ); + + } + + }; + + dom.addEventListener( 'dragenter', () => { + + dragState( true ); + + } ); + + dom.addEventListener( 'dragleave', () => { + + dragState( false ); + + } ); + + dom.addEventListener( 'dragover', ( e ) => { + + e.preventDefault(); + + const { relativeClientX, relativeClientY } = this; + + const centerNodeX = dropNode.getWidth() / 2; + + dropNode.setPosition( relativeClientX - centerNodeX, relativeClientY - 20 ); + + } ); + + dom.addEventListener( 'drop', ( e ) => { + + e.preventDefault(); + + dragState( false ); + + this.droppedItems = e.dataTransfer.items; + + dispatchEventList( this.events.drop, this ); + + } ); + + draggableDOM( dom, ( data ) => { + + const { delta, isTouch } = data; + + if ( ! isTouch ) { + + if ( data.scrollTop === undefined ) { + + data.scrollLeft = dom.scrollLeft; + data.scrollTop = dom.scrollTop; + + } + + dom.scrollLeft = data.scrollLeft - delta.x; + dom.scrollTop = data.scrollTop - delta.y; + + } + + if ( data.dragging ) { + + dom.classList.add( 'grabbing' ); + + } else { + + dom.classList.remove( 'grabbing' ); + + } + + }, 'dragging-canvas' ); + + this._onMoveEvent = ( e ) => { + + const event = e.touches ? e.touches[ 0 ] : e; + const { zoom, rect } = this; + + this.clientX = event.clientX; + this.clientY = event.clientY; + + this.relativeClientX = ( ( ( dom.scrollLeft - this.centerX ) + event.clientX ) - rect.left ) / zoom; + this.relativeClientY = ( ( ( dom.scrollTop - this.centerY ) + event.clientY ) - rect.top ) / zoom; + + }; + + this._onContentLoaded = () => { + + this.centralize(); + + }; + + this._onUpdate = () => { + + this.update(); + + }; + + this.start(); + + } + + get rect() { + + return this.dom.getBoundingClientRect(); + + } + + get relativeX() { + + return this.dom.scrollLeft - this.centerX; + + } + + get relativeY() { + + return this.dom.scrollTop - this.centerY; + + } + + get centerX() { + + return this.width / 2; + + } + + get centerY() { + + return this.height / 2; + + } + + onDrop( callback ) { + + this.events.drop.push( callback ); + + return this; + + } + + start() { + + this.updating = true; + + document.addEventListener( 'wheel', this._onMoveEvent, true ); + + document.addEventListener( 'mousedown', this._onMoveEvent, true ); + document.addEventListener( 'touchstart', this._onMoveEvent, true ); + + document.addEventListener( 'mousemove', this._onMoveEvent, true ); + document.addEventListener( 'touchmove', this._onMoveEvent, true ); + + document.addEventListener( 'dragover', this._onMoveEvent, true ); + + document.addEventListener( 'DOMContentLoaded', this._onContentLoaded ); + + requestAnimationFrame( this._onUpdate ); + + } + + stop() { + + this.updating = false; + + document.removeEventListener( 'wheel', this._onMoveEvent, true ); + + document.removeEventListener( 'mousedown', this._onMoveEvent, true ); + document.removeEventListener( 'touchstart', this._onMoveEvent, true ); + + document.removeEventListener( 'mousemove', this._onMoveEvent, true ); + document.removeEventListener( 'touchmove', this._onMoveEvent, true ); + + document.removeEventListener( 'dragover', this._onMoveEvent, true ); + + document.removeEventListener( 'DOMContentLoaded', this._onContentLoaded ); + + } + + add( node ) { + + if ( node.canvas === this ) return; + + this.nodes.push( node ); + + node.canvas = this; + + this.contentDOM.append( node.dom ); + + return this; + + } + + remove( node ) { + + if ( node === this.selected ) { + + this.select(); + + } + + this.unlink( node ); + + const nodes = this.nodes; + + nodes.splice( nodes.indexOf( node ), 1 ); + + node.canvas = null; + + this.contentDOM.removeChild( node.dom ); + + node.dispatchEvent( new Event( 'remove' ) ); + + return this; + + } + + clear() { + + const nodes = this.nodes; + + while ( nodes.length > 0 ) { + + this.remove( nodes[ 0 ] ); + + } + + return this; + + } + + unlink( node ) { + + const links = this.getLinks(); + + for ( const link of links ) { + + if ( link.inputElement && link.outputElement ) { + + if ( link.inputElement.node === node ) { + + link.inputElement.connect(); + + } else if ( link.outputElement.node === node ) { + + link.inputElement.connect(); + + } + + } + + } + + } + + getLinks() { + + const links = []; + + for ( const node of this.nodes ) { + + links.push( ...node.getLinks() ); + + } + + return links; + + } + + centralize() { + + this.dom.scroll( this.centerX, this.centerY ); + + return this; + + } + + select( node = null ) { + + if ( node === this.selected ) return; + + const previousNode = this.selected; + + if ( previousNode !== null ) { + + previousNode.dom.classList.remove( 'selected' ); + + this.selected = null; + + dispatchEventList( previousNode.events.blur, previousNode ); + + } + + if ( node !== null ) { + + node.dom.classList.add( 'selected' ); + + this.selected = node; + + dispatchEventList( node.events.focus, node ); + + } + + } + + update() { + + if ( this.updating === false ) return; + + requestAnimationFrame( this._onUpdate ); + + const { dom, zoom, canvas, frontCanvas, frontContext, context } = this; + + const width = window.innerWidth; + const height = window.innerHeight; + + const domRect = this.rect; + + if ( canvas.width !== width || canvas.height !== height ) { + + canvas.width = width; + canvas.height = height; + + frontCanvas.width = width; + frontCanvas.height = height; + + } + + context.clearRect( 0, 0, width, height ); + frontContext.clearRect( 0, 0, width, height ); + + context.globalCompositeOperation = 'lighter'; + frontContext.globalCompositeOperation = 'source-over'; + + const links = this.getLinks(); + + const aPos = { x: 0, y: 0 }; + const bPos = { x: 0, y: 0 }; + + const offsetIORadius = 10; + + let dragging = ''; + + for ( const link of links ) { + + const { lioElement, rioElement } = link; + + let draggingLink = ''; + let length = 0; + + if ( lioElement !== null ) { + + const rect = lioElement.dom.getBoundingClientRect(); + + length = Math.max( length, lioElement.rioLength ); + + aPos.x = rect.x + rect.width; + aPos.y = rect.y + ( rect.height / 2 ); + + } else { + + aPos.x = this.clientX; + aPos.y = this.clientY; + + draggingLink = 'lio'; + + } + + if ( rioElement !== null ) { + + const rect = rioElement.dom.getBoundingClientRect(); + + length = Math.max( length, rioElement.lioLength ); + + bPos.x = rect.x; + bPos.y = rect.y + ( rect.height / 2 ); + + } else { + + bPos.x = this.clientX; + bPos.y = this.clientY; + + draggingLink = 'rio'; + + } + + dragging = dragging || draggingLink; + + const drawContext = draggingLink ? frontContext : context; + + if ( draggingLink || length === 1 ) { + + let colorA = null, + colorB = null; + + if ( draggingLink === 'rio' ) { + + colorA = colorB = lioElement.getRIOColor(); + + aPos.x += offsetIORadius; + bPos.x /= zoom; + bPos.y /= zoom; + + } else if ( draggingLink === 'lio' ) { + + colorA = colorB = rioElement.getLIOColor(); + + bPos.x -= offsetIORadius; + aPos.x /= zoom; + aPos.y /= zoom; + + } else { + + colorA = lioElement.getRIOColor(); + colorB = rioElement.getLIOColor(); + + } + + drawLine( + aPos.x * zoom, aPos.y * zoom, + bPos.x * zoom, bPos.y * zoom, + false, 2, colorA || '#ffffff', drawContext, colorB || '#ffffff' + ); + + } else { + + length = Math.min( length, 4 ); + + for ( let i = 0; i < length; i ++ ) { + + const color = colors[ i ] || '#ffffff'; + + const marginY = 4; + + const rioLength = Math.min( lioElement.rioLength, length ); + const lioLength = Math.min( rioElement.lioLength, length ); + + const colorA = lioElement.getRIOColor() || color; + const colorB = rioElement.getLIOColor() || color; + + const aCenterY = ( ( rioLength * marginY ) * .5 ) - ( marginY / 2 ); + const bCenterY = ( ( lioLength * marginY ) * .5 ) - ( marginY / 2 ); + + const aIndex = Math.min( i, rioLength - 1 ); + const bIndex = Math.min( i, lioLength - 1 ); + + const aPosY = ( aIndex * marginY ) - 1; + const bPosY = ( bIndex * marginY ) - 1; + + drawLine( + aPos.x * zoom, ( ( aPos.y + aPosY ) - aCenterY ) * zoom, + bPos.x * zoom, ( ( bPos.y + bPosY ) - bCenterY ) * zoom, + false, 2, colorA, drawContext, colorB + ); + + } + + } + + } + + context.globalCompositeOperation = 'destination-in'; + + context.fillRect( domRect.x, domRect.y, domRect.width, domRect.height ); + + if ( dragging !== '' ) { + + dom.classList.add( 'dragging-' + dragging ); + + } else { + + dom.classList.remove( 'dragging-lio' ); + dom.classList.remove( 'dragging-rio' ); + + } + + } + + serialize( data ) { + + const nodes = []; + + for ( const node of this.nodes ) { + + nodes.push( node.toJSON( data ).id ); + + } + + data.nodes = nodes; + + } + + deserialize( data ) { + + for ( const id of data.nodes ) { + + this.add( data.objects[ id ] ); + + } + + } + +} + +class ButtonInput extends Input { + + constructor( innterText = '' ) { + + const dom = document.createElement( 'button' ); + + const spanDOM = document.createElement( 'span' ); + dom.append( spanDOM ); + + const iconDOM = document.createElement( 'i' ); + dom.append( iconDOM ); + + super( dom ); + + this.spanDOM = spanDOM; + this.iconDOM = iconDOM; + + spanDOM.innerText = innterText; + + dom.onmouseover = () => { + + this.dispatchEvent( new Event( 'mouseover' ) ); + + }; + + dom.onclick = dom.ontouchstart = ( e ) => { + + e.preventDefault(); + + e.stopPropagation(); + + this.dispatchEvent( new Event( 'click' ) ); + + }; + + } + + setIcon( className ) { + + this.iconDOM.className = className; + + return this; + + } + + setValue( val ) { + + this.spanDOM.innerText = val; + + return this; + + } + + getValue() { + + return this.spanDOM.innerText; + + } + +} + +class ObjectNode extends Node { + + constructor( name, inputLength, callback = null, width = 300 ) { + + super(); + + this.setWidth( width ); + + const title = new TitleElement( name ) + .setObjectCallback( callback ) + .setSerializable( false ) + .setOutput( inputLength ); + + const closeButton = new ButtonInput( Styles.icons.close || '✕' ).onClick( () => { + + this.dispose(); + + } ).setIcon( Styles.icons.close ); + + title.addButton( closeButton ); + + this.add( title ); + + this.title = title; + this.closeButton = closeButton; + + } + + setName( value ) { + + this.title.setTitle( value ); + + return this; + + } + + getName() { + + return this.title.getTitle(); + + } + + setObjectCallback( callback ) { + + this.title.setObjectCallback( callback ); + + return this; + + } + + getObject( callback ) { + + return this.title.getObject( callback ); + + } + + setColor( color ) { + + return this.title.setColor( color ); + + } + + setOutputColor( color ) { + + return this.title.setOutputColor( color ); + + } + + invalidate() { + + this.title.dispatchEvent( new Event( 'connect' ) ); + + } + +} + +const ENTER_KEY$1 = 13; + +class StringInput extends Input { + + constructor( value = '' ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + dom.type = 'text'; + dom.value = value; + dom.spellcheck = false; + dom.autocomplete = 'off'; + + dom.onblur = () => { + + this.dispatchEvent( new Event( 'blur' ) ); + + }; + + dom.onchange = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + dom.onkeyup = ( e ) => { + + if ( e.keyCode === ENTER_KEY$1 ) { + + e.target.blur(); + + } + + e.stopPropagation(); + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + } + +} + +const ENTER_KEY = 13; + +class NumberInput extends Input { + + constructor( value = 0, min = - Infinity, max = Infinity, step = .01 ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + this.min = min; + this.max = max; + this.step = step; + + this.integer = false; + + dom.type = 'text'; + dom.className = 'number'; + dom.value = this._getString( value ); + dom.spellcheck = false; + dom.autocomplete = 'off'; + + dom.ondragstart = dom.oncontextmenu = ( e ) => { + + e.preventDefault(); + + e.stopPropagation(); + + }; + + dom.onfocus = dom.onclick = () => { + + dom.select(); + + }; + + dom.onblur = () => { + + this.dispatchEvent( new Event( 'blur' ) ); + + }; + + dom.onchange = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + dom.onkeydown = ( e ) => { + + if ( e.key.length === 1 && /\d|\./.test( e.key ) !== true ) { + + return false; + + } + + if ( e.keyCode === ENTER_KEY ) { + + e.target.blur(); + + } + + e.stopPropagation(); + + }; + + draggableDOM( dom, ( data ) => { + + const { delta } = data; + + if ( data.value === undefined ) { + + data.value = this.getValue(); + + } + + const diff = delta.x - delta.y; + + const value = data.value + ( diff * this.step ); + + this.dom.value = this._getString( value.toFixed( this.precision ) ); + + this.dispatchEvent( new Event( 'change' ) ); + + } ); + + } + + setStep( step ) { + + this.step = step; + + return this; + + } + + setRange( min, max, step ) { + + this.min = min; + this.max = max; + this.step = step; + + this.dispatchEvent( new Event( 'range' ) ); + + return this.setValue( this.getValue() ); + + } + + get precision() { + + if ( this.integer === true ) return 0; + + const fract = this.step % 1; + + return fract !== 0 ? fract.toString().split( '.' )[ 1 ].length : 1; + + } + + setValue( val, dispatch = true ) { + + return super.setValue( this._getString( val ), dispatch ); + + } + + getValue() { + + return Number( this.dom.value ); + + } + + serialize( data ) { + + const { min, max } = this; + + if ( min !== - Infinity && max !== Infinity ) { + + data.min = this.min; + data.max = this.max; + data.step = this.step; + + } + + super.serialize( data ); + + } + + deserialize( data ) { + + if ( data.min !== undefined ) { + + const { min, max, step } = this; + + this.setRange( min, max, step ); + + } + + super.deserialize( data ); + + } + + _getString( value ) { + + const num = Math.min( Math.max( Number( value ), this.min ), this.max ); + + if ( this.integer === true ) { + + return Math.floor( num ); + + } else { + + return num + ( num % 1 ? '' : '.0' ); + + } + + } + +} + +const getStep = ( min, max ) => { + + const sensibility = .001; + + return ( max - min ) * sensibility; + +}; + +class SliderInput extends Input { + + constructor( value = 0, min = 0, max = 100 ) { + + const dom = document.createElement( 'f-subinputs' ); + super( dom ); + + value = Math.min( Math.max( value, min ), max ); + + const step = getStep( min, max ); + + const rangeDOM = document.createElement( 'input' ); + rangeDOM.type = 'range'; + rangeDOM.min = min; + rangeDOM.max = max; + rangeDOM.step = step; + rangeDOM.value = value; + + const field = new NumberInput( value, min, max, step ); + field.dom.className = 'range-value'; + field.onChange( () => { + + rangeDOM.value = field.getValue(); + + } ); + + field.addEventListener( 'range', () => { + + rangeDOM.min = field.min; + rangeDOM.max = field.max; + rangeDOM.step = field.step; + rangeDOM.value = field.getValue(); + + } ); + + dom.append( rangeDOM ); + dom.append( field.dom ); + + this.rangeDOM = rangeDOM; + this.field = field; + + const updateRangeValue = () => { + + let value = Number( rangeDOM.value ); + + if ( value !== this.max && value + this.step >= this.max ) { + + // fix not end range fraction + + rangeDOM.value = value = this.max; + + } + + this.field.setValue( value ); + + }; + + draggableDOM( rangeDOM, () => { + + updateRangeValue(); + + this.dispatchEvent( new Event( 'change' ) ); + + }, '' ); + + } + + get min() { + + return this.field.min; + + } + + get max() { + + return this.field.max; + + } + + get step() { + + return this.field.step; + + } + + setRange( min, max ) { + + this.field.setRange( min, max, getStep( min, max ) ); + + this.dispatchEvent( new Event( 'range' ) ); + this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + setValue( val, dispatch = true ) { + + this.field.setValue( val ); + this.rangeDOM.value = val; + + if ( dispatch ) this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + getValue() { + + return this.field.getValue(); + + } + + serialize( data ) { + + data.min = this.min; + data.max = this.max; + + super.serialize( data ); + + } + + deserialize( data ) { + + const { min, max } = data; + + this.setRange( min, max ); + + super.deserialize( data ); + + } + +} + +class ColorInput extends Input { + + constructor( value = 0x0099ff ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + dom.type = 'color'; + dom.value = toHex( value ); + + dom.oninput = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + } + + setValue( value, dispatch = true ) { + + return super.setValue( toHex( value ), dispatch ); + + } + + getValue() { + + return parseInt( super.getValue().slice( 1 ), 16 ); + + } + +} + +class TextInput extends Input { + + constructor( innerText = '' ) { + + const dom = document.createElement( 'textarea' ); + super( dom ); + + dom.innerText = innerText; + + } + + setValue( val ) { + + this.dom.innerText = val; + + return this; + + } + + getValue() { + + return this.dom.innerText; + + } + +} + +class LabelElement extends Element { + + constructor( label = '', align = '' ) { + + super(); + + this.labelDOM = document.createElement( 'f-label' ); + this.inputsDOM = document.createElement( 'f-inputs' ); + + const spanDOM = document.createElement( 'span' ); + const iconDOM = document.createElement( 'i' ); + + this.spanDOM = spanDOM; + this.iconDOM = iconDOM; + + this.labelDOM.append( this.spanDOM ); + this.labelDOM.append( this.iconDOM ); + + this.dom.append( this.labelDOM ); + this.dom.append( this.inputsDOM ); + + this.serializeLabel = false; + + this.setLabel( label ); + this.setAlign( align ); + + } + + setIcon( value ) { + + this.iconDOM.className = value; + + return this; + + } + + getIcon() { + + return this.iconDOM.className; + + } + + setAlign( align ) { + + this.labelDOM.className = align; + + } + + setLabel( val ) { + + this.spanDOM.innerText = val; + + } + + getLabel() { + + return this.spanDOM.innerText; + + } + + serialize( data ) { + + super.serialize( data ); + + if ( this.serializeLabel ) { + + const label = this.getLabel(); + const icon = this.getIcon(); + + data.label = label; + + if ( icon !== '' ) { + + data.icon = icon; + + } + + } + + } + + deserialize( data ) { + + super.deserialize( data ); + + if ( this.serializeLabel ) { + + this.setLabel( data.label ); + + if ( data.icon !== undefined ) { + + this.setIcon( data.icon ); + + } + + } + + } + +} + +class PanelNode extends Node { + + constructor( title = 'Panel', align = 'top-right' ) { + + super(); + + const titleElement = new TitleElement( title ); + this.add( titleElement ); + + const collapseButton = new ButtonInput( '🗕' ); + collapseButton.onClick( () => { + + this.setCollapse( ! this.collapsed ); + + } ); + + titleElement.addButton( collapseButton ); + + this.collapseButton = collapseButton; + this.titleElement = titleElement; + this.align = align; + this.collapsed = false; + + this.setAlign( align ); + this.setStyle( 'rouded' ); + + } + + setCollapse( value ) { + + const cssClass = 'closed'; + + this.dom.classList.remove( cssClass ); + + this.collapsed = value; + + this.collapseButton.value = value ? '🗖' : '🗕'; + + if ( value === true ) { + + this.dom.classList.add( cssClass ); + + } + + return this; + + } + + setAlign( align ) { + + if ( this.align ) this.dom.classList.remove( this.align ); + this.dom.classList.add( align ); + + this.align = align; + + return this; + + } + + addInput( inputClass, object, property, ...params ) { + + const value = object[ property ]; + + const input = new inputClass( value, ...params ); + input.onChange( () => { + + object[ property ] = input.value; + + } ); + + this.add( new LabelElement( property ).add( input ) ); + + return input; + + } + + addSlider( object, property, min, max ) { + + return this.addInput( SliderInput, object, property, min, max ); + + } + + addNumber( object, property ) { + + return this.addInput( NumberInput, object, property ); + + } + + addColor( object, property ) { + + return this.addInput( ColorInput, object, property ); + + } + + addString( object, property ) { + + return this.addInput( StringInput, object, property ); + + } + + addText( object, property ) { + + const input = this.addInput( TextInput, object, property ); + input.element.setHeight( 70 ); + + return input; + + } + + addButton( name ) { + + const input = new ButtonInput( name ); + + this.add( new Element().setHeight( 34 ).add( input ) ); + + return input; + + } + +} + +class Menu extends EventTarget { + + constructor( className ) { + + super(); + + const dom = document.createElement( 'f-menu' ); + dom.className = className + ' hidden'; + + const listDOM = document.createElement( 'f-list' ); + + dom.append( listDOM ); + + this.dom = dom; + this.listDOM = listDOM; + + this.visible = false; + + this.subMenus = new WeakMap(); + this.domButtons = new WeakMap(); + + this.buttons = []; + + this.events = {}; + + } + + onContext( callback ) { + + this.events.context.push( callback ); + + return this; + + } + + show() { + + this.dom.classList.remove( 'hidden' ); + + this.visible = true; + + this.dispatchEvent( new Event( 'show' ) ); + + return this; + + } + + hide() { + + this.dom.classList.add( 'hidden' ); + + this.dispatchEvent( new Event( 'hide' ) ); + + this.visible = false; + + } + + add( button, submenu = null ) { + + const liDOM = document.createElement( 'f-item' ); + + if ( submenu !== null ) { + + liDOM.classList.add( 'submenu' ); + + liDOM.append( submenu.dom ); + + this.subMenus.set( button, submenu ); + + button.dom.addEventListener( 'mouseover', () => submenu.show() ); + button.dom.addEventListener( 'mouseout', () => submenu.hide() ); + + } + + liDOM.append( button.dom ); + + this.buttons.push( button ); + + this.listDOM.append( liDOM ); + + this.domButtons.set( button, liDOM ); + + return this; + + } + + clear() { + + this.buttons = []; + + this.subMenus = new WeakMap(); + this.domButtons = new WeakMap(); + + while ( this.listDOM.firstChild ) { + + this.listDOM.firstChild.remove(); + + } + + } + +} + +let lastContext = null; + +const onCloseLastContext = ( e ) => { + + if ( lastContext && lastContext.visible === true && e.target.closest( 'f-menu.context' ) === null ) { + + lastContext.hide(); + + } + +}; + +document.body.addEventListener( 'mousedown', onCloseLastContext, true ); +document.body.addEventListener( 'touchstart', onCloseLastContext, true ); + +class ContextMenu extends Menu { + + constructor( target = null ) { + + super( 'context', target ); + + this.events.context = []; + + this._lastButtonClick = null; + + this._onButtonClick = ( e = null ) => { + + const button = e ? e.target : null; + + if ( this._lastButtonClick ) { + + this._lastButtonClick.dom.parentElement.classList.remove( 'active' ); + + } + + this._lastButtonClick = button; + + if ( button ) { + + if ( this.subMenus.has( button ) ) { + + this.subMenus.get( button )._onButtonClick(); + + } + + button.dom.parentElement.classList.add( 'active' ); + + } + + }; + + this._onButtonMouseOver = ( e ) => { + + const button = e.target; + + if ( this.subMenus.has( button ) && this._lastButtonClick !== button ) { + + this._onButtonClick(); + + } + + }; + + this.addEventListener( 'context', ( ) => { + + dispatchEventList( this.events.context, this ); + + } ); + + this.setTarget( target ); + + } + + openFrom( dom ) { + + const rect = dom.getBoundingClientRect(); + + return this.open( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ) ); + + } + + open( x = pointer.x, y = pointer.y ) { + + if ( lastContext !== null ) { + + lastContext.hide(); + + } + + lastContext = this; + + this.setPosition( x, y ); + + document.body.append( this.dom ); + + return this.show(); + + } + + setPosition( x, y ) { + + const dom = this.dom; + + dom.style.left = toPX( x ); + dom.style.top = toPX( y ); + + return this; + + } + + setTarget( target = null ) { + + if ( target !== null ) { + + const onContextMenu = ( e ) => { + + e.preventDefault(); + + if ( e.pointerType !== 'mouse' || ( e.pageX === 0 && e.pageY === 0 ) ) return; + + this.dispatchEvent( new Event( 'context' ) ); + + this.open(); + + }; + + this.target = target; + + target.addEventListener( 'contextmenu', onContextMenu, false ); + + } + + return this; + + } + + show() { + + if ( ! this.opened ) { + + this.dom.style.left = ''; + this.dom.style.transform = ''; + + } + + const domRect = this.dom.getBoundingClientRect(); + + let offsetX = Math.min( window.innerWidth - ( domRect.x + domRect.width + 10 ), 0 ); + let offsetY = Math.min( window.innerHeight - ( domRect.y + domRect.height + 10 ), 0 ); + + if ( this.opened ) { + + if ( offsetX < 0 ) offsetX = - domRect.width; + if ( offsetY < 0 ) offsetY = - domRect.height; + + this.setPosition( domRect.x + offsetX, domRect.y + offsetY ); + + } else { + + // flip submenus + + if ( offsetX < 0 ) this.dom.style.left = '-100%'; + if ( offsetY < 0 ) this.dom.style.transform = 'translateY( calc( 32px - 100% ) )'; + + } + + return super.show(); + + } + + hide() { + + if ( this.opened ) { + + lastContext = null; + + } + + return super.hide(); + + } + + add( button, submenu = null ) { + + button.addEventListener( 'click', this._onButtonClick ); + button.addEventListener( 'mouseover', this._onButtonMouseOver ); + + return super.add( button, submenu ); + + } + + get opened() { + + return lastContext === this; + + } + +} + +class CircleMenu extends Menu { + + constructor( target = null ) { + + super( 'circle', target ); + + } + +} + +class Tips extends EventTarget { + + constructor() { + + super(); + + const dom = document.createElement( 'f-tips' ); + + this.dom = dom; + + this.time = 0; + this.duration = 3000; + + } + + message( str ) { + + return this.tip( str ); + + } + + error( str ) { + + return this.tip( str, 'error' ); + + } + + tip( html, className = '' ) { + + const dom = document.createElement( 'f-tip' ); + dom.className = className; + dom.innerHTML = html; + + this.dom.prepend( dom ); + + //requestAnimationFrame( () => dom.style.opacity = 1 ); + + this.time = Math.min( this.time + this.duration, this.duration ); + + setTimeout( () => { + + this.time -= this.duration; + + dom.style.opacity = 0; + + setTimeout( () => dom.remove(), 250 ); + + }, this.time ); + + return this; + + } + +} + +const filterString = ( str ) => { + + return str.trim().toLowerCase().replace( /\s\s+/g, ' ' ); + +}; + +class Search extends Menu { + + constructor() { + + super( 'search' ); + + this.events.submit = []; + this.events.filter = []; + + const inputDOM = document.createElement( 'input' ); + inputDOM.placeholder = 'Type here'; + + let filter = true; + let filterNeedUpdate = true; + + inputDOM.addEventListener( 'focusout', () => { + + filterNeedUpdate = true; + + this.setValue( '' ); + + } ); + + inputDOM.onkeydown = ( e ) => { + + const keyCode = e.keyCode; + + if ( keyCode === 38 ) { + + const index = this.filteredIndex; + + if ( this.forceAutoComplete ) { + + this.filteredIndex = index !== null ? ( index + 1 ) % ( this.filtered.length || 1 ) : 0; + + } else { + + this.filteredIndex = index !== null ? Math.min( index + 1, this.filtered.length - 1 ) : 0; + + } + + e.preventDefault(); + + filter = false; + + } else if ( keyCode === 40 ) { + + const index = this.filteredIndex; + + if ( this.forceAutoComplete ) { + + this.filteredIndex = index - 1; + + if ( this.filteredIndex === null ) this.filteredIndex = this.filtered.length - 1; + + } else { + + this.filteredIndex = index !== null ? index - 1 : null; + + } + + e.preventDefault(); + + filter = false; + + } else if ( keyCode === 13 ) { + + this.value = this.currentFiltered ? this.currentFiltered.button.getValue() : inputDOM.value; + + this.submit(); + + e.preventDefault(); + + filter = false; + + } else { + + filter = true; + + } + + }; + + inputDOM.onkeyup = () => { + + if ( filter ) { + + if ( filterNeedUpdate ) { + + this.dispatchEvent( new Event( 'filter' ) ); + + filterNeedUpdate = false; + + } + + this.filter( inputDOM.value ); + + } + + }; + + this.filtered = []; + this.currentFiltered = null; + + this.value = ''; + + this.forceAutoComplete = false; + + this.dom.append( inputDOM ); + + this.inputDOM = inputDOM; + + this.addEventListener( 'filter', ( ) => { + + dispatchEventList( this.events.filter, this ); + + } ); + + this.addEventListener( 'submit', ( ) => { + + dispatchEventList( this.events.submit, this ); + + } ); + + } + + submit() { + + this.dispatchEvent( new Event( 'submit' ) ); + + return this.setValue( '' ); + + } + + setValue( value ) { + + this.inputDOM.value = value; + + this.filter( value ); + + return this; + + } + + getValue() { + + return this.value; + + } + + onFilter( callback ) { + + this.events.filter.push( callback ); + + return this; + + } + + onSubmit( callback ) { + + this.events.submit.push( callback ); + + return this; + + } + + getFilterByButton( button ) { + + for ( const filter of this.filtered ) { + + if ( filter.button === button ) { + + return filter; + + } + + } + + return null; + + } + + add( button ) { + + super.add( button ); + + const onDown = () => { + + const filter = this.getFilterByButton( button ); + + this.filteredIndex = this.filtered.indexOf( filter ); + this.value = button.getValue(); + + this.submit(); + + }; + + button.dom.addEventListener( 'mousedown', onDown ); + button.dom.addEventListener( 'touchstart', onDown ); + + this.domButtons.get( button ).remove(); + + return this; + + } + + set filteredIndex( index ) { + + if ( this.currentFiltered ) { + + const buttonDOM = this.domButtons.get( this.currentFiltered.button ); + + buttonDOM.classList.remove( 'active' ); + + this.currentFiltered = null; + + } + + const filteredItem = this.filtered[ index ]; + + if ( filteredItem ) { + + const buttonDOM = this.domButtons.get( filteredItem.button ); + + buttonDOM.classList.add( 'active' ); + + this.currentFiltered = filteredItem; + + } + + this.updateFilter(); + + } + + get filteredIndex() { + + return this.currentFiltered ? this.filtered.indexOf( this.currentFiltered ) : null; + + } + + filter( text ) { + + text = filterString( text ); + + const filtered = []; + + for ( const button of this.buttons ) { + + const buttonDOM = this.domButtons.get( button ); + + buttonDOM.remove(); + + const label = filterString( button.getValue() ); + + if ( text && label.includes( text ) === true ) { + + const score = text.length / label.length; + + filtered.push( { + button, + score + } ); + + } + + } + + filtered.sort( ( a, b ) => b.score - a.score ); + + this.filtered = filtered; + this.filteredIndex = this.forceAutoComplete ? 0 : null; + + } + + updateFilter() { + + const filteredIndex = Math.min( this.filteredIndex, this.filteredIndex - 3 ); + + for ( let i = 0; i < this.filtered.length; i ++ ) { + + const button = this.filtered[ i ].button; + const buttonDOM = this.domButtons.get( button ); + + buttonDOM.remove(); + + if ( i >= filteredIndex ) { + + this.listDOM.append( buttonDOM ); + + } + + } + + } + +} + +class SelectInput extends Input { + + constructor( options = [], value = null ) { + + const dom = document.createElement( 'select' ); + super( dom ); + + dom.onchange = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + dom.onmousedown = dom.ontouchstart = () => { + + this.dispatchEvent( new Event( 'click' ) ); + + }; + + this.setOptions( options, value ); + + } + + setOptions( options, value = null ) { + + const dom = this.dom; + const defaultValue = dom.value; + + let containsDefaultValue = false; + + this.options = options; + dom.innerHTML = ''; + + for ( let index = 0; index < options.length; index ++ ) { + + let opt = options[ index ]; + + if ( typeof opt === 'string' ) { + + opt = { name: opt, value: index }; + + } + + const option = document.createElement( 'option' ); + option.innerText = opt.name; + option.value = opt.value; + + if ( containsDefaultValue === false && defaultValue === opt.value ) { + + containsDefaultValue = true; + + } + + dom.append( option ); + + } + + dom.value = value !== null ? value : containsDefaultValue ? defaultValue : ''; + + return this; + + } + + getOptions() { + + return this._options; + + } + + serialize( data ) { + + data.options = [ ...this.options ]; + + super.serialize( data ); + + } + + deserialize( data ) { + + const currentOptions = this.options; + + if ( currentOptions.length === 0 ) { + + this.setOptions( data.options ); + + } + + super.deserialize( data ); + + } + +} + +class ToggleInput extends Input { + + constructor( value = false ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + dom.type = 'checkbox'; + dom.className = 'toggle'; + dom.checked = value; + + dom.onclick = () => this.dispatchEvent( new Event( 'click' ) ); + dom.onchange = () => this.dispatchEvent( new Event( 'change' ) ); + + } + + setValue( val ) { + + this.dom.checked = val; + + this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + getValue() { + + return this.dom.checked; + + } + +} + +var Flow = /*#__PURE__*/Object.freeze( { + __proto__: null, + Element: Element, + Input: Input, + Node: Node, + Canvas: Canvas, + Serializer: Serializer, + Styles: Styles, + ObjectNode: ObjectNode, + PanelNode: PanelNode, + Menu: Menu, + ContextMenu: ContextMenu, + CircleMenu: CircleMenu, + Tips: Tips, + Search: Search, + DraggableElement: DraggableElement, + LabelElement: LabelElement, + TitleElement: TitleElement, + ButtonInput: ButtonInput, + ColorInput: ColorInput, + NumberInput: NumberInput, + SelectInput: SelectInput, + SliderInput: SliderInput, + StringInput: StringInput, + TextInput: TextInput, + ToggleInput: ToggleInput +} ); + +class Loader extends EventTarget { + + constructor( parseType = Loader.DEFAULT ) { + + super(); + + this.parseType = parseType; + + this.events = { + 'load': [] + }; + + } + + setParseType( type ) { + + this.parseType = type; + + return this; + + } + + getParseType() { + + return this.parseType; + + } + + onLoad( callback ) { + + this.events.load.push( callback ); + + return this; + + } + + async load( url, lib = null ) { + + return await fetch( url ) + .then( response => response.json() ) + .then( result => { + + this.data = this.parse( result, lib ); + + dispatchEventList( this.events.load, this ); + + return this.data; + + } ) + .catch( err => { + + console.error( 'Loader:', err ); + + } ); + + } + + parse( json, lib = null ) { + + json = this._parseObjects( json, lib ); + + const parseType = this.parseType; + + if ( parseType === Loader.DEFAULT ) { + + const flowObj = new Flow[ json.type ](); + + if ( flowObj.getSerializable() ) { + + flowObj.deserialize( json ); + + } + + return flowObj; + + } else if ( parseType === Loader.OBJECTS ) { + + return json; + + } + + } + + _parseObjects( json, lib = null ) { + + json = { ...json }; + + const objects = {}; + + for ( const id in json.objects ) { + + const obj = json.objects[ id ]; + obj.objects = objects; + + const Class = lib && lib[ obj.type ] ? lib[ obj.type ] : Flow[ obj.type ]; + + if ( ! Class ) { + + console.error( `Class "${ obj.type }" not found!` ); + + } + + objects[ id ] = new Class(); + + } + + const ref = new WeakMap(); + + const deserializePass = ( prop = null ) => { + + for ( const id in json.objects ) { + + const newObject = objects[ id ]; + + if ( ref.has( newObject ) === false && ( prop === null || newObject[ prop ] === true ) ) { + + ref.set( newObject, true ); + + if ( newObject.getSerializable() ) { + + newObject.deserialize( json.objects[ id ] ); + + } + + } + + } + + }; + + deserializePass( 'isNode' ); + deserializePass( 'isElement' ); + deserializePass( 'isInput' ); + deserializePass(); + + json.objects = objects; + + return json; + + } + +} + +Loader.DEFAULT = 'default'; +Loader.OBJECTS = 'objects'; + +export { ButtonInput, Canvas, CircleMenu, ColorInput, ContextMenu, DraggableElement, Element, Input, LabelElement, Loader, Menu, Node, NumberInput, ObjectNode, PanelNode, REVISION, Search, SelectInput, Serializer, SliderInput, StringInput, Styles, TextInput, Tips, TitleElement, ToggleInput, Utils }; diff --git a/jsm/libs/ktx-parse.module.js b/jsm/libs/ktx-parse.module.js new file mode 100644 index 0000000..c644054 --- /dev/null +++ b/jsm/libs/ktx-parse.module.js @@ -0,0 +1 @@ +const t=new Uint8Array([0]),e=[171,75,84,88,32,50,48,187,13,10,26,10];var n,i,s,a,r,o,l,f;!function(t){t[t.NONE=0]="NONE",t[t.BASISLZ=1]="BASISLZ",t[t.ZSTD=2]="ZSTD",t[t.ZLIB=3]="ZLIB"}(n||(n={})),function(t){t[t.BASICFORMAT=0]="BASICFORMAT"}(i||(i={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.ETC1S=163]="ETC1S",t[t.UASTC=166]="UASTC"}(s||(s={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.SRGB=1]="SRGB"}(a||(a={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.LINEAR=1]="LINEAR",t[t.SRGB=2]="SRGB",t[t.ITU=3]="ITU",t[t.NTSC=4]="NTSC",t[t.SLOG=5]="SLOG",t[t.SLOG2=6]="SLOG2"}(r||(r={})),function(t){t[t.ALPHA_STRAIGHT=0]="ALPHA_STRAIGHT",t[t.ALPHA_PREMULTIPLIED=1]="ALPHA_PREMULTIPLIED"}(o||(o={})),function(t){t[t.RGB=0]="RGB",t[t.RRR=3]="RRR",t[t.GGG=4]="GGG",t[t.AAA=15]="AAA"}(l||(l={})),function(t){t[t.RGB=0]="RGB",t[t.RGBA=3]="RGBA",t[t.RRR=4]="RRR",t[t.RRRG=5]="RRRG"}(f||(f={}));class U{constructor(){this.vkFormat=0,this.typeSize=1,this.pixelWidth=0,this.pixelHeight=0,this.pixelDepth=0,this.layerCount=0,this.faceCount=1,this.supercompressionScheme=n.NONE,this.levels=[],this.dataFormatDescriptor=[{vendorId:0,descriptorType:i.BASICFORMAT,versionNumber:2,descriptorBlockSize:40,colorModel:s.UNSPECIFIED,colorPrimaries:a.SRGB,transferFunction:a.SRGB,flags:o.ALPHA_STRAIGHT,texelBlockDimension:{x:4,y:4,z:1,w:1},bytesPlane:[],samples:[]}],this.keyValue={},this.globalData=null}}class c{constructor(t,e,n,i){this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}_nextUint8(){const t=this._dataView.getUint8(this._offset);return this._offset+=1,t}_nextUint16(){const t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t}_nextUint32(){const t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t}_nextUint64(){const t=this._dataView.getUint32(this._offset,this._littleEndian)+2**32*this._dataView.getUint32(this._offset+4,this._littleEndian);return this._offset+=8,t}_skip(t){return this._offset+=t,this}_scan(t,e=0){const n=this._offset;let i=0;for(;this._dataView.getUint8(this._offset)!==e&&i{this.setValue(this.$input.checked),this._callOnFinishChange()}),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.checked=this.getValue(),this}}function e(t){let i,e;return(i=t.match(/(#|0x)?([a-f0-9]{6})/i))?e=i[2]:(i=t.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/))?e=parseInt(i[1]).toString(16).padStart(2,0)+parseInt(i[2]).toString(16).padStart(2,0)+parseInt(i[3]).toString(16).padStart(2,0):(i=t.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i))&&(e=i[1]+i[1]+i[2]+i[2]+i[3]+i[3]),!!e&&"#"+e}const s={isPrimitive:!0,match:t=>"string"==typeof t,fromHexString:e,toHexString:e},n={isPrimitive:!0,match:t=>"number"==typeof t,fromHexString:t=>parseInt(t.substring(1),16),toHexString:t=>"#"+t.toString(16).padStart(6,0)},r={isPrimitive:!1,match:Array.isArray,fromHexString(t,i,e=1){const s=n.fromHexString(t);i[0]=(s>>16&255)/255*e,i[1]=(s>>8&255)/255*e,i[2]=(255&s)/255*e},toHexString:([t,i,e],s=1)=>n.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},l={isPrimitive:!1,match:t=>Object(t)===t,fromHexString(t,i,e=1){const s=n.fromHexString(t);i.r=(s>>16&255)/255*e,i.g=(s>>8&255)/255*e,i.b=(255&s)/255*e},toHexString:({r:t,g:i,b:e},s=1)=>n.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},o=[s,n,r,l];class a extends t{constructor(t,i,s,n){var r;super(t,i,s,"color"),this.$input=document.createElement("input"),this.$input.setAttribute("type","color"),this.$input.setAttribute("tabindex",-1),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$text=document.createElement("input"),this.$text.setAttribute("type","text"),this.$text.setAttribute("spellcheck","false"),this.$text.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this.$display.appendChild(this.$input),this.$widget.appendChild(this.$display),this.$widget.appendChild(this.$text),this._format=(r=this.initialValue,o.find(t=>t.match(r))),this._rgbScale=n,this._initialValueHexString=this.save(),this._textFocused=!1,this.$input.addEventListener("input",()=>{this._setValueFromHexString(this.$input.value)}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$text.addEventListener("input",()=>{const t=e(this.$text.value);t&&this._setValueFromHexString(t)}),this.$text.addEventListener("focus",()=>{this._textFocused=!0,this.$text.select()}),this.$text.addEventListener("blur",()=>{this._textFocused=!1,this.updateDisplay(),this._callOnFinishChange()}),this.$disable=this.$text,this.updateDisplay()}reset(){return this._setValueFromHexString(this._initialValueHexString),this}_setValueFromHexString(t){if(this._format.isPrimitive){const i=this._format.fromHexString(t);this.setValue(i)}else this._format.fromHexString(t,this.getValue(),this._rgbScale),this._callOnChange(),this.updateDisplay()}save(){return this._format.toHexString(this.getValue(),this._rgbScale)}load(t){return this._setValueFromHexString(t),this._callOnFinishChange(),this}updateDisplay(){return this.$input.value=this._format.toHexString(this.getValue(),this._rgbScale),this._textFocused||(this.$text.value=this.$input.value.substring(1)),this.$display.style.backgroundColor=this.$input.value,this}}class h extends t{constructor(t,i,e){super(t,i,e,"function"),this.$button=document.createElement("button"),this.$button.appendChild(this.$name),this.$widget.appendChild(this.$button),this.$button.addEventListener("click",t=>{t.preventDefault(),this.getValue().call(this.object)}),this.$button.addEventListener("touchstart",()=>{}),this.$disable=this.$button}}class d extends t{constructor(t,i,e,s,n,r){super(t,i,e,"number"),this._initInput(),this.min(s),this.max(n);const l=void 0!==r;this.step(l?r:this._getImplicitStep(),l),this.updateDisplay()}min(t){return this._min=t,this._onUpdateMinMax(),this}max(t){return this._max=t,this._onUpdateMinMax(),this}step(t,i=!0){return this._step=t,this._stepExplicit=i,this}updateDisplay(){const t=this.getValue();if(this._hasSlider){let i=(t-this._min)/(this._max-this._min);i=Math.max(0,Math.min(i,1)),this.$fill.style.width=100*i+"%"}return this._inputFocused||(this.$input.value=t),this}_initInput(){this.$input=document.createElement("input"),this.$input.setAttribute("type","number"),this.$input.setAttribute("step","any"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$widget.appendChild(this.$input),this.$disable=this.$input;const t=t=>{const i=parseFloat(this.$input.value);isNaN(i)||(this._snapClampSetValue(i+t),this.$input.value=this.getValue())};let i,e,s,n,r,l=!1;const o=t=>{if(l){const s=t.clientX-i,n=t.clientY-e;Math.abs(n)>5?(t.preventDefault(),this.$input.blur(),l=!1,this._setDraggingStyle(!0,"vertical")):Math.abs(s)>5&&a()}if(!l){const i=t.clientY-s;r-=i*this._step*this._arrowKeyMultiplier(t),n+r>this._max?r=this._max-n:n+r{this._setDraggingStyle(!1,"vertical"),this._callOnFinishChange(),window.removeEventListener("mousemove",o),window.removeEventListener("mouseup",a)};this.$input.addEventListener("input",()=>{const t=parseFloat(this.$input.value);isNaN(t)||this.setValue(this._clamp(t))}),this.$input.addEventListener("keydown",i=>{"Enter"===i.code&&this.$input.blur(),"ArrowUp"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i))),"ArrowDown"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i)*-1))}),this.$input.addEventListener("wheel",i=>{this._inputFocused&&(i.preventDefault(),t(this._step*this._normalizeMouseWheel(i)))}),this.$input.addEventListener("mousedown",t=>{i=t.clientX,e=s=t.clientY,l=!0,n=this.getValue(),r=0,window.addEventListener("mousemove",o),window.addEventListener("mouseup",a)}),this.$input.addEventListener("focus",()=>{this._inputFocused=!0}),this.$input.addEventListener("blur",()=>{this._inputFocused=!1,this.updateDisplay(),this._callOnFinishChange()})}_initSlider(){this._hasSlider=!0,this.$slider=document.createElement("div"),this.$slider.classList.add("slider"),this.$fill=document.createElement("div"),this.$fill.classList.add("fill"),this.$slider.appendChild(this.$fill),this.$widget.insertBefore(this.$slider,this.$input),this.domElement.classList.add("hasSlider");const t=t=>{const i=this.$slider.getBoundingClientRect();let e=(s=t,n=i.left,r=i.right,l=this._min,o=this._max,(s-n)/(r-n)*(o-l)+l);var s,n,r,l,o;this._snapClampSetValue(e)},i=i=>{t(i.clientX)},e=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("mousemove",i),window.removeEventListener("mouseup",e)};let s,n,r=!1;const l=i=>{i.preventDefault(),this._setDraggingStyle(!0),t(i.touches[0].clientX),r=!1},o=i=>{if(r){const t=i.touches[0].clientX-s,e=i.touches[0].clientY-n;Math.abs(t)>Math.abs(e)?l(i):(window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a))}else i.preventDefault(),t(i.touches[0].clientX)},a=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a)},h=this._callOnFinishChange.bind(this);let d;this.$slider.addEventListener("mousedown",s=>{this._setDraggingStyle(!0),t(s.clientX),window.addEventListener("mousemove",i),window.addEventListener("mouseup",e)}),this.$slider.addEventListener("touchstart",t=>{t.touches.length>1||(this._hasScrollBar?(s=t.touches[0].clientX,n=t.touches[0].clientY,r=!0):l(t),window.addEventListener("touchmove",o),window.addEventListener("touchend",a))}),this.$slider.addEventListener("wheel",t=>{if(Math.abs(t.deltaX)this._max&&(t=this._max),t}_snapClampSetValue(t){this.setValue(this._clamp(this._snap(t)))}get _hasScrollBar(){const t=this.parent.root.$children;return t.scrollHeight>t.clientHeight}get _hasMin(){return void 0!==this._min}get _hasMax(){return void 0!==this._max}}class c extends t{constructor(t,i,e,s){super(t,i,e,"option"),this.$select=document.createElement("select"),this.$select.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this._values=Array.isArray(s)?s:Object.values(s),this._names=Array.isArray(s)?s:Object.keys(s),this._names.forEach(t=>{const i=document.createElement("option");i.innerHTML=t,this.$select.appendChild(i)}),this.$select.addEventListener("change",()=>{this.setValue(this._values[this.$select.selectedIndex]),this._callOnFinishChange()}),this.$select.addEventListener("focus",()=>{this.$display.classList.add("focus")}),this.$select.addEventListener("blur",()=>{this.$display.classList.remove("focus")}),this.$widget.appendChild(this.$select),this.$widget.appendChild(this.$display),this.$disable=this.$select,this.updateDisplay()}updateDisplay(){const t=this.getValue(),i=this._values.indexOf(t);return this.$select.selectedIndex=i,this.$display.innerHTML=-1===i?t:this._names[i],this}}class u extends t{constructor(t,i,e){super(t,i,e,"string"),this.$input=document.createElement("input"),this.$input.setAttribute("type","text"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$input.addEventListener("input",()=>{this.setValue(this.$input.value)}),this.$input.addEventListener("keydown",t=>{"Enter"===t.code&&this.$input.blur()}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$widget.appendChild(this.$input),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.value=this.getValue(),this}}let p=!1;class g{constructor({parent:t,autoPlace:i=void 0===t,container:e,width:s,title:n="Controls",injectStyles:r=!0,touchStyles:l=!0}={}){if(this.parent=t,this.root=t?t.root:this,this.children=[],this.controllers=[],this.folders=[],this._closed=!1,this._hidden=!1,this.domElement=document.createElement("div"),this.domElement.classList.add("lil-gui"),this.$title=document.createElement("div"),this.$title.classList.add("title"),this.$title.setAttribute("role","button"),this.$title.setAttribute("aria-expanded",!0),this.$title.setAttribute("tabindex",0),this.$title.addEventListener("click",()=>this.openAnimated(this._closed)),this.$title.addEventListener("keydown",t=>{"Enter"!==t.code&&"Space"!==t.code||(t.preventDefault(),this.$title.click())}),this.$title.addEventListener("touchstart",()=>{}),this.$children=document.createElement("div"),this.$children.classList.add("children"),this.domElement.appendChild(this.$title),this.domElement.appendChild(this.$children),this.title(n),l&&this.domElement.classList.add("allow-touch-styles"),this.parent)return this.parent.children.push(this),this.parent.folders.push(this),void this.parent.$children.appendChild(this.domElement);this.domElement.classList.add("root"),!p&&r&&(!function(t){const i=document.createElement("style");i.innerHTML=t;const e=document.querySelector("head link[rel=stylesheet], head style");e?document.head.insertBefore(i,e):document.head.appendChild(i)}('.lil-gui{--background-color:#1f1f1f;--text-color:#ebebeb;--title-background-color:#111;--title-text-color:#ebebeb;--widget-color:#424242;--hover-color:#4f4f4f;--focus-color:#595959;--number-color:#2cc9ff;--string-color:#a2db3c;--font-size:11px;--input-font-size:11px;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;--font-family-mono:Menlo,Monaco,Consolas,"Droid Sans Mono",monospace;--padding:4px;--spacing:4px;--widget-height:20px;--name-width:45%;--slider-knob-width:2px;--slider-input-width:27%;--color-input-width:27%;--slider-input-min-width:45px;--color-input-min-width:45px;--folder-indent:7px;--widget-padding:0 0 0 3px;--widget-border-radius:2px;--checkbox-size:calc(var(--widget-height)*0.75);--scrollbar-width:5px;background-color:var(--background-color);color:var(--text-color);font-family:var(--font-family);font-size:var(--font-size);font-style:normal;font-weight:400;line-height:1;text-align:left;touch-action:manipulation;user-select:none;-webkit-user-select:none}.lil-gui,.lil-gui *{box-sizing:border-box;margin:0;padding:0}.lil-gui.root{display:flex;flex-direction:column;width:var(--width,245px)}.lil-gui.root>.title{background:var(--title-background-color);color:var(--title-text-color)}.lil-gui.root>.children{overflow-x:hidden;overflow-y:auto}.lil-gui.root>.children::-webkit-scrollbar{background:var(--background-color);height:var(--scrollbar-width);width:var(--scrollbar-width)}.lil-gui.root>.children::-webkit-scrollbar-thumb{background:var(--focus-color);border-radius:var(--scrollbar-width)}.lil-gui.force-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}.lil-gui.autoPlace{max-height:100%;position:fixed;right:15px;top:0;z-index:1001}.lil-gui .controller{align-items:center;display:flex;margin:var(--spacing) 0;padding:0 var(--padding)}.lil-gui .controller.disabled{opacity:.5}.lil-gui .controller.disabled,.lil-gui .controller.disabled *{pointer-events:none!important}.lil-gui .controller>.name{flex-shrink:0;line-height:var(--widget-height);min-width:var(--name-width);padding-right:var(--spacing);white-space:pre}.lil-gui .controller .widget{align-items:center;display:flex;min-height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.string input{color:var(--string-color)}.lil-gui .controller.boolean .widget{cursor:pointer}.lil-gui .controller.color .display{border-radius:var(--widget-border-radius);height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.color input[type=color]{cursor:pointer;height:100%;opacity:0;width:100%}.lil-gui .controller.color input[type=text]{flex-shrink:0;font-family:var(--font-family-mono);margin-left:var(--spacing);min-width:var(--color-input-min-width);width:var(--color-input-width)}.lil-gui .controller.option select{max-width:100%;opacity:0;position:absolute;width:100%}.lil-gui .controller.option .display{background:var(--widget-color);border-radius:var(--widget-border-radius);height:var(--widget-height);line-height:var(--widget-height);max-width:100%;overflow:hidden;padding-left:.55em;padding-right:1.75em;pointer-events:none;position:relative;word-break:break-all}.lil-gui .controller.option .display.active{background:var(--focus-color)}.lil-gui .controller.option .display:after{bottom:0;content:"↕";font-family:lil-gui;padding-right:.375em;position:absolute;right:0;top:0}.lil-gui .controller.option .widget,.lil-gui .controller.option select{cursor:pointer}.lil-gui .controller.number input{color:var(--number-color)}.lil-gui .controller.number.hasSlider input{flex-shrink:0;margin-left:var(--spacing);min-width:var(--slider-input-min-width);width:var(--slider-input-width)}.lil-gui .controller.number .slider{background-color:var(--widget-color);border-radius:var(--widget-border-radius);cursor:ew-resize;height:var(--widget-height);overflow:hidden;padding-right:var(--slider-knob-width);touch-action:pan-y;width:100%}.lil-gui .controller.number .slider.active{background-color:var(--focus-color)}.lil-gui .controller.number .slider.active .fill{opacity:.95}.lil-gui .controller.number .fill{border-right:var(--slider-knob-width) solid var(--number-color);box-sizing:content-box;height:100%}.lil-gui-dragging .lil-gui{--hover-color:var(--widget-color)}.lil-gui-dragging *{cursor:ew-resize!important}.lil-gui-dragging.lil-gui-vertical *{cursor:ns-resize!important}.lil-gui .title{--title-height:calc(var(--widget-height) + var(--spacing)*1.25);-webkit-tap-highlight-color:transparent;text-decoration-skip:objects;cursor:pointer;font-weight:600;height:var(--title-height);line-height:calc(var(--title-height) - 4px);outline:none;padding:0 var(--padding)}.lil-gui .title:before{content:"▾";display:inline-block;font-family:lil-gui;padding-right:2px}.lil-gui .title:active{background:var(--title-background-color);opacity:.75}.lil-gui.root>.title:focus{text-decoration:none!important}.lil-gui.closed>.title:before{content:"▸"}.lil-gui.closed>.children{opacity:0;transform:translateY(-7px)}.lil-gui.closed:not(.transition)>.children{display:none}.lil-gui.transition>.children{overflow:hidden;pointer-events:none;transition-duration:.3s;transition-property:height,opacity,transform;transition-timing-function:cubic-bezier(.2,.6,.35,1)}.lil-gui .children:empty:before{content:"Empty";display:block;font-style:italic;height:var(--widget-height);line-height:var(--widget-height);margin:var(--spacing) 0;opacity:.5;padding:0 var(--padding)}.lil-gui.root>.children>.lil-gui>.title{border-width:0;border-bottom:1px solid var(--widget-color);border-left:0 solid var(--widget-color);border-right:0 solid var(--widget-color);border-top:1px solid var(--widget-color);transition:border-color .3s}.lil-gui.root>.children>.lil-gui.closed>.title{border-bottom-color:transparent}.lil-gui+.controller{border-top:1px solid var(--widget-color);margin-top:0;padding-top:var(--spacing)}.lil-gui .lil-gui .lil-gui>.title{border:none}.lil-gui .lil-gui .lil-gui>.children{border:none;border-left:2px solid var(--widget-color);margin-left:var(--folder-indent)}.lil-gui .lil-gui .controller{border:none}.lil-gui input{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:0;border-radius:var(--widget-border-radius);color:var(--text-color);font-family:var(--font-family);font-size:var(--input-font-size);height:var(--widget-height);outline:none;width:100%}.lil-gui input:disabled{opacity:1}.lil-gui input[type=number],.lil-gui input[type=text]{padding:var(--widget-padding)}.lil-gui input[type=number]:focus,.lil-gui input[type=text]:focus{background:var(--focus-color)}.lil-gui input::-webkit-inner-spin-button,.lil-gui input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.lil-gui input[type=number]{-moz-appearance:textfield}.lil-gui input[type=checkbox]{appearance:none;-webkit-appearance:none;border-radius:var(--widget-border-radius);cursor:pointer;height:var(--checkbox-size);text-align:center;width:var(--checkbox-size)}.lil-gui input[type=checkbox]:checked:before{content:"✓";font-family:lil-gui;font-size:var(--checkbox-size);line-height:var(--checkbox-size)}.lil-gui button{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:1px solid var(--widget-color);border-radius:var(--widget-border-radius);color:var(--text-color);cursor:pointer;font-family:var(--font-family);font-size:var(--font-size);height:var(--widget-height);line-height:calc(var(--widget-height) - 4px);outline:none;text-align:center;text-transform:none;width:100%}.lil-gui button:active{background:var(--focus-color)}@font-face{font-family:lil-gui;src:url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff")}@media (pointer:coarse){.lil-gui.allow-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}}@media (hover:hover){.lil-gui .controller.color .display:hover:before{border:1px solid #fff9;border-radius:var(--widget-border-radius);bottom:0;content:" ";display:block;left:0;position:absolute;right:0;top:0}.lil-gui .controller.option .display.focus{background:var(--focus-color)}.lil-gui .controller.option .widget:hover .display{background:var(--hover-color)}.lil-gui .controller.number .slider:hover{background-color:var(--hover-color)}body:not(.lil-gui-dragging) .lil-gui .title:hover{background:var(--title-background-color);opacity:.85}.lil-gui .title:focus{text-decoration:underline var(--focus-color)}.lil-gui input:hover{background:var(--hover-color)}.lil-gui input:active{background:var(--focus-color)}.lil-gui input[type=checkbox]:focus{box-shadow:inset 0 0 0 1px var(--focus-color)}.lil-gui button:hover{background:var(--hover-color);border-color:var(--hover-color)}.lil-gui button:focus{border-color:var(--focus-color)}}'),p=!0),e?e.appendChild(this.domElement):i&&(this.domElement.classList.add("autoPlace"),document.body.appendChild(this.domElement)),s&&this.domElement.style.setProperty("--width",s+"px"),this.domElement.addEventListener("keydown",t=>t.stopPropagation()),this.domElement.addEventListener("keyup",t=>t.stopPropagation())}add(t,e,s,n,r){if(Object(s)===s)return new c(this,t,e,s);const l=t[e];switch(typeof l){case"number":return new d(this,t,e,s,n,r);case"boolean":return new i(this,t,e);case"string":return new u(this,t,e);case"function":return new h(this,t,e)}console.error("gui.add failed\n\tproperty:",e,"\n\tobject:",t,"\n\tvalue:",l)}addColor(t,i,e=1){return new a(this,t,i,e)}addFolder(t){return new g({parent:this,title:t})}load(t,i=!0){return t.controllers&&this.controllers.forEach(i=>{i instanceof h||i._name in t.controllers&&i.load(t.controllers[i._name])}),i&&t.folders&&this.folders.forEach(i=>{i._title in t.folders&&i.load(t.folders[i._title])}),this}save(t=!0){const i={controllers:{},folders:{}};return this.controllers.forEach(t=>{if(!(t instanceof h)){if(t._name in i.controllers)throw new Error(`Cannot save GUI with duplicate property "${t._name}"`);i.controllers[t._name]=t.save()}}),t&&this.folders.forEach(t=>{if(t._title in i.folders)throw new Error(`Cannot save GUI with duplicate folder "${t._title}"`);i.folders[t._title]=t.save()}),i}open(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),this.domElement.classList.toggle("closed",this._closed),this}close(){return this.open(!1)}show(t=!0){return this._hidden=!t,this.domElement.style.display=this._hidden?"none":"",this}hide(){return this.show(!1)}openAnimated(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),requestAnimationFrame(()=>{const i=this.$children.clientHeight;this.$children.style.height=i+"px",this.domElement.classList.add("transition");const e=t=>{t.target===this.$children&&(this.$children.style.height="",this.domElement.classList.remove("transition"),this.$children.removeEventListener("transitionend",e))};this.$children.addEventListener("transitionend",e);const s=t?this.$children.scrollHeight:0;this.domElement.classList.toggle("closed",!t),requestAnimationFrame(()=>{this.$children.style.height=s+"px"})}),this}title(t){return this._title=t,this.$title.innerHTML=t,this}reset(t=!0){return(t?this.controllersRecursive():this.controllers).forEach(t=>t.reset()),this}onChange(t){return this._onChange=t,this}_callOnChange(t){this.parent&&this.parent._callOnChange(t),void 0!==this._onChange&&this._onChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}onFinishChange(t){return this._onFinishChange=t,this}_callOnFinishChange(t){this.parent&&this.parent._callOnFinishChange(t),void 0!==this._onFinishChange&&this._onFinishChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}destroy(){this.parent&&(this.parent.children.splice(this.parent.children.indexOf(this),1),this.parent.folders.splice(this.parent.folders.indexOf(this),1)),this.domElement.parentElement&&this.domElement.parentElement.removeChild(this.domElement),Array.from(this.children).forEach(t=>t.destroy())}controllersRecursive(){let t=Array.from(this.controllers);return this.folders.forEach(i=>{t=t.concat(i.controllersRecursive())}),t}foldersRecursive(){let t=Array.from(this.folders);return this.folders.forEach(i=>{t=t.concat(i.foldersRecursive())}),t}}export default g;export{i as BooleanController,a as ColorController,t as Controller,h as FunctionController,g as GUI,d as NumberController,c as OptionController,u as StringController}; diff --git a/jsm/libs/meshopt_decoder.module.js b/jsm/libs/meshopt_decoder.module.js new file mode 100644 index 0000000..1991e05 --- /dev/null +++ b/jsm/libs/meshopt_decoder.module.js @@ -0,0 +1,113 @@ +// This file is part of meshoptimizer library and is distributed under the terms of MIT License. +// Copyright (C) 2016-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) +var MeshoptDecoder = (function() { + "use strict"; + + // Built with clang version 11.0.0 (https://github.com/llvm/llvm-project.git 0160ad802e899c2922bc9b29564080c22eb0908c) + // Built from meshoptimizer 0.14 + var wasm_base = "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"; + var wasm_simd = "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"; + + // Uses bulk-memory and simd extensions + var detector = new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,3,2,0,0,5,3,1,0,1,12,1,0,10,22,2,12,0,65,0,65,0,65,0,252,10,0,0,11,7,0,65,0,253,15,26,11]); + + // Used to unpack wasm + var wasmpack = new Uint8Array([32,0,65,253,3,1,2,34,4,106,6,5,11,8,7,20,13,33,12,16,128,9,116,64,19,113,127,15,10,21,22,14,255,66,24,54,136,107,18,23,192,26,114,118,132,17,77,101,130,144,27,87,131,44,45,74,156,154,70,167]); + + if (typeof WebAssembly !== 'object') { + // This module requires WebAssembly to function + return { + supported: false, + }; + } + + var wasm = wasm_base; + + if (WebAssembly.validate(detector)) { + wasm = wasm_simd; + console.log("Warning: meshopt_decoder is using experimental SIMD support"); + } + + var instance; + + var promise = + WebAssembly.instantiate(unpack(wasm), {}) + .then(function(result) { + instance = result.instance; + instance.exports.__wasm_call_ctors(); + }); + + function unpack(data) { + var result = new Uint8Array(data.length); + for (var i = 0; i < data.length; ++i) { + var ch = data.charCodeAt(i); + result[i] = ch > 96 ? ch - 71 : ch > 64 ? ch - 65 : ch > 47 ? ch + 4 : ch > 46 ? 63 : 62; + } + var write = 0; + for (var i = 0; i < data.length; ++i) { + result[write++] = (result[i] < 60) ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i]; + } + return result.buffer.slice(0, write); + } + + function decode(fun, target, count, size, source, filter) { + var sbrk = instance.exports.sbrk; + var count4 = (count + 3) & ~3; // pad for SIMD filter + var tp = sbrk(count4 * size); + var sp = sbrk(source.length); + var heap = new Uint8Array(instance.exports.memory.buffer); + heap.set(source, sp); + var res = fun(tp, count, size, sp, source.length); + if (res == 0 && filter) { + filter(tp, count4, size); + } + target.set(heap.subarray(tp, tp + count * size)); + sbrk(tp - sbrk(0)); + if (res != 0) { + throw new Error("Malformed buffer data: " + res); + } + }; + + var filters = { + // legacy index-based enums for glTF + 0: "", + 1: "meshopt_decodeFilterOct", + 2: "meshopt_decodeFilterQuat", + 3: "meshopt_decodeFilterExp", + // string-based enums for glTF + NONE: "", + OCTAHEDRAL: "meshopt_decodeFilterOct", + QUATERNION: "meshopt_decodeFilterQuat", + EXPONENTIAL: "meshopt_decodeFilterExp", + }; + + var decoders = { + // legacy index-based enums for glTF + 0: "meshopt_decodeVertexBuffer", + 1: "meshopt_decodeIndexBuffer", + 2: "meshopt_decodeIndexSequence", + // string-based enums for glTF + ATTRIBUTES: "meshopt_decodeVertexBuffer", + TRIANGLES: "meshopt_decodeIndexBuffer", + INDICES: "meshopt_decodeIndexSequence", + }; + + return { + ready: promise, + supported: true, + decodeVertexBuffer: function(target, count, size, source, filter) { + decode(instance.exports.meshopt_decodeVertexBuffer, target, count, size, source, instance.exports[filters[filter]]); + }, + decodeIndexBuffer: function(target, count, size, source) { + decode(instance.exports.meshopt_decodeIndexBuffer, target, count, size, source); + }, + decodeIndexSequence: function(target, count, size, source) { + decode(instance.exports.meshopt_decodeIndexSequence, target, count, size, source); + }, + decodeGltfBuffer: function(target, count, size, source, mode, filter) { + decode(instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]); + } + }; +})(); + +export { MeshoptDecoder }; diff --git a/jsm/libs/mikktspace.module.js b/jsm/libs/mikktspace.module.js new file mode 100644 index 0000000..0a1baaa --- /dev/null +++ b/jsm/libs/mikktspace.module.js @@ -0,0 +1,128 @@ +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function getObject(idx) { return heap[idx]; } + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachegetFloat32Memory0 = null; +function getFloat32Memory0() { + if (cachegetFloat32Memory0 === null || cachegetFloat32Memory0.buffer !== wasm.memory.buffer) { + cachegetFloat32Memory0 = new Float32Array(wasm.memory.buffer); + } + return cachegetFloat32Memory0; +} + +let WASM_VECTOR_LEN = 0; + +function passArrayF32ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 4); + getFloat32Memory0().set(arg, ptr / 4); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +let cachegetInt32Memory0 = null; +function getInt32Memory0() { + if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { + cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachegetInt32Memory0; +} + +function getArrayF32FromWasm0(ptr, len) { + return getFloat32Memory0().subarray(ptr / 4, ptr / 4 + len); +} +/** +* Generates vertex tangents for the given position/normal/texcoord attributes. +* @param {Float32Array} position +* @param {Float32Array} normal +* @param {Float32Array} texcoord +* @returns {Float32Array} +*/ +export function generateTangents(position, normal, texcoord) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = passArrayF32ToWasm0(position, wasm.__wbindgen_malloc); + var len0 = WASM_VECTOR_LEN; + var ptr1 = passArrayF32ToWasm0(normal, wasm.__wbindgen_malloc); + var len1 = WASM_VECTOR_LEN; + var ptr2 = passArrayF32ToWasm0(texcoord, wasm.__wbindgen_malloc); + var len2 = WASM_VECTOR_LEN; + wasm.generateTangents(retptr, ptr0, len0, ptr1, len1, ptr2, len2); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v3 = getArrayF32FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 4); + return v3; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +export const __wbindgen_string_new = function(arg0, arg1) { + var ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); +}; + +export const __wbindgen_rethrow = function(arg0) { + throw takeObject(arg0); +}; + +// + +const wasmDataURI = 'data:application/octet-stream;base64,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TQ0WIARBAnRqIgQgBCgCAEEBajYCACACQQRqIQIgA0F/aiIDDQALCyABQdgFaigCACABKALgBUUNFUEAIQJBADYCAEEBIQQDQCABQfgCaiABQdgFahBjIAEoAvwCIgMgBEF/aiIHTQ0UIAEoAvgCIAJqKAIAIQMgAUHwAmogAUHoBWoQYyABKAL0AiINIAdNDRMgASgC8AIgAmooAgAhByABQdgFaigCACENIAEoAuAFIhEgBE0NEiACIA1qQQRqIAMgB2o2AgAgAkEEaiECIARBAWoiBEGAEEcNAAsgCEEJTwRAQQAhBCABQZgGakEAQQRBCCAGGyALG2ohCCAFIQMDQCABQegCaiADKAIAEGEgAUGYBmogACABKALoAiABKALsAhAxIDAgLiAIKgIAEDwhAiABQeACaiABQdgFahBjIAEoAuQCIgYgAk0NEiABQcgFaigCACABKALQBSINIAJBAnQiBiABKALgAmooAgAiC00NESABQdgCaiABQfgFahBjIAEoAtwCIg0gAk0NECALQQJ0aiABKALYAiAGaigCAEECdGogBDYCACABQfgFaigCACELIAEoAoAGIgcgAk0NDyAGIAtqIgIgAigCAEEBajYCACADQQRqIQMgCiAEQQFqIgRHDQALCyABQdACaiABQegFahBjIAEoAtQCRQ0MIAEoAtACKAIAIQNBBCECQQEhBANAIAFByAJqIAFB6AVqEGMgASgCzAIiCiAETQ0MIAMgASgCyAIgAmooAgBJBEAgAUHAAmogAUHoBWoQYyABKALEAiIDIARNDQwgASgCwAIgAmooAgAhAwsgAkEEaiECIARBAWoiBEGAEEcNAAsgAUHgA2oiAkIANwIAIAJBCGpCADcCACABQaAGaiABQegDaikDADcDACABIAEpA+ADNwOYBiABQYgGaiABQZgGaiADEEEgASgCiAYhCiABKAKQBiELQQAhBgNAIAFBuAJqIAFB2AVqEGMgASgCvAIiAyAGTQ0KIAFByAVqKAIAIQIgASgC0AUiBCAGQQJ0IgggASgCuAJqKAIAIgNNDQkgAUGwAmogAUHoBWoQYyABKAK0AiIEIAZNDQggASgCsAIgCGooAgAiCEECTwRAIAIgA0ECdGohBEEAIQMgCiECA0AgAUGoAmogBSAEKAIAIgdBAnRqKAIAEGEgAUGYBmogACABKAKoAiABKAKsAhAxIAMgC0YNCSACIAEoApgGNgIAIAJBBGogASgCnAY2AgAgASgCoAYhDSACQQxqIAc2AgAgAkEIaiANNgIAIARBBGohBCACQRBqIQIgCCADQQFqIgNHDQALIAUgCiAAQQAgCEF/ahAECyAGQQFqIgZBgBBHDQALIBNBASATQQFLGyEFIA5BPGohAyABQYgGahBSIAFB+AVqKAIAGiABQfgFahBWIAFB6AVqKAIAGiABQegFahBWIAFB2AVqKAIAGiABQdgFahBWIAFByAVqKAIAGiABQcgFahBWQQAhAkEAIQRBACEGQQAhBwNAIAFBoAJqIAFBwANqEGMgASgCpAIiCiAETQ0GIAEoAqACIAJqKAIAIQogAUGYAmogAUHAA2oQYyABKAKcAiILIARBAWpNDQUgASgCmAIgAmpBBGooAgAhCyABQZACaiABQcADahBjIAEoApQCIgggBEECak0NBCABKAKQAiACakEIaigCACEIIAFBiAJqIAoQYSABQYgGaiAAIAEoAogCIAEoAowCEDEgAUGAAmogCxBhIAFB4ANqIAAgASgCgAIgASgChAIQMSABQfgBaiAIEGEgAUGYBmogACABKAL4ASABKAL8ARAxAkACQCABQYgGaiABQeADahALDQAgAUGIBmogAUGYBmoQCw0AIAFB4ANqIAFBmAZqEAtFDQELIAwgBk0NBCADIAMoAgBBAXI2AgAgB0EBaiEHCyACQQxqIQIgBEEDaiEEIANByABqIQMgBSAGQQFqIgZHDQALIA4gAUHAA2ooAgAgEyAHayIKIBMQECABQcADaigCACELAkACQCAKRQRAQX8hBgwBC0EAIQYDQCAOIAZByABsaiIDQn83AgAgA0EIakF/NgIAIANBDGpCADcCACADQRRqQQA2AgAgA0E8aiADQRhqIQIgAygCPCEEQQMhAwNAIAJCADcCACACQRhqQgA3AgAgAkEQakIANwIAIAJBCGpCADcCACAEQQRyIQQgA0F/aiIDDQALIAQ2AgAgBkEBaiIGIApHDQALIAFBpAZqIQYgAUHsA2ohDEEAIQUDQCABQfABaiALIAVBDGxqIgMoAgAQYSABQagFaiAAIAEoAvABIAEoAvQBEDEgAUHoAWogA0EEaiICKAIAEGEgAUG4BWogACABKALoASABKALsARAxIAFB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AkAgASgCRBAxIAEpA4gGISsgASgCkAYhFyABQThqIAIQYSABQYgGaiAAIAEoAjggASgCPBAxIAEoAogGIQIgASgCjAYhECABKAKQBiESIAFBMGogHRBhIAFBiAZqIAAgASgCMCABKAI0EDEgASkDiAYhLCABKAKQBiEdIAEgFzYCgAYgASArNwP4BSABIBI2ApAGIAEgEDYCjAYgASACNgKIBiABQcgFaiABQfgFaiABQYgGahAVIAEgHTYCgAYgASAsNwP4BSABIBI2ApAGIAEgEDYCjAYgASACNgKIBiABQdgFaiABQfgFaiABQYgGahAVIBYgAUHQBWoiECgCADYCACABIAEpA8gFNwP4BSABQbgFaiABQcgFahARITUgBCAYKAIANgIAIAEgASkDuAU3A4gGIAFBKGogAUGIBmogGRBoIAEoAigiAiABKAIsIhJHBEADQCACIDUgAioCAJQ4AgAgEiACQQRqIgJHDQALCyABQcgFaiABQfgFaiABQYgGahAVIAQgECgCADYCACABIAEpA8gFNwOIBiABQYgGahBRBEAgBCAQKAIANgIAIAEgASkDyAU3A4gGIAFByAVqIAFBiAZqECMLIBYgAUHgBWoiECgCADYCACABIAEpA9gFNwP4BSABQbgFaiABQdgFahARITUgBCAYKAIANgIAIAEgASkDuAU3A4gGIAFBIGogAUGIBmogGRBoIAEoAiAiAiABKAIkIhJHBEADQCACIDUgAioCAJQ4AgAgEiACQQRqIgJHDQALCyABQdgFaiABQfgFaiABQYgGahAVIAQgECgCADYCACABIAEpA9gFNwOIBiABQYgGahBRBEAgBCAQKAIANgIAIAEgASkD2AU3A4gGIAFB2AVqIAFBiAZqECMLQwAAgD8gAUHIBWogAUHYBWoQESI1QwAAgL+XIDVDAACAP14buxAOIA8qAjQhNSAPKgIwITcgFiADKAIANgIAIAEgASkDmAY3A/gFIAEgMjgCkAYgASAvOAKMBiABIDM4AogGIAFBGGogAUGIBmogGRBotiEvIAEoAhgiAiABKAIcIg9HBEADQCACIAIqAgAgL5Q4AgAgDyACQQRqIgJHDQALCyABQZgGaiABQfgFaiABQYgGahAWIBYgFUEIaiIPKAIANgIAIAEgFSkCADcD+AUgASAxOAKQBiABIDA4AowGIAEgNDgCiAYgAUEQaiABQYgGaiAZEGggASgCECICIAEoAhQiEEcEQANAIAIgAioCACAvlDgCACAQIAJBBGoiAkcNAAsLIAFB6AVqIAFB+AVqIAFBiAZqEBYgDyABQfAFaigCADYCACAVIAEpA+gFNwIAIAEgNyAvlCABKgKkBpI4AqQGIAEgNSAvlCABKgK0BpI4ArQGIC4gL5IhLgsgCSAHQQFqIgdHDQALDAQLIAkgD0HcgsAAED8ACyAFIAJB7ILAABA/AAsgDCACQfyCwAAQPwALIAwgB0GMg8AAED8ACyAEIAMoAgA2AgAgASABKQOYBjcDiAYgAUGIBmoQUQRAIAQgAygCADYCACABIAEpA5gGNwOIBiABQZgGaiABQYgGahAjCyAEIBVBCGoiAigCADYCACABIBUpAgA3A4gGIAFBiAZqEFEEQCAEIAIoAgA2AgAgASAVKQIANwOIBiABQfgFaiABQYgGahAjIAIgAUGABmooAgA2AgAgFSABKQP4BTcCAAsgLkMAAAAAXkEBc0UEQCABIAEqAqQGIC6VOAKkBiABIAEqArQGIC6VOAK0BgsgASgC4AQiAiAMTQ0DIAEoAtgEIAxBKGxqIAFBmAZqQSgQTiAMQQFqIQwLICAEQCAIKAJAIAggKGpBxABqLQAAaiICICJPDQICfyAUIAJBKGxqIgIoAiBBAUcEQCABKALgBCIEIAVNDQYgAiABKALYBCAFQShsakEoEDsaQQEMAQsgASgC4AQiBCAFTQ0GIAFBmAZqIAIgASgC2AQgBUEobGoQBSACIAFBmAZqQSgQTkECCyEFIAJBIGogBTYCACACICYtAAA6ACQgESgCABogERBWIB9BAWoiHyANKAIASA0BDAYLC0F/QQRBrIPAABA/AAsgAiAiQbyDwAAQPwALIAwgAkGcg8AAED8ACyAFIARBzIPAABA/AAsgBSAEQdyDwAAQPwALICNBAWoiIyAGRw0ACyABQfgEaigCABogAUH4BGoQViABQegEahBJIAFB6ARqEFIgAUHYBGoQVCABKALIBCEUIAEoAtADIQsLIAFBwANqKAIAIQUgCiATSARAIBtBAUghCSAKIQMDQCALIANByABsaiIELQA8QQJxRQRAIANBA2whDCAEQUBrIQhBACEGA0ACQCAJDQAgBSAGIAxqQQJ0aigCACEOQQAhAgNAAkAgAiAOIAUgAkECdGooAgAiB0ciDWohAiAHIA5GDQAgAiAbSA0BCwsgDQ0AIBQgCCgCACAEIAZqQcQAai0AAGpBKGxqIBQgCyACQQNuIg5ByABsaiIHKAJAIAcgAiAOQQNsa2pBxABqLQAAakEobGpBKBA7GgsgBkEBaiIGQQNHDQALCyADICVGIANBAWohA0UNAAsLIApBAU4EQCALQcQAaiEEQQAhBQNAAkAgCyAFQcgAbGoiAy0APEECcUUNACABQQA2AugDIAFCADcD4AMgAUEIaiADKAI4IgYCf0EBQQEgA0HFAGotAABBH3F0QQEgAy0AREEfcXRyQQEgA0HGAGotAABBH3F0ciICQQJxRQ0AGkECIAJBBHFFDQAaQQBBAyACQQhxGwsiDhBmEGEgAUGYBmogACABKAIIIAEoAgwQMSABQegDaiABQaAGaigCADYCACABIAEpA5gGNwPgAyADQUBrIQNBACECA0AgASAGIAIgBGotAAAiCRBmEGEgAUGYBmogACABKAIAIAEoAgQQMSABQZgGaiABQeADahALBEAgFCADKAIAIgMgDmpBKGxqIBQgAyAJakEobGpBKBA7GgwCCyACQQFqIgJBA0cNAAsLIARByABqIQQgBUEBaiIFIApHDQALCyABKALQBCEFQQAhBkEAIQkDQEEAIAUgCWsiAyADIAVLGyEKIBQgCUEobGohDkEAIQNBACEEA0AgBCAKRg0DIAMgDmoiAikCACErIAEgAkEIaigCADYC6AMgASArNwLgAyAAIAFB4ANqIAJBDGoqAgAgAkEcaioCACACQSRqLQAAEB8gBEEBaiEEIANBKGoiA0H4AEcNAAsgBCAJaiEJIAZBAWoiBiATRw0ACyABQcgEahBUIAFBuARqKAIAGiABQbgEahBWIAFBqARqEFIgAUHQA2oQUyABQcADaigCABogAUHAA2oQVkEBIQILIAFB4AZqJAAgAg8LIAQgCWogBUHMgsAAED8ACyAGIAxBvILAABA/AAsgBEECaiAIQayCwAAQPwALIARBAWogC0GcgsAAED8ACyAEIApBjILAABA/AAsgCyALQdyFwAAQPwALIAYgBEHMhcAAED8ACyADIARBvIXAABA/AAsgBiADQayFwAAQPwALIAQgA0GchcAAED8ACyAEIApBjIXAABA/AAtBAEEA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AiALIANBEGogAiABIANBIGoQLSADQRhqKAIAIQEgAygCFCECIAMoAhBBAUYNASAAIAI2AgAgACABQShuNgIECyADQTBqJAAPCyADQQhqIAIgARBoIAMoAgghASADKAIMDAELIAMgAkEAEGggAygCACEBIAMoAgQLIgAEQCABIAAQcAALEGsAC6cCAgN/AX4jAEEwayIDJAACfwJAAkAgACgCBCIEIAFrIAJJBEAgASACaiICIAFJDQJBBCEBAkAgBEEBdCIFIAIgBSACSxsiAkEEIAJBBEsbrUIMfiIGQiCIp0UEQCAGpyECDAELIAAoAgQhBEEAIQELAkAgBARAIAAoAgAhBSADQShqQQQ2AgAgAyAEQQxsNgIkIAMgBTYCIAwBCyADQQA2AiALIANBEGogAiABIANBIGoQLSADQRhqKAIAIQEgAygCFCECIAMoAhBBAUYNASAAIAI2AgAgACABQQxuNgIECyADQTBqJAAPCyADQQhqIAIgARBoIAMoAgghASADKAIMDAELIAMgAkEAEGggAygCACEBIAMoAgQLIgAEQCABIAAQcAALEGsAC6gCAQN/IwBBMGsiAyQAAn8CQAJAIAAoAgQiBCABayACSQRAIAEgAmoiAiABSQ0CQQQhAQJAIARBAXQiBSACIAUgAksbIgJBBCACQQRLGyICQf////8DcSACRgRAIAJBAnQhAgwBCyAAKAIEIQRBACEBCwJAIAQEQCAAKAIAIQUgA0EoakEENgIAIAMgBEECdDYCJCADIAU2AiAMAQsgA0EANgIgCyADQRBqIAIgASADQSBqEC0gA0EYaigCACEBIAMoAhQhAiADKAIQQQFGDQEgACACNgIAIAAgAUECdjYCBAsgA0EwaiQADwsgA0EIaiACIAEQaCADKAIIIQEgAygCDAwBCyADIAJBABBoIAMoAgAhASADKAIECyIABEAgASAAEHAACxBrAAuoAgEDfyMAQTBrIgMkAAJ/AkACQCAAKAIEIgQgAWsgAkkEQCABIAJqIgIgAUkNAkEEIQECQCAEQQF0IgUgAiAFIAJLGyICQQQgAkEESxsiAkH/////AHEgAkYEQCACQQR0IQIMAQsgACgCBCEEQQAhAQsCQCAEBEAgACgCACEFIANBKGpBBDYCACADIARBBHQ2AiQgAyAFNgIgDAELIANBADYCIAsgA0EQ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aiEDIAUoAggiBiAFKAIMIgdJBEAgByAGayEIA0AgAikCACEJIANBCGogAkEIaikCADcCACADIAk3AgAgA0EQaiEDIAhBf2oiCA0ACyAEIAdqIAZrIQQLIAAgAQR/IAIpAgAhCSADQQhqIAJBCGopAgA3AgAgAyAJNwIAIARBAWoFIAQLNgIIIAVBEGokAAuqAQMBfwN+An0jAEEgayICJAAgAkEYaiABKAIAEGEgACACKAIYIAIoAhwQPSEDIAJBEGogASgCBBBhIAAgAigCECACKAIUED0hBCACQQhqIAEoAggQYSAAIAIoAgggAigCDBA9IQUgAkEgaiQAIASnviADp74iBpMgBUIgiKe+IANCIIinviIHk5QgBEIgiKe+IAeTIAWnviAGk5STIgaMIAYgBkMAAAAAXRsLsQEBBX8jAEEQayIDJAACQCAAKAIEIgIgAU8EQAJAIAIEQCACQQJ0IQIgACgCACEEAkAgAUECdCIFRQRAQQQhBiACRQ0BIAQgAkEEEGoMAQsgBCACQQQgBRBiIgZFDQILIAAgBjYCACAAIAFB/////wNxNgIECyADQRBqJAAPCyADQQhqIAVBBBBoIAMoAgwiAEUNASADKAIIIAAQcAALQcWIwABBJEHsiMAAEEgACxBrAAu2AQEBfyMAQRBrIgMkAAJAIABFDQAgAyAANgIEIAFFDQACQCACQQRLDQAgAUEDakECdkF/aiIAQf8BSw0AIANBoJXAADYCCCADIABBAnRBpJXAAGoiACgCADYCDCADQQRqIANBDGogA0EIakGUicAAEBMgACADKAIMNgIADAELIANBoJXAACgCADYCDCADQQRqIANBDGpB/IjAAEH8iMAAEBNBoJXAACADKAIMNgIACyADQRBqJAALmQEBBn8jAEEQayIFJAAgACAAKAIIIAEQGCAAKAIAIAAoAgghAyAFQQhqQQEgARBoIANByABsaiEEIAUoAggiBiAFKAIMIgdJBEAgByAGayEIA0AgBCACQcgAEDtByABqIQQgCEF/aiIIDQALIAMgB2ogBmshAwsgACABBH8gBCACQcgAEDsaIANBAWoFIAMLNgIIIAVBEGokAAuVAQEGfyMAQRBrIgUkACAAIAAoAgggARAZIAAoAgAgACgCCCEDIAVBCGpBASABEGggA0EobGohBCAFKAIIIgYgBSgC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AWoiAiABIAJLG0GHgARqIgFBEHZAACICQX9GBEBBACEDQQEMAQsgAkEQdCIDQgA3AwAgA0EANgIIIAMgAyABQYCAfHFqQQJyNgIAQQALIQIgACADNgIEIAAgAjYCAAtvAQF/AkAgASAATwRAIAJFDQEgACEDA0AgAyABLQAAOgAAIAFBAWohASADQQFqIQMgAkF/aiICDQALDAELIAJFDQAgAUF/aiEBIABBf2ohAwNAIAIgA2ogASACai0AADoAACACQX9qIgINAAsLIAALbwECfwJ/IAIgAJMgASAAk5VDAAAARZQiAEMAAADPYCIDQQFzIABD////Tl9FckUEQCAAqAwBC0H/////B0GAgICAeCADGyIEIARBACAAQ////05fGyADGwsiA0EAIANBAEobIgNB/w8gA0H/D0gbC2YBAX8CQCAAQSBqKAIAIgMgAUEGbCACQQF0aiIBSwRAIAMgAUEBciICTQ0BIAAoAhgiACACQQJ0ajUCAEIghiAAIAFBAnRqNQIAhA8LIAEgA0HsgMAAED8ACyACIANB/IDAABA/AAtrAQJ/IwBBEGsiBiQAAkAgACABIAIgAyAEEAoiBQ0AIAZBCGogAyAAIAEgBCgCDBEFAEEAIQUgBigCCA0AIAYoAgwiBSACKAIANgIIIAIgBTYCACAAIAEgAiADIAQQCiEFCyAGQRBqJAAgBQtsAQF/IwBBMGsiAyQAIAMgATYCBCADIAA2AgAgA0EcakECNgIAIANBLGpBHTYCACADQgI3AgwgA0G0kMAANgIIIANBHTYCJCADIANBIGo2AhggAyADNgIoIAMgA0EEajYCICADQQhqIAIQWQALVAEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAkEEaiACQQhqEAggAkEgaiQAC1kCAX8BfiMAQSBrIgMkACADQQhqIAIQNyADKQMIIQQgAEEANgIIIAAgBDcCACADQRhqIAFBCGopAgA3AwAgAyABKQIANwMQIAAgAiADQRBqECcgA0EgaiQAC1kCAX8BfiMAQSBrIgMkACADQQhqIAIQNCADKQMIIQQgAEEANgIIIAAgBDcCACADQRhqIAFBCGooAgA2AgAgAyABKQIANwMQIAAgAiADQRBqECYgA0EgaiQAC1kCAX8BfiMAQSBrIgMkACADQQhqIAIQNyADKQMIIQQgAEEANgIIIAAgBDcCACADQRhqIAFBCGopAgA3AwAgAyABKQIANwMQIAAgAiADQRBqECAgA0EgaiQAC1QBAn8gASgCACECIAFBADYCAAJAIAIEQCABKAIEIQNBCEEEEGciAUUNASABIAM2AgQgASACNgIAIABByI3AADYCBCAAIAE2AgAPCwALQQhBBBBwAAtKAgF/AX4jAEHQAGsiAyQAIAMgAhA2IAMpAwAhBCAAQQA2AgggACAENwIAIANBCGogAUHIABBOIAAgAiADQQhqECsgA0HQAGokAAtHAgF/AX4jAEEwayIDJAAgAyACEDUgAykDACEEIABBADYCCCAAIAQ3AgAgA0EIaiABQSgQTiAAIAIgA0EIahAsIANBMGokAAtKAAJ/IAFBgIDEAEcEQEEBIAAoAhggASAAQRxqKAIAKAIQEQIADQEaCyACRQRAQQAPCyAAKAIYIAJBACAAQRxqKAIAKAIMEQQACwtHAQF/IwBBIGsiAyQAIANBFGpBADYCACADQfCPwAA2AhAgA0IBNwIEIAMgATYCHCADIAA2AhggAyADQRhqNgIAIAMgAhBZAAs8AQF/IAAoAggiAQRAIAFBBHQhASAAKAIAQQRqIQADQCAAKAIAGiAAEFYgAEEQaiEAIAFBcGoiAQ0ACwsLOAAgAEIANwIAIABBCGpBADYCACAAQSRqQQA6AAAgAEEcakIANwIAIABBFGpCADcCACAAQgA3AgwLRAECfyABKAIEIQIgASgCACEDQQhBBBBnIgFFBEBBCEEEEHAACyABIAI2AgQgASADNgIAIABBoI/AADYCBCAAIAE2AgALPQAgAEIANwIgIAAgASkCADcCACAAIAIpAgA3AhAgAEEIaiABQQhqKQIANwIAIABBGGogAkEIaikCADcCAAtbAQN/IwBBEGsiASQAIAAoAgwiAkUEQEGAjsAAQStByI7AABBIAAsgACgCCCIDRQRAQYCOwABBK0HYjsAAEEgACyABIAI2AgggASAANgIEIAEgAzYCACABEFgACysAIAIEQANAIAAgAS0AADoAACABQQFqIQEgAEEBaiEAIAJBf2oiAg0ACwsLKQEBfyADIAIQLiIEBEAgBCAAIAMgASABIANLGxBOIAAgASACECoLIAQLLAACQCAAQXxNBEAgAEUEQEEEIQAMAgsgACAAQX1JQQJ0EGciAA0BCwALIAALNwEBf0EBIQECQCAAKgIAi0MAAIAAXg0AIAAqAgSLQwAAgABeDQAgACoCCItDAACAAF4hAQsgAQsmAQF/AkAgACgCBCIBRQ0AIAAoAgAgAUEEdCIBRQ0AIAFBBBBqCwsnAQF/AkAgACgCBCIBRQ0AIAAoAgAgAUHIAGwiAUUNACABQQQQagsLJgEBfwJAIAAoAgQiAUUNACAAKAIAIAFBKGwiAUUNACABQQQQagsLJgEBfwJAIAAoAgQiAUUNACAAKAIAIAFBDGwiAUUNACABQQQQagsLJgEBfwJAIAAoAgQiAUUNACAAKAIAIAFBAnQiAUUNACABQQQQagsLLAEBfyMAQRBrIgEkACABQQhqIABBCGooAgA2AgAgASAAKQIANwMAIAEQWwALLAEBfyMAQRBrIgEkACABQQhqIABBCGooAgA2AgAgASAAKQIANwMAIAEQMwALNAEBfyMAQRBrIgIkACACIAE2AgwgAiAANgIIIAJB8I/AADYCBCACQfCPwAA2AgAgAhBNAAspAQF/IAEEQCAAIQIDQCACQQA6AAAgAkEBaiECIAFBf2oiAQ0ACwsgAAsrAQF/IwBBEGsiASQAIAEgACkCADcDCCABQQhqQbSNwABBACAAKAIIECUACycBAX8jAEEQayICJAAgAiABNgIIIAJBHTYCBCACIAA2AgAgAhBXAAsmAQF/AkAgACgCACIBRQ0AIABBBGooAgAiAEUNACABIABBARBqCwsmAQF/AkAgACgCBCIBRQ0AIABBCGooAgAiAEUNACABIABBARBqCwshACAAQQM2AgwgACABNgIEIAAgATYCACAAIAFBDGo2AggLHQAgASgCAEUEQAALIABByI3AADYCBCAAIAE2AgALFgAgACABQQNxNgIEIAAgAUECdjYCAAsMACAAIAEgAiADEE8LFgAgACABKAIINgIEIAAgASgCADYCAAsPACABBEAgACABQQQQagsLEgAgACgCACABIAEgAmoQIkEACw0AIAFBA3EgAEECdHILCAAgACABEC4LEAAgACACNgIEIAAgATYCAAsTACAAQaCPwAA2AgQgACABNgIACwoAIAAgASACECoLEQBBzI/AAEERQeCPwAAQSAALDgAgACgCABoDQAwACwALCwAgADUCACABEBQLCwAgACMAaiQAIwALCwAgAItDAACAAF4LGQAgACABQcSdwAAoAgAiAEEPIAAbEQEAAAsFAEGABAsEAEEBCwQAIAELBABBAAsNAEL0+Z7m7qOq+f4ACw0AQve47vqqzNXu5QALDABC6dCi28yi6rtGCwMAAQsDAAELC6QVAgBBgIDAAAvBCnNyYy9saWIucnMAAAAAEAAKAAAANgAAAA0AAAAAABAACgAAADcAAAANAAAAAAAQAAoAAAA4AAAADQAAAAAAEAAKAAAAPgAAAA0AAAAAABAACgAAAD8AAAANAAAAAAAQAAoAAABAAAAADQAAAAAAEAAKAAAARgAAAA0AAAAAABAACgAAAEcAAAANAAAARmFpbGVkIHRvIGdlbmVyYXRlIHRhbmdlbnRzLi9Vc2Vycy9kb25tY2N1cmR5Ly5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL21pa2t0c3BhY2UtMC4yLjAvc3JjL2dlbmVyYXRlZC5ycwCoABAAYwAAANcAAAASAAAAqAAQAGMAAADYAAAAEgAAAKgAEABjAAAA2QAAABIAAACoABAAYwAAAN4AAAANAAAAqAAQAGMAAAAjAQAAOAAAAKgAEABjAAAA/QEAABUAAACoABAAYwAAAA8CAABAAAAAqAAQAGMAAAAVAgAAEQAAAKgAEABjAAAAFgIAABEAAACoABAAYwAAABgCAAARAAAAqAAQAGMAAAAjAgAAGQAAAKgAEABjAAAAJAIAADIAAACoABAAYwAAACoCAAAcAAAAqAAQAGMAAAAmAgAANAAAAKgAEABjAAAAvQUAAAkAAACoABAAYwAAAMAFAAAFAAAAqAAQAGMAAADDBQAAHAAAAKgAEABjAAAAwwUAADMAAACoABAAYwAAAMMFAAAJAAAAqAAQAGMAAADTBQAAIwAAAKgAEABjAAAA0wUAABcAAACoABAAYwAAANQFAAAYAAAAqAAQAGMAAADVBQAACQAAAKgAEABjAAAA3AUAABEAAACoABAAYwAAAN8FAAAYAAAAqAAQAGMAAADgBQAAGQAAAKgAEABjAAAA6AUAAC0AAACoABAAYwAAAOgFAAAhAAAAqAAQAGMAAADpBQAAGAAAAKgAEABjAAAA8gUAABEAAACoABAAYwAAAKcGAAANAAAAqAAQAGMAAACuBgAAEQAAAKgAEABjAAAArwYAABEAAACoABAAYwAAALAGAAARAAAAqAAQAGMAAAD9BgAACQAAAE1hdHJpeCBzbGljaW5nIG91dCBvZiBib3VuZHMuL1VzZXJzL2Rvbm1jY3VyZHkvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbmFsZ2VicmEtMC4xOS4wL3NyYy9iYXNlL21hdHJpeF9zbGljZS5ycwBZAxAAagAAAOMAAAAJAAAAL1VzZXJzL2Rvbm1jY3VyZHkvLnJ1c3R1cC90b29sY2hhaW5zL3N0YWJsZS14ODZfNjQtYXBwbGUtZGFyd2luL2xpYi9ydXN0bGliL3NyYy9ydXN0L2xpYnJhcnkvYWxsb2Mvc3JjL3Jhd192ZWMucnNUcmllZCB0byBzaHJpbmsgdG8gYSBsYXJnZXIgY2FwYWNpdHkAAADUAxAAcQAAAMUBAAAJAAAAAQAAAAAAAAABAAAAAgAAAAMAAAAEAAAABQAAAAQAAAAEAAAABgAAAAcAAAAIAAAACQAAAAAAAAABAAAAAgAAAAMAAAAEAAAAL1VzZXJzL2Rvbm1jY3VyZHkvLnJ1c3R1cC90b29sY2hhaW5zL3N0YWJsZS14ODZfNjQtYXBwbGUtZGFyd2luL2xpYi9ydXN0bGliL3NyYy9ydXN0L2xpYnJhcnkvY29yZS9zcmMvbnVtL21vZC5yc8QEEABwAAAAnQIAAAUAQdCKwAAL0AphdHRlbXB0IHRvIGNhbGN1bGF0ZSB0aGUgcmVtYWluZGVyIHdpdGggYSBkaXZpc29yIG9mIHplcm9NYXRyaXggc2xpY2luZyBvdXQgb2YgYm91bmRzLi9Vc2Vycy9kb25tY2N1cmR5Ly5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL25hbGdlYnJhLTAuMTkuMC9zcmMvYmFzZS9tYXRyaXhfc2xpY2UucnOmBRAAagAAAOMAAAAJAAAAL1VzZXJzL2Rvbm1jY3VyZHkvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbWlra3RzcGFjZS0wLjIuMC9zcmMvZ2VuZXJhdGVkLnJzACAGEABjAAAA2wIAABkAAAAgBhAAYwAAANsCAAAyAAAAIAYQAGMAAAB+AwAACAAAAAoAAAAIAAAABAAAAAsAAAAMAAAADQAAAAgAAAAEAAAADgAAABAAAAAEAAAABAAAABEAAAASAAAAEwAAABAAAAAAAAAAAQAAABQAAABjYWxsZWQgYE9wdGlvbjo6dW53cmFwKClgIG9uIGEgYE5vbmVgIHZhbHVlbGlicmFyeS9zdGQvc3JjL3Bhbmlja2luZy5ycwArBxAAHAAAAO0BAAAfAAAAKwcQABwAAADuAQAAHgAAABUAAAAQAAAABAAAABYAAAAXAAAAEAAAAAgAAAAEAAAAGAAAABkAAAAaAAAADAAAAAQAAAAbAAAAEAAAAAgAAAAEAAAAHAAAAGxpYnJhcnkvYWxsb2Mvc3JjL3Jhd192ZWMucnNjYXBhY2l0eSBvdmVyZmxvdwAAALAHEAAcAAAAHgIAAAUAAAAfAAAAAAAAAAEAAAAgAAAAaW5kZXggb3V0IG9mIGJvdW5kczogdGhlIGxlbiBpcyAgYnV0IHRoZSBpbmRleCBpcyAAAAAIEAAgAAAAIAgQABIAAAAwMDAxMDIwMzA0MDUwNjA3MDgwOTEwMTExMjEzMTQxNTE2MTcxODE5MjAyMTIyMjMyNDI1MjYyNzI4MjkzMDMxMzIzMzM0MzUzNjM3MzgzOTQwNDE0MjQzNDQ0NTQ2NDc0ODQ5NTA1MTUyNTM1NDU1NTY1NzU4NTk2MDYxNjI2MzY0NjU2NjY3Njg2OTcwNzE3MjczNzQ3NTc2Nzc3ODc5ODA4MTgyODM4NDg1ODY4Nzg4ODk5MDkxOTI5Mzk0OTU5Njk3OTg5OWxpYnJhcnkvY29yZS9zcmMvZm10L21vZC5ycwAMCRAAGwAAAFUEAAARAAAADAkQABsAAABfBAAAJAAAAAMAAAAEAAAABAAAAAYAAACD+aIARE5uAPwpFQDRVycA3TT1AGLbwAA8mZUAQZBDAGNR/gC73qsAt2HFADpuJADSTUIASQbgAAnqLgAcktEA6x3+ACmxHADoPqcA9TWCAES7LgCc6YQAtCZwAEF+XwDWkTkAU4M5AJz0OQCLX4QAKPm9APgfOwDe/5cAD5gFABEv7wAKWosAbR9tAM9+NgAJyycARk+3AJ5mPwAt6l8Auid1AOXrxwA9e/EA9zkHAJJSigD7a+oAH7FfAAhdjQAwA1YAe/xGAPCrawAgvM8ANvSaAOOpHQBeYZEACBvmAIWZZQCgFF8AjUBoAIDY/wAnc00ABgYxAMpWFQDJqHMAe+JgAGuMwAAAAABA+yH5PwAAAAAtRHQ+AAAAgJhG+DwAAABgUcx4OwAAAICDG/A5AAAAQCAlejgAAACAIoLjNgAAAAAd82k1AHsJcHJvZHVjZXJzAghsYW5ndWFnZQEEUnVzdAAMcHJvY2Vzc2VkLWJ5AwVydXN0Yx0xLjQ5LjAgKGUxODg0YThlMyAyMDIwLTEyLTI5KQZ3YWxydXMGMC4xOC4wDHdhc20tYmluZGdlbhIwLjIuNzMgKDNjZWZlMmM4Mik='; + +export let wasm; + +export let isReady = false; + +export const ready = fetch(wasmDataURI) + .then((res) => res.arrayBuffer()) + .then((buffer) => WebAssembly.instantiate(buffer, { + './mikktspace_module_bg.js': {__wbindgen_string_new, __wbindgen_rethrow} + })) + .then((result) => { + wasm = result.instance.exports; + isReady = true; + }); diff --git a/jsm/libs/mmdparser.module.js b/jsm/libs/mmdparser.module.js new file mode 100644 index 0000000..5a587f1 --- /dev/null +++ b/jsm/libs/mmdparser.module.js @@ -0,0 +1,11530 @@ +/** + * @author Takahiro / https://github.com/takahirox + * + * Simple CharsetEncoder. + */ + +function CharsetEncoder() { +} + +/* + * Converts from Shift_JIS Uint8Array data to Unicode strings. + */ +CharsetEncoder.prototype.s2u = function(uint8Array) { + var t = this.s2uTable; + var str = ''; + var p = 0; + + while(p < uint8Array.length) { + var key = uint8Array[p++]; + + if(! ((key >= 0x00 && key <= 0x7e) || + (key >= 0xa1 && key <= 0xdf)) && + p < uint8Array.length) { + key = (key << 8) | uint8Array[p++]; + } + + if(t[key] === undefined) { + console.error('unknown char code ' + key + '.'); + return str; + } + + str += String.fromCharCode(t[key]); + } + + return str; +}; + +CharsetEncoder.prototype.s2uTable = { +0:0, +1:1, +2:2, +3:3, +4:4, +5:5, +6:6, +7:7, +8:8, +9:9, +10:10, +11:11, +12:12, +13:13, +14:14, +15:15, +16:16, +17:17, +18:18, +19:19, +20:20, +21:21, +22:22, +23:23, +24:24, +25:25, +26:26, +27:27, +28:28, +29:29, +30:30, +31:31, +32:32, +33:33, +34:34, +35:35, +36:36, +37:37, +38:38, +39:39, +40:40, +41:41, +42:42, +43:43, +44:44, +45:45, +46:46, +47:47, +48:48, +49:49, +50:50, +51:51, +52:52, +53:53, +54:54, +55:55, +56:56, +57:57, +58:58, +59:59, +60:60, +61:61, +62:62, +63:63, +64:64, +65:65, +66:66, +67:67, +68:68, +69:69, +70:70, +71:71, +72:72, +73:73, +74:74, +75:75, +76:76, +77:77, +78:78, +79:79, +80:80, +81:81, +82:82, +83:83, +84:84, +85:85, +86:86, +87:87, +88:88, +89:89, +90:90, +91:91, +92:92, +93:93, +94:94, +95:95, +96:96, +97:97, +98:98, +99:99, +100:100, +101:101, +102:102, +103:103, +104:104, +105:105, +106:106, +107:107, +108:108, +109:109, +110:110, +111:111, +112:112, +113:113, +114:114, +115:115, +116:116, +117:117, +118:118, +119:119, +120:120, +121:121, +122:122, +123:123, +124:124, +125:125, +126:126, +161:65377, +162:65378, +163:65379, +164:65380, +165:65381, +166:65382, +167:65383, +168:65384, +169:65385, +170:65386, +171:65387, +172:65388, +173:65389, +174:65390, +175:65391, +176:65392, +177:65393, +178:65394, +179:65395, +180:65396, +181:65397, +182:65398, +183:65399, +184:65400, +185:65401, +186:65402, +187:65403, +188:65404, +189:65405, +190:65406, +191:65407, +192:65408, +193:65409, +194:65410, +195:65411, +196:65412, +197:65413, +198:65414, +199:65415, +200:65416, +201:65417, +202:65418, +203:65419, +204:65420, +205:65421, +206:65422, +207:65423, +208:65424, +209:65425, +210:65426, +211:65427, +212:65428, +213:65429, +214:65430, +215:65431, +216:65432, +217:65433, +218:65434, +219:65435, +220:65436, +221:65437, +222:65438, +223:65439, +33088:12288, +33089:12289, +33090:12290, +33091:65292, +33092:65294, +33093:12539, +33094:65306, +33095:65307, +33096:65311, +33097:65281, +33098:12443, +33099:12444, +33100:180, +33101:65344, +33102:168, +33103:65342, +33104:65507, +33105:65343, +33106:12541, +33107:12542, +33108:12445, +33109:12446, +33110:12291, +33111:20189, +33112:12293, +33113:12294, +33114:12295, +33115:12540, +33116:8213, +33117:8208, +33118:65295, +33119:65340, +33120:65374, +33121:8741, +33122:65372, +33123:8230, +33124:8229, +33125:8216, +33126:8217, +33127:8220, +33128:8221, +33129:65288, +33130:65289, +33131:12308, +33132:12309, +33133:65339, +33134:65341, +33135:65371, +33136:65373, +33137:12296, +33138:12297, +33139:12298, +33140:12299, +33141:12300, +33142:12301, +33143:12302, +33144:12303, +33145:12304, +33146:12305, +33147:65291, +33148:65293, +33149:177, +33150:215, +33152:247, +33153:65309, +33154:8800, +33155:65308, +33156:65310, +33157:8806, +33158:8807, +33159:8734, +33160:8756, +33161:9794, +33162:9792, +33163:176, +33164:8242, +33165:8243, +33166:8451, +33167:65509, +33168:65284, +33169:65504, +33170:65505, +33171:65285, +33172:65283, +33173:65286, +33174:65290, +33175:65312, +33176:167, +33177:9734, +33178:9733, +33179:9675, +33180:9679, +33181:9678, +33182:9671, +33183:9670, +33184:9633, +33185:9632, +33186:9651, +33187:9650, +33188:9661, +33189:9660, +33190:8251, +33191:12306, +33192:8594, +33193:8592, +33194:8593, +33195:8595, +33196:12307, +33208:8712, +33209:8715, +33210:8838, +33211:8839, +33212:8834, +33213:8835, +33214:8746, +33215:8745, +33224:8743, +33225:8744, +33226:65506, +33227:8658, +33228:8660, +33229:8704, +33230:8707, +33242:8736, +33243:8869, +33244:8978, +33245:8706, +33246:8711, +33247:8801, +33248:8786, +33249:8810, +33250:8811, +33251:8730, +33252:8765, +33253:8733, +33254:8757, +33255:8747, +33256:8748, +33264:8491, +33265:8240, +33266:9839, +33267:9837, +33268:9834, +33269:8224, +33270:8225, +33271:182, +33276:9711, +33359:65296, +33360:65297, +33361:65298, +33362:65299, +33363:65300, +33364:65301, +33365:65302, +33366:65303, +33367:65304, +33368:65305, +33376:65313, +33377:65314, +33378:65315, +33379:65316, +33380:65317, +33381:65318, +33382:65319, +33383:65320, +33384:65321, +33385:65322, +33386:65323, +33387:65324, +33388:65325, +33389:65326, +33390:65327, +33391:65328, +33392:65329, +33393:65330, +33394:65331, +33395:65332, +33396:65333, +33397:65334, +33398:65335, +33399:65336, +33400:65337, +33401:65338, +33409:65345, +33410:65346, +33411:65347, +33412:65348, +33413:65349, +33414:65350, +33415:65351, +33416:65352, +33417:65353, +33418:65354, +33419:65355, +33420:65356, +33421:65357, +33422:65358, +33423:65359, +33424:65360, +33425:65361, +33426:65362, +33427:65363, +33428:65364, +33429:65365, +33430:65366, +33431:65367, +33432:65368, +33433:65369, +33434:65370, +33439:12353, +33440:12354, +33441:12355, +33442:12356, +33443:12357, +33444:12358, +33445:12359, +33446:12360, +33447:12361, +33448:12362, +33449:12363, +33450:12364, +33451:12365, +33452:12366, +33453:12367, +33454:12368, +33455:12369, +33456:12370, +33457:12371, +33458:12372, +33459:12373, +33460:12374, +33461:12375, +33462:12376, +33463:12377, +33464:12378, +33465:12379, +33466:12380, +33467:12381, +33468:12382, +33469:12383, +33470:12384, +33471:12385, +33472:12386, +33473:12387, +33474:12388, +33475:12389, +33476:12390, +33477:12391, +33478:12392, +33479:12393, +33480:12394, +33481:12395, +33482:12396, +33483:12397, +33484:12398, +33485:12399, +33486:12400, +33487:12401, +33488:12402, +33489:12403, +33490:12404, +33491:12405, +33492:12406, +33493:12407, +33494:12408, +33495:12409, +33496:12410, +33497:12411, +33498:12412, +33499:12413, +33500:12414, +33501:12415, +33502:12416, +33503:12417, +33504:12418, +33505:12419, +33506:12420, +33507:12421, +33508:12422, +33509:12423, +33510:12424, +33511:12425, +33512:12426, +33513:12427, +33514:12428, +33515:12429, +33516:12430, +33517:12431, +33518:12432, +33519:12433, +33520:12434, +33521:12435, +33600:12449, +33601:12450, +33602:12451, +33603:12452, +33604:12453, +33605:12454, +33606:12455, +33607:12456, +33608:12457, +33609:12458, +33610:12459, +33611:12460, +33612:12461, +33613:12462, +33614:12463, +33615:12464, +33616:12465, +33617:12466, +33618:12467, +33619:12468, +33620:12469, +33621:12470, +33622:12471, +33623:12472, +33624:12473, +33625:12474, +33626:12475, +33627:12476, +33628:12477, +33629:12478, +33630:12479, +33631:12480, +33632:12481, +33633:12482, +33634:12483, +33635:12484, +33636:12485, +33637:12486, +33638:12487, +33639:12488, +33640:12489, +33641:12490, +33642:12491, +33643:12492, +33644:12493, +33645:12494, +33646:12495, +33647:12496, +33648:12497, +33649:12498, +33650:12499, +33651:12500, +33652:12501, +33653:12502, +33654:12503, +33655:12504, +33656:12505, +33657:12506, +33658:12507, +33659:12508, +33660:12509, +33661:12510, +33662:12511, +33664:12512, +33665:12513, +33666:12514, +33667:12515, +33668:12516, +33669:12517, +33670:12518, +33671:12519, +33672:12520, +33673:12521, +33674:12522, +33675:12523, +33676:12524, +33677:12525, +33678:12526, +33679:12527, +33680:12528, +33681:12529, +33682:12530, +33683:12531, +33684:12532, +33685:12533, +33686:12534, +33695:913, +33696:914, +33697:915, +33698:916, +33699:917, +33700:918, +33701:919, +33702:920, +33703:921, +33704:922, +33705:923, +33706:924, +33707:925, +33708:926, +33709:927, +33710:928, +33711:929, +33712:931, +33713:932, +33714:933, +33715:934, +33716:935, +33717:936, +33718:937, +33727:945, +33728:946, +33729:947, +33730:948, +33731:949, +33732:950, +33733:951, +33734:952, +33735:953, +33736:954, +33737:955, +33738:956, +33739:957, +33740:958, +33741:959, +33742:960, +33743:961, +33744:963, +33745:964, +33746:965, +33747:966, +33748:967, +33749:968, +33750:969, +33856:1040, +33857:1041, +33858:1042, +33859:1043, +33860:1044, +33861:1045, +33862:1025, +33863:1046, +33864:1047, +33865:1048, +33866:1049, +33867:1050, +33868:1051, +33869:1052, +33870:1053, +33871:1054, +33872:1055, +33873:1056, +33874:1057, +33875:1058, +33876:1059, +33877:1060, +33878:1061, +33879:1062, +33880:1063, +33881:1064, +33882:1065, +33883:1066, +33884:1067, +33885:1068, +33886:1069, +33887:1070, +33888:1071, +33904:1072, +33905:1073, +33906:1074, +33907:1075, +33908:1076, +33909:1077, +33910:1105, +33911:1078, +33912:1079, +33913:1080, +33914:1081, +33915:1082, +33916:1083, +33917:1084, +33918:1085, +33920:1086, +33921:1087, +33922:1088, +33923:1089, +33924:1090, +33925:1091, +33926:1092, +33927:1093, +33928:1094, +33929:1095, +33930:1096, +33931:1097, +33932:1098, +33933:1099, +33934:1100, +33935:1101, +33936:1102, +33937:1103, +33951:9472, +33952:9474, +33953:9484, +33954:9488, +33955:9496, +33956:9492, +33957:9500, +33958:9516, +33959:9508, +33960:9524, +33961:9532, +33962:9473, +33963:9475, +33964:9487, +33965:9491, +33966:9499, +33967:9495, +33968:9507, +33969:9523, +33970:9515, +33971:9531, +33972:9547, +33973:9504, +33974:9519, +33975:9512, +33976:9527, +33977:9535, +33978:9501, +33979:9520, +33980:9509, +33981:9528, +33982:9538, +34624:9312, +34625:9313, +34626:9314, +34627:9315, +34628:9316, +34629:9317, +34630:9318, +34631:9319, +34632:9320, +34633:9321, +34634:9322, +34635:9323, +34636:9324, +34637:9325, +34638:9326, +34639:9327, +34640:9328, +34641:9329, +34642:9330, +34643:9331, +34644:8544, +34645:8545, +34646:8546, +34647:8547, +34648:8548, +34649:8549, +34650:8550, +34651:8551, +34652:8552, +34653:8553, +34655:13129, +34656:13076, +34657:13090, +34658:13133, +34659:13080, +34660:13095, +34661:13059, +34662:13110, +34663:13137, +34664:13143, +34665:13069, +34666:13094, +34667:13091, +34668:13099, +34669:13130, +34670:13115, +34671:13212, +34672:13213, +34673:13214, +34674:13198, +34675:13199, +34676:13252, +34677:13217, +34686:13179, +34688:12317, +34689:12319, +34690:8470, +34691:13261, +34692:8481, +34693:12964, +34694:12965, +34695:12966, +34696:12967, +34697:12968, +34698:12849, +34699:12850, +34700:12857, +34701:13182, +34702:13181, +34703:13180, +34704:8786, +34705:8801, +34706:8747, +34707:8750, +34708:8721, +34709:8730, +34710:8869, +34711:8736, +34712:8735, +34713:8895, +34714:8757, +34715:8745, +34716:8746, +34975:20124, +34976:21782, +34977:23043, +34978:38463, +34979:21696, +34980:24859, +34981:25384, +34982:23030, +34983:36898, +34984:33909, +34985:33564, +34986:31312, +34987:24746, +34988:25569, +34989:28197, +34990:26093, +34991:33894, +34992:33446, +34993:39925, +34994:26771, +34995:22311, +34996:26017, +34997:25201, +34998:23451, +34999:22992, +35000:34427, +35001:39156, +35002:32098, +35003:32190, +35004:39822, +35005:25110, +35006:31903, +35007:34999, +35008:23433, +35009:24245, +35010:25353, +35011:26263, +35012:26696, +35013:38343, +35014:38797, +35015:26447, +35016:20197, +35017:20234, +35018:20301, +35019:20381, +35020:20553, +35021:22258, +35022:22839, +35023:22996, +35024:23041, +35025:23561, +35026:24799, +35027:24847, +35028:24944, +35029:26131, +35030:26885, +35031:28858, +35032:30031, +35033:30064, +35034:31227, +35035:32173, +35036:32239, +35037:32963, +35038:33806, +35039:34915, +35040:35586, +35041:36949, +35042:36986, +35043:21307, +35044:20117, +35045:20133, +35046:22495, +35047:32946, +35048:37057, +35049:30959, +35050:19968, +35051:22769, +35052:28322, +35053:36920, +35054:31282, +35055:33576, +35056:33419, +35057:39983, +35058:20801, +35059:21360, +35060:21693, +35061:21729, +35062:22240, +35063:23035, +35064:24341, +35065:39154, +35066:28139, +35067:32996, +35068:34093, +35136:38498, +35137:38512, +35138:38560, +35139:38907, +35140:21515, +35141:21491, +35142:23431, +35143:28879, +35144:32701, +35145:36802, +35146:38632, +35147:21359, +35148:40284, +35149:31418, +35150:19985, +35151:30867, +35152:33276, +35153:28198, +35154:22040, +35155:21764, +35156:27421, +35157:34074, +35158:39995, +35159:23013, +35160:21417, +35161:28006, +35162:29916, +35163:38287, +35164:22082, +35165:20113, +35166:36939, +35167:38642, +35168:33615, +35169:39180, +35170:21473, +35171:21942, +35172:23344, +35173:24433, +35174:26144, +35175:26355, +35176:26628, +35177:27704, +35178:27891, +35179:27945, +35180:29787, +35181:30408, +35182:31310, +35183:38964, +35184:33521, +35185:34907, +35186:35424, +35187:37613, +35188:28082, +35189:30123, +35190:30410, +35191:39365, +35192:24742, +35193:35585, +35194:36234, +35195:38322, +35196:27022, +35197:21421, +35198:20870, +35200:22290, +35201:22576, +35202:22852, +35203:23476, +35204:24310, +35205:24616, +35206:25513, +35207:25588, +35208:27839, +35209:28436, +35210:28814, +35211:28948, +35212:29017, +35213:29141, +35214:29503, +35215:32257, +35216:33398, +35217:33489, +35218:34199, +35219:36960, +35220:37467, +35221:40219, +35222:22633, +35223:26044, +35224:27738, +35225:29989, +35226:20985, +35227:22830, +35228:22885, +35229:24448, +35230:24540, +35231:25276, +35232:26106, +35233:27178, +35234:27431, +35235:27572, +35236:29579, +35237:32705, +35238:35158, +35239:40236, +35240:40206, +35241:40644, +35242:23713, +35243:27798, +35244:33659, +35245:20740, +35246:23627, +35247:25014, +35248:33222, +35249:26742, +35250:29281, +35251:20057, +35252:20474, +35253:21368, +35254:24681, +35255:28201, +35256:31311, +35257:38899, +35258:19979, +35259:21270, +35260:20206, +35261:20309, +35262:20285, +35263:20385, +35264:20339, +35265:21152, +35266:21487, +35267:22025, +35268:22799, +35269:23233, +35270:23478, +35271:23521, +35272:31185, +35273:26247, +35274:26524, +35275:26550, +35276:27468, +35277:27827, +35278:28779, +35279:29634, +35280:31117, +35281:31166, +35282:31292, +35283:31623, +35284:33457, +35285:33499, +35286:33540, +35287:33655, +35288:33775, +35289:33747, +35290:34662, +35291:35506, +35292:22057, +35293:36008, +35294:36838, +35295:36942, +35296:38686, +35297:34442, +35298:20420, +35299:23784, +35300:25105, +35301:29273, +35302:30011, +35303:33253, +35304:33469, +35305:34558, +35306:36032, +35307:38597, +35308:39187, +35309:39381, +35310:20171, +35311:20250, +35312:35299, +35313:22238, +35314:22602, +35315:22730, +35316:24315, +35317:24555, +35318:24618, +35319:24724, +35320:24674, +35321:25040, +35322:25106, +35323:25296, +35324:25913, +35392:39745, +35393:26214, +35394:26800, +35395:28023, +35396:28784, +35397:30028, +35398:30342, +35399:32117, +35400:33445, +35401:34809, +35402:38283, +35403:38542, +35404:35997, +35405:20977, +35406:21182, +35407:22806, +35408:21683, +35409:23475, +35410:23830, +35411:24936, +35412:27010, +35413:28079, +35414:30861, +35415:33995, +35416:34903, +35417:35442, +35418:37799, +35419:39608, +35420:28012, +35421:39336, +35422:34521, +35423:22435, +35424:26623, +35425:34510, +35426:37390, +35427:21123, +35428:22151, +35429:21508, +35430:24275, +35431:25313, +35432:25785, +35433:26684, +35434:26680, +35435:27579, +35436:29554, +35437:30906, +35438:31339, +35439:35226, +35440:35282, +35441:36203, +35442:36611, +35443:37101, +35444:38307, +35445:38548, +35446:38761, +35447:23398, +35448:23731, +35449:27005, +35450:38989, +35451:38990, +35452:25499, +35453:31520, +35454:27179, +35456:27263, +35457:26806, +35458:39949, +35459:28511, +35460:21106, +35461:21917, +35462:24688, +35463:25324, +35464:27963, +35465:28167, +35466:28369, +35467:33883, +35468:35088, +35469:36676, +35470:19988, +35471:39993, +35472:21494, +35473:26907, +35474:27194, +35475:38788, +35476:26666, +35477:20828, +35478:31427, +35479:33970, +35480:37340, +35481:37772, +35482:22107, +35483:40232, +35484:26658, +35485:33541, +35486:33841, +35487:31909, +35488:21000, +35489:33477, +35490:29926, +35491:20094, +35492:20355, +35493:20896, +35494:23506, +35495:21002, +35496:21208, +35497:21223, +35498:24059, +35499:21914, +35500:22570, +35501:23014, +35502:23436, +35503:23448, +35504:23515, +35505:24178, +35506:24185, +35507:24739, +35508:24863, +35509:24931, +35510:25022, +35511:25563, +35512:25954, +35513:26577, +35514:26707, +35515:26874, +35516:27454, +35517:27475, +35518:27735, +35519:28450, +35520:28567, +35521:28485, +35522:29872, +35523:29976, +35524:30435, +35525:30475, +35526:31487, +35527:31649, +35528:31777, +35529:32233, +35530:32566, +35531:32752, +35532:32925, +35533:33382, +35534:33694, +35535:35251, +35536:35532, +35537:36011, +35538:36996, +35539:37969, +35540:38291, +35541:38289, +35542:38306, +35543:38501, +35544:38867, +35545:39208, +35546:33304, +35547:20024, +35548:21547, +35549:23736, +35550:24012, +35551:29609, +35552:30284, +35553:30524, +35554:23721, +35555:32747, +35556:36107, +35557:38593, +35558:38929, +35559:38996, +35560:39000, +35561:20225, +35562:20238, +35563:21361, +35564:21916, +35565:22120, +35566:22522, +35567:22855, +35568:23305, +35569:23492, +35570:23696, +35571:24076, +35572:24190, +35573:24524, +35574:25582, +35575:26426, +35576:26071, +35577:26082, +35578:26399, +35579:26827, +35580:26820, +35648:27231, +35649:24112, +35650:27589, +35651:27671, +35652:27773, +35653:30079, +35654:31048, +35655:23395, +35656:31232, +35657:32000, +35658:24509, +35659:35215, +35660:35352, +35661:36020, +35662:36215, +35663:36556, +35664:36637, +35665:39138, +35666:39438, +35667:39740, +35668:20096, +35669:20605, +35670:20736, +35671:22931, +35672:23452, +35673:25135, +35674:25216, +35675:25836, +35676:27450, +35677:29344, +35678:30097, +35679:31047, +35680:32681, +35681:34811, +35682:35516, +35683:35696, +35684:25516, +35685:33738, +35686:38816, +35687:21513, +35688:21507, +35689:21931, +35690:26708, +35691:27224, +35692:35440, +35693:30759, +35694:26485, +35695:40653, +35696:21364, +35697:23458, +35698:33050, +35699:34384, +35700:36870, +35701:19992, +35702:20037, +35703:20167, +35704:20241, +35705:21450, +35706:21560, +35707:23470, +35708:24339, +35709:24613, +35710:25937, +35712:26429, +35713:27714, +35714:27762, +35715:27875, +35716:28792, +35717:29699, +35718:31350, +35719:31406, +35720:31496, +35721:32026, +35722:31998, +35723:32102, +35724:26087, +35725:29275, +35726:21435, +35727:23621, +35728:24040, +35729:25298, +35730:25312, +35731:25369, +35732:28192, +35733:34394, +35734:35377, +35735:36317, +35736:37624, +35737:28417, +35738:31142, +35739:39770, +35740:20136, +35741:20139, +35742:20140, +35743:20379, +35744:20384, +35745:20689, +35746:20807, +35747:31478, +35748:20849, +35749:20982, +35750:21332, +35751:21281, +35752:21375, +35753:21483, +35754:21932, +35755:22659, +35756:23777, +35757:24375, +35758:24394, +35759:24623, +35760:24656, +35761:24685, +35762:25375, +35763:25945, +35764:27211, +35765:27841, +35766:29378, +35767:29421, +35768:30703, +35769:33016, +35770:33029, +35771:33288, +35772:34126, +35773:37111, +35774:37857, +35775:38911, +35776:39255, +35777:39514, +35778:20208, +35779:20957, +35780:23597, +35781:26241, +35782:26989, +35783:23616, +35784:26354, +35785:26997, +35786:29577, +35787:26704, +35788:31873, +35789:20677, +35790:21220, +35791:22343, +35792:24062, +35793:37670, +35794:26020, +35795:27427, +35796:27453, +35797:29748, +35798:31105, +35799:31165, +35800:31563, +35801:32202, +35802:33465, +35803:33740, +35804:34943, +35805:35167, +35806:35641, +35807:36817, +35808:37329, +35809:21535, +35810:37504, +35811:20061, +35812:20534, +35813:21477, +35814:21306, +35815:29399, +35816:29590, +35817:30697, +35818:33510, +35819:36527, +35820:39366, +35821:39368, +35822:39378, +35823:20855, +35824:24858, +35825:34398, +35826:21936, +35827:31354, +35828:20598, +35829:23507, +35830:36935, +35831:38533, +35832:20018, +35833:27355, +35834:37351, +35835:23633, +35836:23624, +35904:25496, +35905:31391, +35906:27795, +35907:38772, +35908:36705, +35909:31402, +35910:29066, +35911:38536, +35912:31874, +35913:26647, +35914:32368, +35915:26705, +35916:37740, +35917:21234, +35918:21531, +35919:34219, +35920:35347, +35921:32676, +35922:36557, +35923:37089, +35924:21350, +35925:34952, +35926:31041, +35927:20418, +35928:20670, +35929:21009, +35930:20804, +35931:21843, +35932:22317, +35933:29674, +35934:22411, +35935:22865, +35936:24418, +35937:24452, +35938:24693, +35939:24950, +35940:24935, +35941:25001, +35942:25522, +35943:25658, +35944:25964, +35945:26223, +35946:26690, +35947:28179, +35948:30054, +35949:31293, +35950:31995, +35951:32076, +35952:32153, +35953:32331, +35954:32619, +35955:33550, +35956:33610, +35957:34509, +35958:35336, +35959:35427, +35960:35686, +35961:36605, +35962:38938, +35963:40335, +35964:33464, +35965:36814, +35966:39912, +35968:21127, +35969:25119, +35970:25731, +35971:28608, +35972:38553, +35973:26689, +35974:20625, +35975:27424, +35976:27770, +35977:28500, +35978:31348, +35979:32080, +35980:34880, +35981:35363, +35982:26376, +35983:20214, +35984:20537, +35985:20518, +35986:20581, +35987:20860, +35988:21048, +35989:21091, +35990:21927, +35991:22287, +35992:22533, +35993:23244, +35994:24314, +35995:25010, +35996:25080, +35997:25331, +35998:25458, +35999:26908, +36000:27177, +36001:29309, +36002:29356, +36003:29486, +36004:30740, +36005:30831, +36006:32121, +36007:30476, +36008:32937, +36009:35211, +36010:35609, +36011:36066, +36012:36562, +36013:36963, +36014:37749, +36015:38522, +36016:38997, +36017:39443, +36018:40568, +36019:20803, +36020:21407, +36021:21427, +36022:24187, +36023:24358, +36024:28187, +36025:28304, +36026:29572, +36027:29694, +36028:32067, +36029:33335, +36030:35328, +36031:35578, +36032:38480, +36033:20046, +36034:20491, +36035:21476, +36036:21628, +36037:22266, +36038:22993, +36039:23396, +36040:24049, +36041:24235, +36042:24359, +36043:25144, +36044:25925, +36045:26543, +36046:28246, +36047:29392, +36048:31946, +36049:34996, +36050:32929, +36051:32993, +36052:33776, +36053:34382, +36054:35463, +36055:36328, +36056:37431, +36057:38599, +36058:39015, +36059:40723, +36060:20116, +36061:20114, +36062:20237, +36063:21320, +36064:21577, +36065:21566, +36066:23087, +36067:24460, +36068:24481, +36069:24735, +36070:26791, +36071:27278, +36072:29786, +36073:30849, +36074:35486, +36075:35492, +36076:35703, +36077:37264, +36078:20062, +36079:39881, +36080:20132, +36081:20348, +36082:20399, +36083:20505, +36084:20502, +36085:20809, +36086:20844, +36087:21151, +36088:21177, +36089:21246, +36090:21402, +36091:21475, +36092:21521, +36160:21518, +36161:21897, +36162:22353, +36163:22434, +36164:22909, +36165:23380, +36166:23389, +36167:23439, +36168:24037, +36169:24039, +36170:24055, +36171:24184, +36172:24195, +36173:24218, +36174:24247, +36175:24344, +36176:24658, +36177:24908, +36178:25239, +36179:25304, +36180:25511, +36181:25915, +36182:26114, +36183:26179, +36184:26356, +36185:26477, +36186:26657, +36187:26775, +36188:27083, +36189:27743, +36190:27946, +36191:28009, +36192:28207, +36193:28317, +36194:30002, +36195:30343, +36196:30828, +36197:31295, +36198:31968, +36199:32005, +36200:32024, +36201:32094, +36202:32177, +36203:32789, +36204:32771, +36205:32943, +36206:32945, +36207:33108, +36208:33167, +36209:33322, +36210:33618, +36211:34892, +36212:34913, +36213:35611, +36214:36002, +36215:36092, +36216:37066, +36217:37237, +36218:37489, +36219:30783, +36220:37628, +36221:38308, +36222:38477, +36224:38917, +36225:39321, +36226:39640, +36227:40251, +36228:21083, +36229:21163, +36230:21495, +36231:21512, +36232:22741, +36233:25335, +36234:28640, +36235:35946, +36236:36703, +36237:40633, +36238:20811, +36239:21051, +36240:21578, +36241:22269, +36242:31296, +36243:37239, +36244:40288, +36245:40658, +36246:29508, +36247:28425, +36248:33136, +36249:29969, +36250:24573, +36251:24794, +36252:39592, +36253:29403, +36254:36796, +36255:27492, +36256:38915, +36257:20170, +36258:22256, +36259:22372, +36260:22718, +36261:23130, +36262:24680, +36263:25031, +36264:26127, +36265:26118, +36266:26681, +36267:26801, +36268:28151, +36269:30165, +36270:32058, +36271:33390, +36272:39746, +36273:20123, +36274:20304, +36275:21449, +36276:21766, +36277:23919, +36278:24038, +36279:24046, +36280:26619, +36281:27801, +36282:29811, +36283:30722, +36284:35408, +36285:37782, +36286:35039, +36287:22352, +36288:24231, +36289:25387, +36290:20661, +36291:20652, +36292:20877, +36293:26368, +36294:21705, +36295:22622, +36296:22971, +36297:23472, +36298:24425, +36299:25165, +36300:25505, +36301:26685, +36302:27507, +36303:28168, +36304:28797, +36305:37319, +36306:29312, +36307:30741, +36308:30758, +36309:31085, +36310:25998, +36311:32048, +36312:33756, +36313:35009, +36314:36617, +36315:38555, +36316:21092, +36317:22312, +36318:26448, +36319:32618, +36320:36001, +36321:20916, +36322:22338, +36323:38442, +36324:22586, +36325:27018, +36326:32948, +36327:21682, +36328:23822, +36329:22524, +36330:30869, +36331:40442, +36332:20316, +36333:21066, +36334:21643, +36335:25662, +36336:26152, +36337:26388, +36338:26613, +36339:31364, +36340:31574, +36341:32034, +36342:37679, +36343:26716, +36344:39853, +36345:31545, +36346:21273, +36347:20874, +36348:21047, +36416:23519, +36417:25334, +36418:25774, +36419:25830, +36420:26413, +36421:27578, +36422:34217, +36423:38609, +36424:30352, +36425:39894, +36426:25420, +36427:37638, +36428:39851, +36429:30399, +36430:26194, +36431:19977, +36432:20632, +36433:21442, +36434:23665, +36435:24808, +36436:25746, +36437:25955, +36438:26719, +36439:29158, +36440:29642, +36441:29987, +36442:31639, +36443:32386, +36444:34453, +36445:35715, +36446:36059, +36447:37240, +36448:39184, +36449:26028, +36450:26283, +36451:27531, +36452:20181, +36453:20180, +36454:20282, +36455:20351, +36456:21050, +36457:21496, +36458:21490, +36459:21987, +36460:22235, +36461:22763, +36462:22987, +36463:22985, +36464:23039, +36465:23376, +36466:23629, +36467:24066, +36468:24107, +36469:24535, +36470:24605, +36471:25351, +36472:25903, +36473:23388, +36474:26031, +36475:26045, +36476:26088, +36477:26525, +36478:27490, +36480:27515, +36481:27663, +36482:29509, +36483:31049, +36484:31169, +36485:31992, +36486:32025, +36487:32043, +36488:32930, +36489:33026, +36490:33267, +36491:35222, +36492:35422, +36493:35433, +36494:35430, +36495:35468, +36496:35566, +36497:36039, +36498:36060, +36499:38604, +36500:39164, +36501:27503, +36502:20107, +36503:20284, +36504:20365, +36505:20816, +36506:23383, +36507:23546, +36508:24904, +36509:25345, +36510:26178, +36511:27425, +36512:28363, +36513:27835, +36514:29246, +36515:29885, +36516:30164, +36517:30913, +36518:31034, +36519:32780, +36520:32819, +36521:33258, +36522:33940, +36523:36766, +36524:27728, +36525:40575, +36526:24335, +36527:35672, +36528:40235, +36529:31482, +36530:36600, +36531:23437, +36532:38635, +36533:19971, +36534:21489, +36535:22519, +36536:22833, +36537:23241, +36538:23460, +36539:24713, +36540:28287, +36541:28422, +36542:30142, +36543:36074, +36544:23455, +36545:34048, +36546:31712, +36547:20594, +36548:26612, +36549:33437, +36550:23649, +36551:34122, +36552:32286, +36553:33294, +36554:20889, +36555:23556, +36556:25448, +36557:36198, +36558:26012, +36559:29038, +36560:31038, +36561:32023, +36562:32773, +36563:35613, +36564:36554, +36565:36974, +36566:34503, +36567:37034, +36568:20511, +36569:21242, +36570:23610, +36571:26451, +36572:28796, +36573:29237, +36574:37196, +36575:37320, +36576:37675, +36577:33509, +36578:23490, +36579:24369, +36580:24825, +36581:20027, +36582:21462, +36583:23432, +36584:25163, +36585:26417, +36586:27530, +36587:29417, +36588:29664, +36589:31278, +36590:33131, +36591:36259, +36592:37202, +36593:39318, +36594:20754, +36595:21463, +36596:21610, +36597:23551, +36598:25480, +36599:27193, +36600:32172, +36601:38656, +36602:22234, +36603:21454, +36604:21608, +36672:23447, +36673:23601, +36674:24030, +36675:20462, +36676:24833, +36677:25342, +36678:27954, +36679:31168, +36680:31179, +36681:32066, +36682:32333, +36683:32722, +36684:33261, +36685:33311, +36686:33936, +36687:34886, +36688:35186, +36689:35728, +36690:36468, +36691:36655, +36692:36913, +36693:37195, +36694:37228, +36695:38598, +36696:37276, +36697:20160, +36698:20303, +36699:20805, +36700:21313, +36701:24467, +36702:25102, +36703:26580, +36704:27713, +36705:28171, +36706:29539, +36707:32294, +36708:37325, +36709:37507, +36710:21460, +36711:22809, +36712:23487, +36713:28113, +36714:31069, +36715:32302, +36716:31899, +36717:22654, +36718:29087, +36719:20986, +36720:34899, +36721:36848, +36722:20426, +36723:23803, +36724:26149, +36725:30636, +36726:31459, +36727:33308, +36728:39423, +36729:20934, +36730:24490, +36731:26092, +36732:26991, +36733:27529, +36734:28147, +36736:28310, +36737:28516, +36738:30462, +36739:32020, +36740:24033, +36741:36981, +36742:37255, +36743:38918, +36744:20966, +36745:21021, +36746:25152, +36747:26257, +36748:26329, +36749:28186, +36750:24246, +36751:32210, +36752:32626, +36753:26360, +36754:34223, +36755:34295, +36756:35576, +36757:21161, +36758:21465, +36759:22899, +36760:24207, +36761:24464, +36762:24661, +36763:37604, +36764:38500, +36765:20663, +36766:20767, +36767:21213, +36768:21280, +36769:21319, +36770:21484, +36771:21736, +36772:21830, +36773:21809, +36774:22039, +36775:22888, +36776:22974, +36777:23100, +36778:23477, +36779:23558, +36780:23567, +36781:23569, +36782:23578, +36783:24196, +36784:24202, +36785:24288, +36786:24432, +36787:25215, +36788:25220, +36789:25307, +36790:25484, +36791:25463, +36792:26119, +36793:26124, +36794:26157, +36795:26230, +36796:26494, +36797:26786, +36798:27167, +36799:27189, +36800:27836, +36801:28040, +36802:28169, +36803:28248, +36804:28988, +36805:28966, +36806:29031, +36807:30151, +36808:30465, +36809:30813, +36810:30977, +36811:31077, +36812:31216, +36813:31456, +36814:31505, +36815:31911, +36816:32057, +36817:32918, +36818:33750, +36819:33931, +36820:34121, +36821:34909, +36822:35059, +36823:35359, +36824:35388, +36825:35412, +36826:35443, +36827:35937, +36828:36062, +36829:37284, +36830:37478, +36831:37758, +36832:37912, +36833:38556, +36834:38808, +36835:19978, +36836:19976, +36837:19998, +36838:20055, +36839:20887, +36840:21104, +36841:22478, +36842:22580, +36843:22732, +36844:23330, +36845:24120, +36846:24773, +36847:25854, +36848:26465, +36849:26454, +36850:27972, +36851:29366, +36852:30067, +36853:31331, +36854:33976, +36855:35698, +36856:37304, +36857:37664, +36858:22065, +36859:22516, +36860:39166, +36928:25325, +36929:26893, +36930:27542, +36931:29165, +36932:32340, +36933:32887, +36934:33394, +36935:35302, +36936:39135, +36937:34645, +36938:36785, +36939:23611, +36940:20280, +36941:20449, +36942:20405, +36943:21767, +36944:23072, +36945:23517, +36946:23529, +36947:24515, +36948:24910, +36949:25391, +36950:26032, +36951:26187, +36952:26862, +36953:27035, +36954:28024, +36955:28145, +36956:30003, +36957:30137, +36958:30495, +36959:31070, +36960:31206, +36961:32051, +36962:33251, +36963:33455, +36964:34218, +36965:35242, +36966:35386, +36967:36523, +36968:36763, +36969:36914, +36970:37341, +36971:38663, +36972:20154, +36973:20161, +36974:20995, +36975:22645, +36976:22764, +36977:23563, +36978:29978, +36979:23613, +36980:33102, +36981:35338, +36982:36805, +36983:38499, +36984:38765, +36985:31525, +36986:35535, +36987:38920, +36988:37218, +36989:22259, +36990:21416, +36992:36887, +36993:21561, +36994:22402, +36995:24101, +36996:25512, +36997:27700, +36998:28810, +36999:30561, +37000:31883, +37001:32736, +37002:34928, +37003:36930, +37004:37204, +37005:37648, +37006:37656, +37007:38543, +37008:29790, +37009:39620, +37010:23815, +37011:23913, +37012:25968, +37013:26530, +37014:36264, +37015:38619, +37016:25454, +37017:26441, +37018:26905, +37019:33733, +37020:38935, +37021:38592, +37022:35070, +37023:28548, +37024:25722, +37025:23544, +37026:19990, +37027:28716, +37028:30045, +37029:26159, +37030:20932, +37031:21046, +37032:21218, +37033:22995, +37034:24449, +37035:24615, +37036:25104, +37037:25919, +37038:25972, +37039:26143, +37040:26228, +37041:26866, +37042:26646, +37043:27491, +37044:28165, +37045:29298, +37046:29983, +37047:30427, +37048:31934, +37049:32854, +37050:22768, +37051:35069, +37052:35199, +37053:35488, +37054:35475, +37055:35531, +37056:36893, +37057:37266, +37058:38738, +37059:38745, +37060:25993, +37061:31246, +37062:33030, +37063:38587, +37064:24109, +37065:24796, +37066:25114, +37067:26021, +37068:26132, +37069:26512, +37070:30707, +37071:31309, +37072:31821, +37073:32318, +37074:33034, +37075:36012, +37076:36196, +37077:36321, +37078:36447, +37079:30889, +37080:20999, +37081:25305, +37082:25509, +37083:25666, +37084:25240, +37085:35373, +37086:31363, +37087:31680, +37088:35500, +37089:38634, +37090:32118, +37091:33292, +37092:34633, +37093:20185, +37094:20808, +37095:21315, +37096:21344, +37097:23459, +37098:23554, +37099:23574, +37100:24029, +37101:25126, +37102:25159, +37103:25776, +37104:26643, +37105:26676, +37106:27849, +37107:27973, +37108:27927, +37109:26579, +37110:28508, +37111:29006, +37112:29053, +37113:26059, +37114:31359, +37115:31661, +37116:32218, +37184:32330, +37185:32680, +37186:33146, +37187:33307, +37188:33337, +37189:34214, +37190:35438, +37191:36046, +37192:36341, +37193:36984, +37194:36983, +37195:37549, +37196:37521, +37197:38275, +37198:39854, +37199:21069, +37200:21892, +37201:28472, +37202:28982, +37203:20840, +37204:31109, +37205:32341, +37206:33203, +37207:31950, +37208:22092, +37209:22609, +37210:23720, +37211:25514, +37212:26366, +37213:26365, +37214:26970, +37215:29401, +37216:30095, +37217:30094, +37218:30990, +37219:31062, +37220:31199, +37221:31895, +37222:32032, +37223:32068, +37224:34311, +37225:35380, +37226:38459, +37227:36961, +37228:40736, +37229:20711, +37230:21109, +37231:21452, +37232:21474, +37233:20489, +37234:21930, +37235:22766, +37236:22863, +37237:29245, +37238:23435, +37239:23652, +37240:21277, +37241:24803, +37242:24819, +37243:25436, +37244:25475, +37245:25407, +37246:25531, +37248:25805, +37249:26089, +37250:26361, +37251:24035, +37252:27085, +37253:27133, +37254:28437, +37255:29157, +37256:20105, +37257:30185, +37258:30456, +37259:31379, +37260:31967, +37261:32207, +37262:32156, +37263:32865, +37264:33609, +37265:33624, +37266:33900, +37267:33980, +37268:34299, +37269:35013, +37270:36208, +37271:36865, +37272:36973, +37273:37783, +37274:38684, +37275:39442, +37276:20687, +37277:22679, +37278:24974, +37279:33235, +37280:34101, +37281:36104, +37282:36896, +37283:20419, +37284:20596, +37285:21063, +37286:21363, +37287:24687, +37288:25417, +37289:26463, +37290:28204, +37291:36275, +37292:36895, +37293:20439, +37294:23646, +37295:36042, +37296:26063, +37297:32154, +37298:21330, +37299:34966, +37300:20854, +37301:25539, +37302:23384, +37303:23403, +37304:23562, +37305:25613, +37306:26449, +37307:36956, +37308:20182, +37309:22810, +37310:22826, +37311:27760, +37312:35409, +37313:21822, +37314:22549, +37315:22949, +37316:24816, +37317:25171, +37318:26561, +37319:33333, +37320:26965, +37321:38464, +37322:39364, +37323:39464, +37324:20307, +37325:22534, +37326:23550, +37327:32784, +37328:23729, +37329:24111, +37330:24453, +37331:24608, +37332:24907, +37333:25140, +37334:26367, +37335:27888, +37336:28382, +37337:32974, +37338:33151, +37339:33492, +37340:34955, +37341:36024, +37342:36864, +37343:36910, +37344:38538, +37345:40667, +37346:39899, +37347:20195, +37348:21488, +37349:22823, +37350:31532, +37351:37261, +37352:38988, +37353:40441, +37354:28381, +37355:28711, +37356:21331, +37357:21828, +37358:23429, +37359:25176, +37360:25246, +37361:25299, +37362:27810, +37363:28655, +37364:29730, +37365:35351, +37366:37944, +37367:28609, +37368:35582, +37369:33592, +37370:20967, +37371:34552, +37372:21482, +37440:21481, +37441:20294, +37442:36948, +37443:36784, +37444:22890, +37445:33073, +37446:24061, +37447:31466, +37448:36799, +37449:26842, +37450:35895, +37451:29432, +37452:40008, +37453:27197, +37454:35504, +37455:20025, +37456:21336, +37457:22022, +37458:22374, +37459:25285, +37460:25506, +37461:26086, +37462:27470, +37463:28129, +37464:28251, +37465:28845, +37466:30701, +37467:31471, +37468:31658, +37469:32187, +37470:32829, +37471:32966, +37472:34507, +37473:35477, +37474:37723, +37475:22243, +37476:22727, +37477:24382, +37478:26029, +37479:26262, +37480:27264, +37481:27573, +37482:30007, +37483:35527, +37484:20516, +37485:30693, +37486:22320, +37487:24347, +37488:24677, +37489:26234, +37490:27744, +37491:30196, +37492:31258, +37493:32622, +37494:33268, +37495:34584, +37496:36933, +37497:39347, +37498:31689, +37499:30044, +37500:31481, +37501:31569, +37502:33988, +37504:36880, +37505:31209, +37506:31378, +37507:33590, +37508:23265, +37509:30528, +37510:20013, +37511:20210, +37512:23449, +37513:24544, +37514:25277, +37515:26172, +37516:26609, +37517:27880, +37518:34411, +37519:34935, +37520:35387, +37521:37198, +37522:37619, +37523:39376, +37524:27159, +37525:28710, +37526:29482, +37527:33511, +37528:33879, +37529:36015, +37530:19969, +37531:20806, +37532:20939, +37533:21899, +37534:23541, +37535:24086, +37536:24115, +37537:24193, +37538:24340, +37539:24373, +37540:24427, +37541:24500, +37542:25074, +37543:25361, +37544:26274, +37545:26397, +37546:28526, +37547:29266, +37548:30010, +37549:30522, +37550:32884, +37551:33081, +37552:33144, +37553:34678, +37554:35519, +37555:35548, +37556:36229, +37557:36339, +37558:37530, +37559:38263, +37560:38914, +37561:40165, +37562:21189, +37563:25431, +37564:30452, +37565:26389, +37566:27784, +37567:29645, +37568:36035, +37569:37806, +37570:38515, +37571:27941, +37572:22684, +37573:26894, +37574:27084, +37575:36861, +37576:37786, +37577:30171, +37578:36890, +37579:22618, +37580:26626, +37581:25524, +37582:27131, +37583:20291, +37584:28460, +37585:26584, +37586:36795, +37587:34086, +37588:32180, +37589:37716, +37590:26943, +37591:28528, +37592:22378, +37593:22775, +37594:23340, +37595:32044, +37596:29226, +37597:21514, +37598:37347, +37599:40372, +37600:20141, +37601:20302, +37602:20572, +37603:20597, +37604:21059, +37605:35998, +37606:21576, +37607:22564, +37608:23450, +37609:24093, +37610:24213, +37611:24237, +37612:24311, +37613:24351, +37614:24716, +37615:25269, +37616:25402, +37617:25552, +37618:26799, +37619:27712, +37620:30855, +37621:31118, +37622:31243, +37623:32224, +37624:33351, +37625:35330, +37626:35558, +37627:36420, +37628:36883, +37696:37048, +37697:37165, +37698:37336, +37699:40718, +37700:27877, +37701:25688, +37702:25826, +37703:25973, +37704:28404, +37705:30340, +37706:31515, +37707:36969, +37708:37841, +37709:28346, +37710:21746, +37711:24505, +37712:25764, +37713:36685, +37714:36845, +37715:37444, +37716:20856, +37717:22635, +37718:22825, +37719:23637, +37720:24215, +37721:28155, +37722:32399, +37723:29980, +37724:36028, +37725:36578, +37726:39003, +37727:28857, +37728:20253, +37729:27583, +37730:28593, +37731:30000, +37732:38651, +37733:20814, +37734:21520, +37735:22581, +37736:22615, +37737:22956, +37738:23648, +37739:24466, +37740:26007, +37741:26460, +37742:28193, +37743:30331, +37744:33759, +37745:36077, +37746:36884, +37747:37117, +37748:37709, +37749:30757, +37750:30778, +37751:21162, +37752:24230, +37753:22303, +37754:22900, +37755:24594, +37756:20498, +37757:20826, +37758:20908, +37760:20941, +37761:20992, +37762:21776, +37763:22612, +37764:22616, +37765:22871, +37766:23445, +37767:23798, +37768:23947, +37769:24764, +37770:25237, +37771:25645, +37772:26481, +37773:26691, +37774:26812, +37775:26847, +37776:30423, +37777:28120, +37778:28271, +37779:28059, +37780:28783, +37781:29128, +37782:24403, +37783:30168, +37784:31095, +37785:31561, +37786:31572, +37787:31570, +37788:31958, +37789:32113, +37790:21040, +37791:33891, +37792:34153, +37793:34276, +37794:35342, +37795:35588, +37796:35910, +37797:36367, +37798:36867, +37799:36879, +37800:37913, +37801:38518, +37802:38957, +37803:39472, +37804:38360, +37805:20685, +37806:21205, +37807:21516, +37808:22530, +37809:23566, +37810:24999, +37811:25758, +37812:27934, +37813:30643, +37814:31461, +37815:33012, +37816:33796, +37817:36947, +37818:37509, +37819:23776, +37820:40199, +37821:21311, +37822:24471, +37823:24499, +37824:28060, +37825:29305, +37826:30563, +37827:31167, +37828:31716, +37829:27602, +37830:29420, +37831:35501, +37832:26627, +37833:27233, +37834:20984, +37835:31361, +37836:26932, +37837:23626, +37838:40182, +37839:33515, +37840:23493, +37841:37193, +37842:28702, +37843:22136, +37844:23663, +37845:24775, +37846:25958, +37847:27788, +37848:35930, +37849:36929, +37850:38931, +37851:21585, +37852:26311, +37853:37389, +37854:22856, +37855:37027, +37856:20869, +37857:20045, +37858:20970, +37859:34201, +37860:35598, +37861:28760, +37862:25466, +37863:37707, +37864:26978, +37865:39348, +37866:32260, +37867:30071, +37868:21335, +37869:26976, +37870:36575, +37871:38627, +37872:27741, +37873:20108, +37874:23612, +37875:24336, +37876:36841, +37877:21250, +37878:36049, +37879:32905, +37880:34425, +37881:24319, +37882:26085, +37883:20083, +37884:20837, +37952:22914, +37953:23615, +37954:38894, +37955:20219, +37956:22922, +37957:24525, +37958:35469, +37959:28641, +37960:31152, +37961:31074, +37962:23527, +37963:33905, +37964:29483, +37965:29105, +37966:24180, +37967:24565, +37968:25467, +37969:25754, +37970:29123, +37971:31896, +37972:20035, +37973:24316, +37974:20043, +37975:22492, +37976:22178, +37977:24745, +37978:28611, +37979:32013, +37980:33021, +37981:33075, +37982:33215, +37983:36786, +37984:35223, +37985:34468, +37986:24052, +37987:25226, +37988:25773, +37989:35207, +37990:26487, +37991:27874, +37992:27966, +37993:29750, +37994:30772, +37995:23110, +37996:32629, +37997:33453, +37998:39340, +37999:20467, +38000:24259, +38001:25309, +38002:25490, +38003:25943, +38004:26479, +38005:30403, +38006:29260, +38007:32972, +38008:32954, +38009:36649, +38010:37197, +38011:20493, +38012:22521, +38013:23186, +38014:26757, +38016:26995, +38017:29028, +38018:29437, +38019:36023, +38020:22770, +38021:36064, +38022:38506, +38023:36889, +38024:34687, +38025:31204, +38026:30695, +38027:33833, +38028:20271, +38029:21093, +38030:21338, +38031:25293, +38032:26575, +38033:27850, +38034:30333, +38035:31636, +38036:31893, +38037:33334, +38038:34180, +38039:36843, +38040:26333, +38041:28448, +38042:29190, +38043:32283, +38044:33707, +38045:39361, +38046:40614, +38047:20989, +38048:31665, +38049:30834, +38050:31672, +38051:32903, +38052:31560, +38053:27368, +38054:24161, +38055:32908, +38056:30033, +38057:30048, +38058:20843, +38059:37474, +38060:28300, +38061:30330, +38062:37271, +38063:39658, +38064:20240, +38065:32624, +38066:25244, +38067:31567, +38068:38309, +38069:40169, +38070:22138, +38071:22617, +38072:34532, +38073:38588, +38074:20276, +38075:21028, +38076:21322, +38077:21453, +38078:21467, +38079:24070, +38080:25644, +38081:26001, +38082:26495, +38083:27710, +38084:27726, +38085:29256, +38086:29359, +38087:29677, +38088:30036, +38089:32321, +38090:33324, +38091:34281, +38092:36009, +38093:31684, +38094:37318, +38095:29033, +38096:38930, +38097:39151, +38098:25405, +38099:26217, +38100:30058, +38101:30436, +38102:30928, +38103:34115, +38104:34542, +38105:21290, +38106:21329, +38107:21542, +38108:22915, +38109:24199, +38110:24444, +38111:24754, +38112:25161, +38113:25209, +38114:25259, +38115:26000, +38116:27604, +38117:27852, +38118:30130, +38119:30382, +38120:30865, +38121:31192, +38122:32203, +38123:32631, +38124:32933, +38125:34987, +38126:35513, +38127:36027, +38128:36991, +38129:38750, +38130:39131, +38131:27147, +38132:31800, +38133:20633, +38134:23614, +38135:24494, +38136:26503, +38137:27608, +38138:29749, +38139:30473, +38140:32654, +38208:40763, +38209:26570, +38210:31255, +38211:21305, +38212:30091, +38213:39661, +38214:24422, +38215:33181, +38216:33777, +38217:32920, +38218:24380, +38219:24517, +38220:30050, +38221:31558, +38222:36924, +38223:26727, +38224:23019, +38225:23195, +38226:32016, +38227:30334, +38228:35628, +38229:20469, +38230:24426, +38231:27161, +38232:27703, +38233:28418, +38234:29922, +38235:31080, +38236:34920, +38237:35413, +38238:35961, +38239:24287, +38240:25551, +38241:30149, +38242:31186, +38243:33495, +38244:37672, +38245:37618, +38246:33948, +38247:34541, +38248:39981, +38249:21697, +38250:24428, +38251:25996, +38252:27996, +38253:28693, +38254:36007, +38255:36051, +38256:38971, +38257:25935, +38258:29942, +38259:19981, +38260:20184, +38261:22496, +38262:22827, +38263:23142, +38264:23500, +38265:20904, +38266:24067, +38267:24220, +38268:24598, +38269:25206, +38270:25975, +38272:26023, +38273:26222, +38274:28014, +38275:29238, +38276:31526, +38277:33104, +38278:33178, +38279:33433, +38280:35676, +38281:36000, +38282:36070, +38283:36212, +38284:38428, +38285:38468, +38286:20398, +38287:25771, +38288:27494, +38289:33310, +38290:33889, +38291:34154, +38292:37096, +38293:23553, +38294:26963, +38295:39080, +38296:33914, +38297:34135, +38298:20239, +38299:21103, +38300:24489, +38301:24133, +38302:26381, +38303:31119, +38304:33145, +38305:35079, +38306:35206, +38307:28149, +38308:24343, +38309:25173, +38310:27832, +38311:20175, +38312:29289, +38313:39826, +38314:20998, +38315:21563, +38316:22132, +38317:22707, +38318:24996, +38319:25198, +38320:28954, +38321:22894, +38322:31881, +38323:31966, +38324:32027, +38325:38640, +38326:25991, +38327:32862, +38328:19993, +38329:20341, +38330:20853, +38331:22592, +38332:24163, +38333:24179, +38334:24330, +38335:26564, +38336:20006, +38337:34109, +38338:38281, +38339:38491, +38340:31859, +38341:38913, +38342:20731, +38343:22721, +38344:30294, +38345:30887, +38346:21029, +38347:30629, +38348:34065, +38349:31622, +38350:20559, +38351:22793, +38352:29255, +38353:31687, +38354:32232, +38355:36794, +38356:36820, +38357:36941, +38358:20415, +38359:21193, +38360:23081, +38361:24321, +38362:38829, +38363:20445, +38364:33303, +38365:37610, +38366:22275, +38367:25429, +38368:27497, +38369:29995, +38370:35036, +38371:36628, +38372:31298, +38373:21215, +38374:22675, +38375:24917, +38376:25098, +38377:26286, +38378:27597, +38379:31807, +38380:33769, +38381:20515, +38382:20472, +38383:21253, +38384:21574, +38385:22577, +38386:22857, +38387:23453, +38388:23792, +38389:23791, +38390:23849, +38391:24214, +38392:25265, +38393:25447, +38394:25918, +38395:26041, +38396:26379, +38464:27861, +38465:27873, +38466:28921, +38467:30770, +38468:32299, +38469:32990, +38470:33459, +38471:33804, +38472:34028, +38473:34562, +38474:35090, +38475:35370, +38476:35914, +38477:37030, +38478:37586, +38479:39165, +38480:40179, +38481:40300, +38482:20047, +38483:20129, +38484:20621, +38485:21078, +38486:22346, +38487:22952, +38488:24125, +38489:24536, +38490:24537, +38491:25151, +38492:26292, +38493:26395, +38494:26576, +38495:26834, +38496:20882, +38497:32033, +38498:32938, +38499:33192, +38500:35584, +38501:35980, +38502:36031, +38503:37502, +38504:38450, +38505:21536, +38506:38956, +38507:21271, +38508:20693, +38509:21340, +38510:22696, +38511:25778, +38512:26420, +38513:29287, +38514:30566, +38515:31302, +38516:37350, +38517:21187, +38518:27809, +38519:27526, +38520:22528, +38521:24140, +38522:22868, +38523:26412, +38524:32763, +38525:20961, +38526:30406, +38528:25705, +38529:30952, +38530:39764, +38531:40635, +38532:22475, +38533:22969, +38534:26151, +38535:26522, +38536:27598, +38537:21737, +38538:27097, +38539:24149, +38540:33180, +38541:26517, +38542:39850, +38543:26622, +38544:40018, +38545:26717, +38546:20134, +38547:20451, +38548:21448, +38549:25273, +38550:26411, +38551:27819, +38552:36804, +38553:20397, +38554:32365, +38555:40639, +38556:19975, +38557:24930, +38558:28288, +38559:28459, +38560:34067, +38561:21619, +38562:26410, +38563:39749, +38564:24051, +38565:31637, +38566:23724, +38567:23494, +38568:34588, +38569:28234, +38570:34001, +38571:31252, +38572:33032, +38573:22937, +38574:31885, +38575:27665, +38576:30496, +38577:21209, +38578:22818, +38579:28961, +38580:29279, +38581:30683, +38582:38695, +38583:40289, +38584:26891, +38585:23167, +38586:23064, +38587:20901, +38588:21517, +38589:21629, +38590:26126, +38591:30431, +38592:36855, +38593:37528, +38594:40180, +38595:23018, +38596:29277, +38597:28357, +38598:20813, +38599:26825, +38600:32191, +38601:32236, +38602:38754, +38603:40634, +38604:25720, +38605:27169, +38606:33538, +38607:22916, +38608:23391, +38609:27611, +38610:29467, +38611:30450, +38612:32178, +38613:32791, +38614:33945, +38615:20786, +38616:26408, +38617:40665, +38618:30446, +38619:26466, +38620:21247, +38621:39173, +38622:23588, +38623:25147, +38624:31870, +38625:36016, +38626:21839, +38627:24758, +38628:32011, +38629:38272, +38630:21249, +38631:20063, +38632:20918, +38633:22812, +38634:29242, +38635:32822, +38636:37326, +38637:24357, +38638:30690, +38639:21380, +38640:24441, +38641:32004, +38642:34220, +38643:35379, +38644:36493, +38645:38742, +38646:26611, +38647:34222, +38648:37971, +38649:24841, +38650:24840, +38651:27833, +38652:30290, +38720:35565, +38721:36664, +38722:21807, +38723:20305, +38724:20778, +38725:21191, +38726:21451, +38727:23461, +38728:24189, +38729:24736, +38730:24962, +38731:25558, +38732:26377, +38733:26586, +38734:28263, +38735:28044, +38736:29494, +38737:29495, +38738:30001, +38739:31056, +38740:35029, +38741:35480, +38742:36938, +38743:37009, +38744:37109, +38745:38596, +38746:34701, +38747:22805, +38748:20104, +38749:20313, +38750:19982, +38751:35465, +38752:36671, +38753:38928, +38754:20653, +38755:24188, +38756:22934, +38757:23481, +38758:24248, +38759:25562, +38760:25594, +38761:25793, +38762:26332, +38763:26954, +38764:27096, +38765:27915, +38766:28342, +38767:29076, +38768:29992, +38769:31407, +38770:32650, +38771:32768, +38772:33865, +38773:33993, +38774:35201, +38775:35617, +38776:36362, +38777:36965, +38778:38525, +38779:39178, +38780:24958, +38781:25233, +38782:27442, +38784:27779, +38785:28020, +38786:32716, +38787:32764, +38788:28096, +38789:32645, +38790:34746, +38791:35064, +38792:26469, +38793:33713, +38794:38972, +38795:38647, +38796:27931, +38797:32097, +38798:33853, +38799:37226, +38800:20081, +38801:21365, +38802:23888, +38803:27396, +38804:28651, +38805:34253, +38806:34349, +38807:35239, +38808:21033, +38809:21519, +38810:23653, +38811:26446, +38812:26792, +38813:29702, +38814:29827, +38815:30178, +38816:35023, +38817:35041, +38818:37324, +38819:38626, +38820:38520, +38821:24459, +38822:29575, +38823:31435, +38824:33870, +38825:25504, +38826:30053, +38827:21129, +38828:27969, +38829:28316, +38830:29705, +38831:30041, +38832:30827, +38833:31890, +38834:38534, +38835:31452, +38836:40845, +38837:20406, +38838:24942, +38839:26053, +38840:34396, +38841:20102, +38842:20142, +38843:20698, +38844:20001, +38845:20940, +38846:23534, +38847:26009, +38848:26753, +38849:28092, +38850:29471, +38851:30274, +38852:30637, +38853:31260, +38854:31975, +38855:33391, +38856:35538, +38857:36988, +38858:37327, +38859:38517, +38860:38936, +38861:21147, +38862:32209, +38863:20523, +38864:21400, +38865:26519, +38866:28107, +38867:29136, +38868:29747, +38869:33256, +38870:36650, +38871:38563, +38872:40023, +38873:40607, +38874:29792, +38875:22593, +38876:28057, +38877:32047, +38878:39006, +38879:20196, +38880:20278, +38881:20363, +38882:20919, +38883:21169, +38884:23994, +38885:24604, +38886:29618, +38887:31036, +38888:33491, +38889:37428, +38890:38583, +38891:38646, +38892:38666, +38893:40599, +38894:40802, +38895:26278, +38896:27508, +38897:21015, +38898:21155, +38899:28872, +38900:35010, +38901:24265, +38902:24651, +38903:24976, +38904:28451, +38905:29001, +38906:31806, +38907:32244, +38908:32879, +38976:34030, +38977:36899, +38978:37676, +38979:21570, +38980:39791, +38981:27347, +38982:28809, +38983:36034, +38984:36335, +38985:38706, +38986:21172, +38987:23105, +38988:24266, +38989:24324, +38990:26391, +38991:27004, +38992:27028, +38993:28010, +38994:28431, +38995:29282, +38996:29436, +38997:31725, +38998:32769, +38999:32894, +39000:34635, +39001:37070, +39002:20845, +39003:40595, +39004:31108, +39005:32907, +39006:37682, +39007:35542, +39008:20525, +39009:21644, +39010:35441, +39011:27498, +39012:36036, +39013:33031, +39014:24785, +39015:26528, +39016:40434, +39017:20121, +39018:20120, +39019:39952, +39020:35435, +39021:34241, +39022:34152, +39023:26880, +39024:28286, +39025:30871, +39026:33109, +39071:24332, +39072:19984, +39073:19989, +39074:20010, +39075:20017, +39076:20022, +39077:20028, +39078:20031, +39079:20034, +39080:20054, +39081:20056, +39082:20098, +39083:20101, +39084:35947, +39085:20106, +39086:33298, +39087:24333, +39088:20110, +39089:20126, +39090:20127, +39091:20128, +39092:20130, +39093:20144, +39094:20147, +39095:20150, +39096:20174, +39097:20173, +39098:20164, +39099:20166, +39100:20162, +39101:20183, +39102:20190, +39103:20205, +39104:20191, +39105:20215, +39106:20233, +39107:20314, +39108:20272, +39109:20315, +39110:20317, +39111:20311, +39112:20295, +39113:20342, +39114:20360, +39115:20367, +39116:20376, +39117:20347, +39118:20329, +39119:20336, +39120:20369, +39121:20335, +39122:20358, +39123:20374, +39124:20760, +39125:20436, +39126:20447, +39127:20430, +39128:20440, +39129:20443, +39130:20433, +39131:20442, +39132:20432, +39133:20452, +39134:20453, +39135:20506, +39136:20520, +39137:20500, +39138:20522, +39139:20517, +39140:20485, +39141:20252, +39142:20470, +39143:20513, +39144:20521, +39145:20524, +39146:20478, +39147:20463, +39148:20497, +39149:20486, +39150:20547, +39151:20551, +39152:26371, +39153:20565, +39154:20560, +39155:20552, +39156:20570, +39157:20566, +39158:20588, +39159:20600, +39160:20608, +39161:20634, +39162:20613, +39163:20660, +39164:20658, +39232:20681, +39233:20682, +39234:20659, +39235:20674, +39236:20694, +39237:20702, +39238:20709, +39239:20717, +39240:20707, +39241:20718, +39242:20729, +39243:20725, +39244:20745, +39245:20737, +39246:20738, +39247:20758, +39248:20757, +39249:20756, +39250:20762, +39251:20769, +39252:20794, +39253:20791, +39254:20796, +39255:20795, +39256:20799, +39257:20800, +39258:20818, +39259:20812, +39260:20820, +39261:20834, +39262:31480, +39263:20841, +39264:20842, +39265:20846, +39266:20864, +39267:20866, +39268:22232, +39269:20876, +39270:20873, +39271:20879, +39272:20881, +39273:20883, +39274:20885, +39275:20886, +39276:20900, +39277:20902, +39278:20898, +39279:20905, +39280:20906, +39281:20907, +39282:20915, +39283:20913, +39284:20914, +39285:20912, +39286:20917, +39287:20925, +39288:20933, +39289:20937, +39290:20955, +39291:20960, +39292:34389, +39293:20969, +39294:20973, +39296:20976, +39297:20981, +39298:20990, +39299:20996, +39300:21003, +39301:21012, +39302:21006, +39303:21031, +39304:21034, +39305:21038, +39306:21043, +39307:21049, +39308:21071, +39309:21060, +39310:21067, +39311:21068, +39312:21086, +39313:21076, +39314:21098, +39315:21108, +39316:21097, +39317:21107, +39318:21119, +39319:21117, +39320:21133, +39321:21140, +39322:21138, +39323:21105, +39324:21128, +39325:21137, +39326:36776, +39327:36775, +39328:21164, +39329:21165, +39330:21180, +39331:21173, +39332:21185, +39333:21197, +39334:21207, +39335:21214, +39336:21219, +39337:21222, +39338:39149, +39339:21216, +39340:21235, +39341:21237, +39342:21240, +39343:21241, +39344:21254, +39345:21256, +39346:30008, +39347:21261, +39348:21264, +39349:21263, +39350:21269, +39351:21274, +39352:21283, +39353:21295, +39354:21297, +39355:21299, +39356:21304, +39357:21312, +39358:21318, +39359:21317, +39360:19991, +39361:21321, +39362:21325, +39363:20950, +39364:21342, +39365:21353, +39366:21358, +39367:22808, +39368:21371, +39369:21367, +39370:21378, +39371:21398, +39372:21408, +39373:21414, +39374:21413, +39375:21422, +39376:21424, +39377:21430, +39378:21443, +39379:31762, +39380:38617, +39381:21471, +39382:26364, +39383:29166, +39384:21486, +39385:21480, +39386:21485, +39387:21498, +39388:21505, +39389:21565, +39390:21568, +39391:21548, +39392:21549, +39393:21564, +39394:21550, +39395:21558, +39396:21545, +39397:21533, +39398:21582, +39399:21647, +39400:21621, +39401:21646, +39402:21599, +39403:21617, +39404:21623, +39405:21616, +39406:21650, +39407:21627, +39408:21632, +39409:21622, +39410:21636, +39411:21648, +39412:21638, +39413:21703, +39414:21666, +39415:21688, +39416:21669, +39417:21676, +39418:21700, +39419:21704, +39420:21672, +39488:21675, +39489:21698, +39490:21668, +39491:21694, +39492:21692, +39493:21720, +39494:21733, +39495:21734, +39496:21775, +39497:21780, +39498:21757, +39499:21742, +39500:21741, +39501:21754, +39502:21730, +39503:21817, +39504:21824, +39505:21859, +39506:21836, +39507:21806, +39508:21852, +39509:21829, +39510:21846, +39511:21847, +39512:21816, +39513:21811, +39514:21853, +39515:21913, +39516:21888, +39517:21679, +39518:21898, +39519:21919, +39520:21883, +39521:21886, +39522:21912, +39523:21918, +39524:21934, +39525:21884, +39526:21891, +39527:21929, +39528:21895, +39529:21928, +39530:21978, +39531:21957, +39532:21983, +39533:21956, +39534:21980, +39535:21988, +39536:21972, +39537:22036, +39538:22007, +39539:22038, +39540:22014, +39541:22013, +39542:22043, +39543:22009, +39544:22094, +39545:22096, +39546:29151, +39547:22068, +39548:22070, +39549:22066, +39550:22072, +39552:22123, +39553:22116, +39554:22063, +39555:22124, +39556:22122, +39557:22150, +39558:22144, +39559:22154, +39560:22176, +39561:22164, +39562:22159, +39563:22181, +39564:22190, +39565:22198, +39566:22196, +39567:22210, +39568:22204, +39569:22209, +39570:22211, +39571:22208, +39572:22216, +39573:22222, +39574:22225, +39575:22227, +39576:22231, +39577:22254, +39578:22265, +39579:22272, +39580:22271, +39581:22276, +39582:22281, +39583:22280, +39584:22283, +39585:22285, +39586:22291, +39587:22296, +39588:22294, +39589:21959, +39590:22300, +39591:22310, +39592:22327, +39593:22328, +39594:22350, +39595:22331, +39596:22336, +39597:22351, +39598:22377, +39599:22464, +39600:22408, +39601:22369, +39602:22399, +39603:22409, +39604:22419, +39605:22432, +39606:22451, +39607:22436, +39608:22442, +39609:22448, +39610:22467, +39611:22470, +39612:22484, +39613:22482, +39614:22483, +39615:22538, +39616:22486, +39617:22499, +39618:22539, +39619:22553, +39620:22557, +39621:22642, +39622:22561, +39623:22626, +39624:22603, +39625:22640, +39626:27584, +39627:22610, +39628:22589, +39629:22649, +39630:22661, +39631:22713, +39632:22687, +39633:22699, +39634:22714, +39635:22750, +39636:22715, +39637:22712, +39638:22702, +39639:22725, +39640:22739, +39641:22737, +39642:22743, +39643:22745, +39644:22744, +39645:22757, +39646:22748, +39647:22756, +39648:22751, +39649:22767, +39650:22778, +39651:22777, +39652:22779, +39653:22780, +39654:22781, +39655:22786, +39656:22794, +39657:22800, +39658:22811, +39659:26790, +39660:22821, +39661:22828, +39662:22829, +39663:22834, +39664:22840, +39665:22846, +39666:31442, +39667:22869, +39668:22864, +39669:22862, +39670:22874, +39671:22872, +39672:22882, +39673:22880, +39674:22887, +39675:22892, +39676:22889, +39744:22904, +39745:22913, +39746:22941, +39747:20318, +39748:20395, +39749:22947, +39750:22962, +39751:22982, +39752:23016, +39753:23004, +39754:22925, +39755:23001, +39756:23002, +39757:23077, +39758:23071, +39759:23057, +39760:23068, +39761:23049, +39762:23066, +39763:23104, +39764:23148, +39765:23113, +39766:23093, +39767:23094, +39768:23138, +39769:23146, +39770:23194, +39771:23228, +39772:23230, +39773:23243, +39774:23234, +39775:23229, +39776:23267, +39777:23255, +39778:23270, +39779:23273, +39780:23254, +39781:23290, +39782:23291, +39783:23308, +39784:23307, +39785:23318, +39786:23346, +39787:23248, +39788:23338, +39789:23350, +39790:23358, +39791:23363, +39792:23365, +39793:23360, +39794:23377, +39795:23381, +39796:23386, +39797:23387, +39798:23397, +39799:23401, +39800:23408, +39801:23411, +39802:23413, +39803:23416, +39804:25992, +39805:23418, +39806:23424, +39808:23427, +39809:23462, +39810:23480, +39811:23491, +39812:23495, +39813:23497, +39814:23508, +39815:23504, +39816:23524, +39817:23526, +39818:23522, +39819:23518, +39820:23525, +39821:23531, +39822:23536, +39823:23542, +39824:23539, +39825:23557, +39826:23559, +39827:23560, +39828:23565, +39829:23571, +39830:23584, +39831:23586, +39832:23592, +39833:23608, +39834:23609, +39835:23617, +39836:23622, +39837:23630, +39838:23635, +39839:23632, +39840:23631, +39841:23409, +39842:23660, +39843:23662, +39844:20066, +39845:23670, +39846:23673, +39847:23692, +39848:23697, +39849:23700, +39850:22939, +39851:23723, +39852:23739, +39853:23734, +39854:23740, +39855:23735, +39856:23749, +39857:23742, +39858:23751, +39859:23769, +39860:23785, +39861:23805, +39862:23802, +39863:23789, +39864:23948, +39865:23786, +39866:23819, +39867:23829, +39868:23831, +39869:23900, +39870:23839, +39871:23835, +39872:23825, +39873:23828, +39874:23842, +39875:23834, +39876:23833, +39877:23832, +39878:23884, +39879:23890, +39880:23886, +39881:23883, +39882:23916, +39883:23923, +39884:23926, +39885:23943, +39886:23940, +39887:23938, +39888:23970, +39889:23965, +39890:23980, +39891:23982, +39892:23997, +39893:23952, +39894:23991, +39895:23996, +39896:24009, +39897:24013, +39898:24019, +39899:24018, +39900:24022, +39901:24027, +39902:24043, +39903:24050, +39904:24053, +39905:24075, +39906:24090, +39907:24089, +39908:24081, +39909:24091, +39910:24118, +39911:24119, +39912:24132, +39913:24131, +39914:24128, +39915:24142, +39916:24151, +39917:24148, +39918:24159, +39919:24162, +39920:24164, +39921:24135, +39922:24181, +39923:24182, +39924:24186, +39925:40636, +39926:24191, +39927:24224, +39928:24257, +39929:24258, +39930:24264, +39931:24272, +39932:24271, +40000:24278, +40001:24291, +40002:24285, +40003:24282, +40004:24283, +40005:24290, +40006:24289, +40007:24296, +40008:24297, +40009:24300, +40010:24305, +40011:24307, +40012:24304, +40013:24308, +40014:24312, +40015:24318, +40016:24323, +40017:24329, +40018:24413, +40019:24412, +40020:24331, +40021:24337, +40022:24342, +40023:24361, +40024:24365, +40025:24376, +40026:24385, +40027:24392, +40028:24396, +40029:24398, +40030:24367, +40031:24401, +40032:24406, +40033:24407, +40034:24409, +40035:24417, +40036:24429, +40037:24435, +40038:24439, +40039:24451, +40040:24450, +40041:24447, +40042:24458, +40043:24456, +40044:24465, +40045:24455, +40046:24478, +40047:24473, +40048:24472, +40049:24480, +40050:24488, +40051:24493, +40052:24508, +40053:24534, +40054:24571, +40055:24548, +40056:24568, +40057:24561, +40058:24541, +40059:24755, +40060:24575, +40061:24609, +40062:24672, +40064:24601, +40065:24592, +40066:24617, +40067:24590, +40068:24625, +40069:24603, +40070:24597, +40071:24619, +40072:24614, +40073:24591, +40074:24634, +40075:24666, +40076:24641, +40077:24682, +40078:24695, +40079:24671, +40080:24650, +40081:24646, +40082:24653, +40083:24675, +40084:24643, +40085:24676, +40086:24642, +40087:24684, +40088:24683, +40089:24665, +40090:24705, +40091:24717, +40092:24807, +40093:24707, +40094:24730, +40095:24708, +40096:24731, +40097:24726, +40098:24727, +40099:24722, +40100:24743, +40101:24715, +40102:24801, +40103:24760, +40104:24800, +40105:24787, +40106:24756, +40107:24560, +40108:24765, +40109:24774, +40110:24757, +40111:24792, +40112:24909, +40113:24853, +40114:24838, +40115:24822, +40116:24823, +40117:24832, +40118:24820, +40119:24826, +40120:24835, +40121:24865, +40122:24827, +40123:24817, +40124:24845, +40125:24846, +40126:24903, +40127:24894, +40128:24872, +40129:24871, +40130:24906, +40131:24895, +40132:24892, +40133:24876, +40134:24884, +40135:24893, +40136:24898, +40137:24900, +40138:24947, +40139:24951, +40140:24920, +40141:24921, +40142:24922, +40143:24939, +40144:24948, +40145:24943, +40146:24933, +40147:24945, +40148:24927, +40149:24925, +40150:24915, +40151:24949, +40152:24985, +40153:24982, +40154:24967, +40155:25004, +40156:24980, +40157:24986, +40158:24970, +40159:24977, +40160:25003, +40161:25006, +40162:25036, +40163:25034, +40164:25033, +40165:25079, +40166:25032, +40167:25027, +40168:25030, +40169:25018, +40170:25035, +40171:32633, +40172:25037, +40173:25062, +40174:25059, +40175:25078, +40176:25082, +40177:25076, +40178:25087, +40179:25085, +40180:25084, +40181:25086, +40182:25088, +40183:25096, +40184:25097, +40185:25101, +40186:25100, +40187:25108, +40188:25115, +40256:25118, +40257:25121, +40258:25130, +40259:25134, +40260:25136, +40261:25138, +40262:25139, +40263:25153, +40264:25166, +40265:25182, +40266:25187, +40267:25179, +40268:25184, +40269:25192, +40270:25212, +40271:25218, +40272:25225, +40273:25214, +40274:25234, +40275:25235, +40276:25238, +40277:25300, +40278:25219, +40279:25236, +40280:25303, +40281:25297, +40282:25275, +40283:25295, +40284:25343, +40285:25286, +40286:25812, +40287:25288, +40288:25308, +40289:25292, +40290:25290, +40291:25282, +40292:25287, +40293:25243, +40294:25289, +40295:25356, +40296:25326, +40297:25329, +40298:25383, +40299:25346, +40300:25352, +40301:25327, +40302:25333, +40303:25424, +40304:25406, +40305:25421, +40306:25628, +40307:25423, +40308:25494, +40309:25486, +40310:25472, +40311:25515, +40312:25462, +40313:25507, +40314:25487, +40315:25481, +40316:25503, +40317:25525, +40318:25451, +40320:25449, +40321:25534, +40322:25577, +40323:25536, +40324:25542, +40325:25571, +40326:25545, +40327:25554, +40328:25590, +40329:25540, +40330:25622, +40331:25652, +40332:25606, +40333:25619, +40334:25638, +40335:25654, +40336:25885, +40337:25623, +40338:25640, +40339:25615, +40340:25703, +40341:25711, +40342:25718, +40343:25678, +40344:25898, +40345:25749, +40346:25747, +40347:25765, +40348:25769, +40349:25736, +40350:25788, +40351:25818, +40352:25810, +40353:25797, +40354:25799, +40355:25787, +40356:25816, +40357:25794, +40358:25841, +40359:25831, +40360:33289, +40361:25824, +40362:25825, +40363:25260, +40364:25827, +40365:25839, +40366:25900, +40367:25846, +40368:25844, +40369:25842, +40370:25850, +40371:25856, +40372:25853, +40373:25880, +40374:25884, +40375:25861, +40376:25892, +40377:25891, +40378:25899, +40379:25908, +40380:25909, +40381:25911, +40382:25910, +40383:25912, +40384:30027, +40385:25928, +40386:25942, +40387:25941, +40388:25933, +40389:25944, +40390:25950, +40391:25949, +40392:25970, +40393:25976, +40394:25986, +40395:25987, +40396:35722, +40397:26011, +40398:26015, +40399:26027, +40400:26039, +40401:26051, +40402:26054, +40403:26049, +40404:26052, +40405:26060, +40406:26066, +40407:26075, +40408:26073, +40409:26080, +40410:26081, +40411:26097, +40412:26482, +40413:26122, +40414:26115, +40415:26107, +40416:26483, +40417:26165, +40418:26166, +40419:26164, +40420:26140, +40421:26191, +40422:26180, +40423:26185, +40424:26177, +40425:26206, +40426:26205, +40427:26212, +40428:26215, +40429:26216, +40430:26207, +40431:26210, +40432:26224, +40433:26243, +40434:26248, +40435:26254, +40436:26249, +40437:26244, +40438:26264, +40439:26269, +40440:26305, +40441:26297, +40442:26313, +40443:26302, +40444:26300, +40512:26308, +40513:26296, +40514:26326, +40515:26330, +40516:26336, +40517:26175, +40518:26342, +40519:26345, +40520:26352, +40521:26357, +40522:26359, +40523:26383, +40524:26390, +40525:26398, +40526:26406, +40527:26407, +40528:38712, +40529:26414, +40530:26431, +40531:26422, +40532:26433, +40533:26424, +40534:26423, +40535:26438, +40536:26462, +40537:26464, +40538:26457, +40539:26467, +40540:26468, +40541:26505, +40542:26480, +40543:26537, +40544:26492, +40545:26474, +40546:26508, +40547:26507, +40548:26534, +40549:26529, +40550:26501, +40551:26551, +40552:26607, +40553:26548, +40554:26604, +40555:26547, +40556:26601, +40557:26552, +40558:26596, +40559:26590, +40560:26589, +40561:26594, +40562:26606, +40563:26553, +40564:26574, +40565:26566, +40566:26599, +40567:27292, +40568:26654, +40569:26694, +40570:26665, +40571:26688, +40572:26701, +40573:26674, +40574:26702, +40576:26803, +40577:26667, +40578:26713, +40579:26723, +40580:26743, +40581:26751, +40582:26783, +40583:26767, +40584:26797, +40585:26772, +40586:26781, +40587:26779, +40588:26755, +40589:27310, +40590:26809, +40591:26740, +40592:26805, +40593:26784, +40594:26810, +40595:26895, +40596:26765, +40597:26750, +40598:26881, +40599:26826, +40600:26888, +40601:26840, +40602:26914, +40603:26918, +40604:26849, +40605:26892, +40606:26829, +40607:26836, +40608:26855, +40609:26837, +40610:26934, +40611:26898, +40612:26884, +40613:26839, +40614:26851, +40615:26917, +40616:26873, +40617:26848, +40618:26863, +40619:26920, +40620:26922, +40621:26906, +40622:26915, +40623:26913, +40624:26822, +40625:27001, +40626:26999, +40627:26972, +40628:27000, +40629:26987, +40630:26964, +40631:27006, +40632:26990, +40633:26937, +40634:26996, +40635:26941, +40636:26969, +40637:26928, +40638:26977, +40639:26974, +40640:26973, +40641:27009, +40642:26986, +40643:27058, +40644:27054, +40645:27088, +40646:27071, +40647:27073, +40648:27091, +40649:27070, +40650:27086, +40651:23528, +40652:27082, +40653:27101, +40654:27067, +40655:27075, +40656:27047, +40657:27182, +40658:27025, +40659:27040, +40660:27036, +40661:27029, +40662:27060, +40663:27102, +40664:27112, +40665:27138, +40666:27163, +40667:27135, +40668:27402, +40669:27129, +40670:27122, +40671:27111, +40672:27141, +40673:27057, +40674:27166, +40675:27117, +40676:27156, +40677:27115, +40678:27146, +40679:27154, +40680:27329, +40681:27171, +40682:27155, +40683:27204, +40684:27148, +40685:27250, +40686:27190, +40687:27256, +40688:27207, +40689:27234, +40690:27225, +40691:27238, +40692:27208, +40693:27192, +40694:27170, +40695:27280, +40696:27277, +40697:27296, +40698:27268, +40699:27298, +40700:27299, +40768:27287, +40769:34327, +40770:27323, +40771:27331, +40772:27330, +40773:27320, +40774:27315, +40775:27308, +40776:27358, +40777:27345, +40778:27359, +40779:27306, +40780:27354, +40781:27370, +40782:27387, +40783:27397, +40784:34326, +40785:27386, +40786:27410, +40787:27414, +40788:39729, +40789:27423, +40790:27448, +40791:27447, +40792:30428, +40793:27449, +40794:39150, +40795:27463, +40796:27459, +40797:27465, +40798:27472, +40799:27481, +40800:27476, +40801:27483, +40802:27487, +40803:27489, +40804:27512, +40805:27513, +40806:27519, +40807:27520, +40808:27524, +40809:27523, +40810:27533, +40811:27544, +40812:27541, +40813:27550, +40814:27556, +40815:27562, +40816:27563, +40817:27567, +40818:27570, +40819:27569, +40820:27571, +40821:27575, +40822:27580, +40823:27590, +40824:27595, +40825:27603, +40826:27615, +40827:27628, +40828:27627, +40829:27635, +40830:27631, +40832:40638, +40833:27656, +40834:27667, +40835:27668, +40836:27675, +40837:27684, +40838:27683, +40839:27742, +40840:27733, +40841:27746, +40842:27754, +40843:27778, +40844:27789, +40845:27802, +40846:27777, +40847:27803, +40848:27774, +40849:27752, +40850:27763, +40851:27794, +40852:27792, +40853:27844, +40854:27889, +40855:27859, +40856:27837, +40857:27863, +40858:27845, +40859:27869, +40860:27822, +40861:27825, +40862:27838, +40863:27834, +40864:27867, +40865:27887, +40866:27865, +40867:27882, +40868:27935, +40869:34893, +40870:27958, +40871:27947, +40872:27965, +40873:27960, +40874:27929, +40875:27957, +40876:27955, +40877:27922, +40878:27916, +40879:28003, +40880:28051, +40881:28004, +40882:27994, +40883:28025, +40884:27993, +40885:28046, +40886:28053, +40887:28644, +40888:28037, +40889:28153, +40890:28181, +40891:28170, +40892:28085, +40893:28103, +40894:28134, +40895:28088, +40896:28102, +40897:28140, +40898:28126, +40899:28108, +40900:28136, +40901:28114, +40902:28101, +40903:28154, +40904:28121, +40905:28132, +40906:28117, +40907:28138, +40908:28142, +40909:28205, +40910:28270, +40911:28206, +40912:28185, +40913:28274, +40914:28255, +40915:28222, +40916:28195, +40917:28267, +40918:28203, +40919:28278, +40920:28237, +40921:28191, +40922:28227, +40923:28218, +40924:28238, +40925:28196, +40926:28415, +40927:28189, +40928:28216, +40929:28290, +40930:28330, +40931:28312, +40932:28361, +40933:28343, +40934:28371, +40935:28349, +40936:28335, +40937:28356, +40938:28338, +40939:28372, +40940:28373, +40941:28303, +40942:28325, +40943:28354, +40944:28319, +40945:28481, +40946:28433, +40947:28748, +40948:28396, +40949:28408, +40950:28414, +40951:28479, +40952:28402, +40953:28465, +40954:28399, +40955:28466, +40956:28364, +57408:28478, +57409:28435, +57410:28407, +57411:28550, +57412:28538, +57413:28536, +57414:28545, +57415:28544, +57416:28527, +57417:28507, +57418:28659, +57419:28525, +57420:28546, +57421:28540, +57422:28504, +57423:28558, +57424:28561, +57425:28610, +57426:28518, +57427:28595, +57428:28579, +57429:28577, +57430:28580, +57431:28601, +57432:28614, +57433:28586, +57434:28639, +57435:28629, +57436:28652, +57437:28628, +57438:28632, +57439:28657, +57440:28654, +57441:28635, +57442:28681, +57443:28683, +57444:28666, +57445:28689, +57446:28673, +57447:28687, +57448:28670, +57449:28699, +57450:28698, +57451:28532, +57452:28701, +57453:28696, +57454:28703, +57455:28720, +57456:28734, +57457:28722, +57458:28753, +57459:28771, +57460:28825, +57461:28818, +57462:28847, +57463:28913, +57464:28844, +57465:28856, +57466:28851, +57467:28846, +57468:28895, +57469:28875, +57470:28893, +57472:28889, +57473:28937, +57474:28925, +57475:28956, +57476:28953, +57477:29029, +57478:29013, +57479:29064, +57480:29030, +57481:29026, +57482:29004, +57483:29014, +57484:29036, +57485:29071, +57486:29179, +57487:29060, +57488:29077, +57489:29096, +57490:29100, +57491:29143, +57492:29113, +57493:29118, +57494:29138, +57495:29129, +57496:29140, +57497:29134, +57498:29152, +57499:29164, +57500:29159, +57501:29173, +57502:29180, +57503:29177, +57504:29183, +57505:29197, +57506:29200, +57507:29211, +57508:29224, +57509:29229, +57510:29228, +57511:29232, +57512:29234, +57513:29243, +57514:29244, +57515:29247, +57516:29248, +57517:29254, +57518:29259, +57519:29272, +57520:29300, +57521:29310, +57522:29314, +57523:29313, +57524:29319, +57525:29330, +57526:29334, +57527:29346, +57528:29351, +57529:29369, +57530:29362, +57531:29379, +57532:29382, +57533:29380, +57534:29390, +57535:29394, +57536:29410, +57537:29408, +57538:29409, +57539:29433, +57540:29431, +57541:20495, +57542:29463, +57543:29450, +57544:29468, +57545:29462, +57546:29469, +57547:29492, +57548:29487, +57549:29481, +57550:29477, +57551:29502, +57552:29518, +57553:29519, +57554:40664, +57555:29527, +57556:29546, +57557:29544, +57558:29552, +57559:29560, +57560:29557, +57561:29563, +57562:29562, +57563:29640, +57564:29619, +57565:29646, +57566:29627, +57567:29632, +57568:29669, +57569:29678, +57570:29662, +57571:29858, +57572:29701, +57573:29807, +57574:29733, +57575:29688, +57576:29746, +57577:29754, +57578:29781, +57579:29759, +57580:29791, +57581:29785, +57582:29761, +57583:29788, +57584:29801, +57585:29808, +57586:29795, +57587:29802, +57588:29814, +57589:29822, +57590:29835, +57591:29854, +57592:29863, +57593:29898, +57594:29903, +57595:29908, +57596:29681, +57664:29920, +57665:29923, +57666:29927, +57667:29929, +57668:29934, +57669:29938, +57670:29936, +57671:29937, +57672:29944, +57673:29943, +57674:29956, +57675:29955, +57676:29957, +57677:29964, +57678:29966, +57679:29965, +57680:29973, +57681:29971, +57682:29982, +57683:29990, +57684:29996, +57685:30012, +57686:30020, +57687:30029, +57688:30026, +57689:30025, +57690:30043, +57691:30022, +57692:30042, +57693:30057, +57694:30052, +57695:30055, +57696:30059, +57697:30061, +57698:30072, +57699:30070, +57700:30086, +57701:30087, +57702:30068, +57703:30090, +57704:30089, +57705:30082, +57706:30100, +57707:30106, +57708:30109, +57709:30117, +57710:30115, +57711:30146, +57712:30131, +57713:30147, +57714:30133, +57715:30141, +57716:30136, +57717:30140, +57718:30129, +57719:30157, +57720:30154, +57721:30162, +57722:30169, +57723:30179, +57724:30174, +57725:30206, +57726:30207, +57728:30204, +57729:30209, +57730:30192, +57731:30202, +57732:30194, +57733:30195, +57734:30219, +57735:30221, +57736:30217, +57737:30239, +57738:30247, +57739:30240, +57740:30241, +57741:30242, +57742:30244, +57743:30260, +57744:30256, +57745:30267, +57746:30279, +57747:30280, +57748:30278, +57749:30300, +57750:30296, +57751:30305, +57752:30306, +57753:30312, +57754:30313, +57755:30314, +57756:30311, +57757:30316, +57758:30320, +57759:30322, +57760:30326, +57761:30328, +57762:30332, +57763:30336, +57764:30339, +57765:30344, +57766:30347, +57767:30350, +57768:30358, +57769:30355, +57770:30361, +57771:30362, +57772:30384, +57773:30388, +57774:30392, +57775:30393, +57776:30394, +57777:30402, +57778:30413, +57779:30422, +57780:30418, +57781:30430, +57782:30433, +57783:30437, +57784:30439, +57785:30442, +57786:34351, +57787:30459, +57788:30472, +57789:30471, +57790:30468, +57791:30505, +57792:30500, +57793:30494, +57794:30501, +57795:30502, +57796:30491, +57797:30519, +57798:30520, +57799:30535, +57800:30554, +57801:30568, +57802:30571, +57803:30555, +57804:30565, +57805:30591, +57806:30590, +57807:30585, +57808:30606, +57809:30603, +57810:30609, +57811:30624, +57812:30622, +57813:30640, +57814:30646, +57815:30649, +57816:30655, +57817:30652, +57818:30653, +57819:30651, +57820:30663, +57821:30669, +57822:30679, +57823:30682, +57824:30684, +57825:30691, +57826:30702, +57827:30716, +57828:30732, +57829:30738, +57830:31014, +57831:30752, +57832:31018, +57833:30789, +57834:30862, +57835:30836, +57836:30854, +57837:30844, +57838:30874, +57839:30860, +57840:30883, +57841:30901, +57842:30890, +57843:30895, +57844:30929, +57845:30918, +57846:30923, +57847:30932, +57848:30910, +57849:30908, +57850:30917, +57851:30922, +57852:30956, +57920:30951, +57921:30938, +57922:30973, +57923:30964, +57924:30983, +57925:30994, +57926:30993, +57927:31001, +57928:31020, +57929:31019, +57930:31040, +57931:31072, +57932:31063, +57933:31071, +57934:31066, +57935:31061, +57936:31059, +57937:31098, +57938:31103, +57939:31114, +57940:31133, +57941:31143, +57942:40779, +57943:31146, +57944:31150, +57945:31155, +57946:31161, +57947:31162, +57948:31177, +57949:31189, +57950:31207, +57951:31212, +57952:31201, +57953:31203, +57954:31240, +57955:31245, +57956:31256, +57957:31257, +57958:31264, +57959:31263, +57960:31104, +57961:31281, +57962:31291, +57963:31294, +57964:31287, +57965:31299, +57966:31319, +57967:31305, +57968:31329, +57969:31330, +57970:31337, +57971:40861, +57972:31344, +57973:31353, +57974:31357, +57975:31368, +57976:31383, +57977:31381, +57978:31384, +57979:31382, +57980:31401, +57981:31432, +57982:31408, +57984:31414, +57985:31429, +57986:31428, +57987:31423, +57988:36995, +57989:31431, +57990:31434, +57991:31437, +57992:31439, +57993:31445, +57994:31443, +57995:31449, +57996:31450, +57997:31453, +57998:31457, +57999:31458, +58000:31462, +58001:31469, +58002:31472, +58003:31490, +58004:31503, +58005:31498, +58006:31494, +58007:31539, +58008:31512, +58009:31513, +58010:31518, +58011:31541, +58012:31528, +58013:31542, +58014:31568, +58015:31610, +58016:31492, +58017:31565, +58018:31499, +58019:31564, +58020:31557, +58021:31605, +58022:31589, +58023:31604, +58024:31591, +58025:31600, +58026:31601, +58027:31596, +58028:31598, +58029:31645, +58030:31640, +58031:31647, +58032:31629, +58033:31644, +58034:31642, +58035:31627, +58036:31634, +58037:31631, +58038:31581, +58039:31641, +58040:31691, +58041:31681, +58042:31692, +58043:31695, +58044:31668, +58045:31686, +58046:31709, +58047:31721, +58048:31761, +58049:31764, +58050:31718, +58051:31717, +58052:31840, +58053:31744, +58054:31751, +58055:31763, +58056:31731, +58057:31735, +58058:31767, +58059:31757, +58060:31734, +58061:31779, +58062:31783, +58063:31786, +58064:31775, +58065:31799, +58066:31787, +58067:31805, +58068:31820, +58069:31811, +58070:31828, +58071:31823, +58072:31808, +58073:31824, +58074:31832, +58075:31839, +58076:31844, +58077:31830, +58078:31845, +58079:31852, +58080:31861, +58081:31875, +58082:31888, +58083:31908, +58084:31917, +58085:31906, +58086:31915, +58087:31905, +58088:31912, +58089:31923, +58090:31922, +58091:31921, +58092:31918, +58093:31929, +58094:31933, +58095:31936, +58096:31941, +58097:31938, +58098:31960, +58099:31954, +58100:31964, +58101:31970, +58102:39739, +58103:31983, +58104:31986, +58105:31988, +58106:31990, +58107:31994, +58108:32006, +58176:32002, +58177:32028, +58178:32021, +58179:32010, +58180:32069, +58181:32075, +58182:32046, +58183:32050, +58184:32063, +58185:32053, +58186:32070, +58187:32115, +58188:32086, +58189:32078, +58190:32114, +58191:32104, +58192:32110, +58193:32079, +58194:32099, +58195:32147, +58196:32137, +58197:32091, +58198:32143, +58199:32125, +58200:32155, +58201:32186, +58202:32174, +58203:32163, +58204:32181, +58205:32199, +58206:32189, +58207:32171, +58208:32317, +58209:32162, +58210:32175, +58211:32220, +58212:32184, +58213:32159, +58214:32176, +58215:32216, +58216:32221, +58217:32228, +58218:32222, +58219:32251, +58220:32242, +58221:32225, +58222:32261, +58223:32266, +58224:32291, +58225:32289, +58226:32274, +58227:32305, +58228:32287, +58229:32265, +58230:32267, +58231:32290, +58232:32326, +58233:32358, +58234:32315, +58235:32309, +58236:32313, +58237:32323, +58238:32311, +58240:32306, +58241:32314, +58242:32359, +58243:32349, +58244:32342, +58245:32350, +58246:32345, +58247:32346, +58248:32377, +58249:32362, +58250:32361, +58251:32380, +58252:32379, +58253:32387, +58254:32213, +58255:32381, +58256:36782, +58257:32383, +58258:32392, +58259:32393, +58260:32396, +58261:32402, +58262:32400, +58263:32403, +58264:32404, +58265:32406, +58266:32398, +58267:32411, +58268:32412, +58269:32568, +58270:32570, +58271:32581, +58272:32588, +58273:32589, +58274:32590, +58275:32592, +58276:32593, +58277:32597, +58278:32596, +58279:32600, +58280:32607, +58281:32608, +58282:32616, +58283:32617, +58284:32615, +58285:32632, +58286:32642, +58287:32646, +58288:32643, +58289:32648, +58290:32647, +58291:32652, +58292:32660, +58293:32670, +58294:32669, +58295:32666, +58296:32675, +58297:32687, +58298:32690, +58299:32697, +58300:32686, +58301:32694, +58302:32696, +58303:35697, +58304:32709, +58305:32710, +58306:32714, +58307:32725, +58308:32724, +58309:32737, +58310:32742, +58311:32745, +58312:32755, +58313:32761, +58314:39132, +58315:32774, +58316:32772, +58317:32779, +58318:32786, +58319:32792, +58320:32793, +58321:32796, +58322:32801, +58323:32808, +58324:32831, +58325:32827, +58326:32842, +58327:32838, +58328:32850, +58329:32856, +58330:32858, +58331:32863, +58332:32866, +58333:32872, +58334:32883, +58335:32882, +58336:32880, +58337:32886, +58338:32889, +58339:32893, +58340:32895, +58341:32900, +58342:32902, +58343:32901, +58344:32923, +58345:32915, +58346:32922, +58347:32941, +58348:20880, +58349:32940, +58350:32987, +58351:32997, +58352:32985, +58353:32989, +58354:32964, +58355:32986, +58356:32982, +58357:33033, +58358:33007, +58359:33009, +58360:33051, +58361:33065, +58362:33059, +58363:33071, +58364:33099, +58432:38539, +58433:33094, +58434:33086, +58435:33107, +58436:33105, +58437:33020, +58438:33137, +58439:33134, +58440:33125, +58441:33126, +58442:33140, +58443:33155, +58444:33160, +58445:33162, +58446:33152, +58447:33154, +58448:33184, +58449:33173, +58450:33188, +58451:33187, +58452:33119, +58453:33171, +58454:33193, +58455:33200, +58456:33205, +58457:33214, +58458:33208, +58459:33213, +58460:33216, +58461:33218, +58462:33210, +58463:33225, +58464:33229, +58465:33233, +58466:33241, +58467:33240, +58468:33224, +58469:33242, +58470:33247, +58471:33248, +58472:33255, +58473:33274, +58474:33275, +58475:33278, +58476:33281, +58477:33282, +58478:33285, +58479:33287, +58480:33290, +58481:33293, +58482:33296, +58483:33302, +58484:33321, +58485:33323, +58486:33336, +58487:33331, +58488:33344, +58489:33369, +58490:33368, +58491:33373, +58492:33370, +58493:33375, +58494:33380, +58496:33378, +58497:33384, +58498:33386, +58499:33387, +58500:33326, +58501:33393, +58502:33399, +58503:33400, +58504:33406, +58505:33421, +58506:33426, +58507:33451, +58508:33439, +58509:33467, +58510:33452, +58511:33505, +58512:33507, +58513:33503, +58514:33490, +58515:33524, +58516:33523, +58517:33530, +58518:33683, +58519:33539, +58520:33531, +58521:33529, +58522:33502, +58523:33542, +58524:33500, +58525:33545, +58526:33497, +58527:33589, +58528:33588, +58529:33558, +58530:33586, +58531:33585, +58532:33600, +58533:33593, +58534:33616, +58535:33605, +58536:33583, +58537:33579, +58538:33559, +58539:33560, +58540:33669, +58541:33690, +58542:33706, +58543:33695, +58544:33698, +58545:33686, +58546:33571, +58547:33678, +58548:33671, +58549:33674, +58550:33660, +58551:33717, +58552:33651, +58553:33653, +58554:33696, +58555:33673, +58556:33704, +58557:33780, +58558:33811, +58559:33771, +58560:33742, +58561:33789, +58562:33795, +58563:33752, +58564:33803, +58565:33729, +58566:33783, +58567:33799, +58568:33760, +58569:33778, +58570:33805, +58571:33826, +58572:33824, +58573:33725, +58574:33848, +58575:34054, +58576:33787, +58577:33901, +58578:33834, +58579:33852, +58580:34138, +58581:33924, +58582:33911, +58583:33899, +58584:33965, +58585:33902, +58586:33922, +58587:33897, +58588:33862, +58589:33836, +58590:33903, +58591:33913, +58592:33845, +58593:33994, +58594:33890, +58595:33977, +58596:33983, +58597:33951, +58598:34009, +58599:33997, +58600:33979, +58601:34010, +58602:34000, +58603:33985, +58604:33990, +58605:34006, +58606:33953, +58607:34081, +58608:34047, +58609:34036, +58610:34071, +58611:34072, +58612:34092, +58613:34079, +58614:34069, +58615:34068, +58616:34044, +58617:34112, +58618:34147, +58619:34136, +58620:34120, +58688:34113, +58689:34306, +58690:34123, +58691:34133, +58692:34176, +58693:34212, +58694:34184, +58695:34193, +58696:34186, +58697:34216, +58698:34157, +58699:34196, +58700:34203, +58701:34282, +58702:34183, +58703:34204, +58704:34167, +58705:34174, +58706:34192, +58707:34249, +58708:34234, +58709:34255, +58710:34233, +58711:34256, +58712:34261, +58713:34269, +58714:34277, +58715:34268, +58716:34297, +58717:34314, +58718:34323, +58719:34315, +58720:34302, +58721:34298, +58722:34310, +58723:34338, +58724:34330, +58725:34352, +58726:34367, +58727:34381, +58728:20053, +58729:34388, +58730:34399, +58731:34407, +58732:34417, +58733:34451, +58734:34467, +58735:34473, +58736:34474, +58737:34443, +58738:34444, +58739:34486, +58740:34479, +58741:34500, +58742:34502, +58743:34480, +58744:34505, +58745:34851, +58746:34475, +58747:34516, +58748:34526, +58749:34537, +58750:34540, +58752:34527, +58753:34523, +58754:34543, +58755:34578, +58756:34566, +58757:34568, +58758:34560, +58759:34563, +58760:34555, +58761:34577, +58762:34569, +58763:34573, +58764:34553, +58765:34570, +58766:34612, +58767:34623, +58768:34615, +58769:34619, +58770:34597, +58771:34601, +58772:34586, +58773:34656, +58774:34655, +58775:34680, +58776:34636, +58777:34638, +58778:34676, +58779:34647, +58780:34664, +58781:34670, +58782:34649, +58783:34643, +58784:34659, +58785:34666, +58786:34821, +58787:34722, +58788:34719, +58789:34690, +58790:34735, +58791:34763, +58792:34749, +58793:34752, +58794:34768, +58795:38614, +58796:34731, +58797:34756, +58798:34739, +58799:34759, +58800:34758, +58801:34747, +58802:34799, +58803:34802, +58804:34784, +58805:34831, +58806:34829, +58807:34814, +58808:34806, +58809:34807, +58810:34830, +58811:34770, +58812:34833, +58813:34838, +58814:34837, +58815:34850, +58816:34849, +58817:34865, +58818:34870, +58819:34873, +58820:34855, +58821:34875, +58822:34884, +58823:34882, +58824:34898, +58825:34905, +58826:34910, +58827:34914, +58828:34923, +58829:34945, +58830:34942, +58831:34974, +58832:34933, +58833:34941, +58834:34997, +58835:34930, +58836:34946, +58837:34967, +58838:34962, +58839:34990, +58840:34969, +58841:34978, +58842:34957, +58843:34980, +58844:34992, +58845:35007, +58846:34993, +58847:35011, +58848:35012, +58849:35028, +58850:35032, +58851:35033, +58852:35037, +58853:35065, +58854:35074, +58855:35068, +58856:35060, +58857:35048, +58858:35058, +58859:35076, +58860:35084, +58861:35082, +58862:35091, +58863:35139, +58864:35102, +58865:35109, +58866:35114, +58867:35115, +58868:35137, +58869:35140, +58870:35131, +58871:35126, +58872:35128, +58873:35148, +58874:35101, +58875:35168, +58876:35166, +58944:35174, +58945:35172, +58946:35181, +58947:35178, +58948:35183, +58949:35188, +58950:35191, +58951:35198, +58952:35203, +58953:35208, +58954:35210, +58955:35219, +58956:35224, +58957:35233, +58958:35241, +58959:35238, +58960:35244, +58961:35247, +58962:35250, +58963:35258, +58964:35261, +58965:35263, +58966:35264, +58967:35290, +58968:35292, +58969:35293, +58970:35303, +58971:35316, +58972:35320, +58973:35331, +58974:35350, +58975:35344, +58976:35340, +58977:35355, +58978:35357, +58979:35365, +58980:35382, +58981:35393, +58982:35419, +58983:35410, +58984:35398, +58985:35400, +58986:35452, +58987:35437, +58988:35436, +58989:35426, +58990:35461, +58991:35458, +58992:35460, +58993:35496, +58994:35489, +58995:35473, +58996:35493, +58997:35494, +58998:35482, +58999:35491, +59000:35524, +59001:35533, +59002:35522, +59003:35546, +59004:35563, +59005:35571, +59006:35559, +59008:35556, +59009:35569, +59010:35604, +59011:35552, +59012:35554, +59013:35575, +59014:35550, +59015:35547, +59016:35596, +59017:35591, +59018:35610, +59019:35553, +59020:35606, +59021:35600, +59022:35607, +59023:35616, +59024:35635, +59025:38827, +59026:35622, +59027:35627, +59028:35646, +59029:35624, +59030:35649, +59031:35660, +59032:35663, +59033:35662, +59034:35657, +59035:35670, +59036:35675, +59037:35674, +59038:35691, +59039:35679, +59040:35692, +59041:35695, +59042:35700, +59043:35709, +59044:35712, +59045:35724, +59046:35726, +59047:35730, +59048:35731, +59049:35734, +59050:35737, +59051:35738, +59052:35898, +59053:35905, +59054:35903, +59055:35912, +59056:35916, +59057:35918, +59058:35920, +59059:35925, +59060:35938, +59061:35948, +59062:35960, +59063:35962, +59064:35970, +59065:35977, +59066:35973, +59067:35978, +59068:35981, +59069:35982, +59070:35988, +59071:35964, +59072:35992, +59073:25117, +59074:36013, +59075:36010, +59076:36029, +59077:36018, +59078:36019, +59079:36014, +59080:36022, +59081:36040, +59082:36033, +59083:36068, +59084:36067, +59085:36058, +59086:36093, +59087:36090, +59088:36091, +59089:36100, +59090:36101, +59091:36106, +59092:36103, +59093:36111, +59094:36109, +59095:36112, +59096:40782, +59097:36115, +59098:36045, +59099:36116, +59100:36118, +59101:36199, +59102:36205, +59103:36209, +59104:36211, +59105:36225, +59106:36249, +59107:36290, +59108:36286, +59109:36282, +59110:36303, +59111:36314, +59112:36310, +59113:36300, +59114:36315, +59115:36299, +59116:36330, +59117:36331, +59118:36319, +59119:36323, +59120:36348, +59121:36360, +59122:36361, +59123:36351, +59124:36381, +59125:36382, +59126:36368, +59127:36383, +59128:36418, +59129:36405, +59130:36400, +59131:36404, +59132:36426, +59200:36423, +59201:36425, +59202:36428, +59203:36432, +59204:36424, +59205:36441, +59206:36452, +59207:36448, +59208:36394, +59209:36451, +59210:36437, +59211:36470, +59212:36466, +59213:36476, +59214:36481, +59215:36487, +59216:36485, +59217:36484, +59218:36491, +59219:36490, +59220:36499, +59221:36497, +59222:36500, +59223:36505, +59224:36522, +59225:36513, +59226:36524, +59227:36528, +59228:36550, +59229:36529, +59230:36542, +59231:36549, +59232:36552, +59233:36555, +59234:36571, +59235:36579, +59236:36604, +59237:36603, +59238:36587, +59239:36606, +59240:36618, +59241:36613, +59242:36629, +59243:36626, +59244:36633, +59245:36627, +59246:36636, +59247:36639, +59248:36635, +59249:36620, +59250:36646, +59251:36659, +59252:36667, +59253:36665, +59254:36677, +59255:36674, +59256:36670, +59257:36684, +59258:36681, +59259:36678, +59260:36686, +59261:36695, +59262:36700, +59264:36706, +59265:36707, +59266:36708, +59267:36764, +59268:36767, +59269:36771, +59270:36781, +59271:36783, +59272:36791, +59273:36826, +59274:36837, +59275:36834, +59276:36842, +59277:36847, +59278:36999, +59279:36852, +59280:36869, +59281:36857, +59282:36858, +59283:36881, +59284:36885, +59285:36897, +59286:36877, +59287:36894, +59288:36886, +59289:36875, +59290:36903, +59291:36918, +59292:36917, +59293:36921, +59294:36856, +59295:36943, +59296:36944, +59297:36945, +59298:36946, +59299:36878, +59300:36937, +59301:36926, +59302:36950, +59303:36952, +59304:36958, +59305:36968, +59306:36975, +59307:36982, +59308:38568, +59309:36978, +59310:36994, +59311:36989, +59312:36993, +59313:36992, +59314:37002, +59315:37001, +59316:37007, +59317:37032, +59318:37039, +59319:37041, +59320:37045, +59321:37090, +59322:37092, +59323:25160, +59324:37083, +59325:37122, +59326:37138, +59327:37145, +59328:37170, +59329:37168, +59330:37194, +59331:37206, +59332:37208, +59333:37219, +59334:37221, +59335:37225, +59336:37235, +59337:37234, +59338:37259, +59339:37257, +59340:37250, +59341:37282, +59342:37291, +59343:37295, +59344:37290, +59345:37301, +59346:37300, +59347:37306, +59348:37312, +59349:37313, +59350:37321, +59351:37323, +59352:37328, +59353:37334, +59354:37343, +59355:37345, +59356:37339, +59357:37372, +59358:37365, +59359:37366, +59360:37406, +59361:37375, +59362:37396, +59363:37420, +59364:37397, +59365:37393, +59366:37470, +59367:37463, +59368:37445, +59369:37449, +59370:37476, +59371:37448, +59372:37525, +59373:37439, +59374:37451, +59375:37456, +59376:37532, +59377:37526, +59378:37523, +59379:37531, +59380:37466, +59381:37583, +59382:37561, +59383:37559, +59384:37609, +59385:37647, +59386:37626, +59387:37700, +59388:37678, +59456:37657, +59457:37666, +59458:37658, +59459:37667, +59460:37690, +59461:37685, +59462:37691, +59463:37724, +59464:37728, +59465:37756, +59466:37742, +59467:37718, +59468:37808, +59469:37804, +59470:37805, +59471:37780, +59472:37817, +59473:37846, +59474:37847, +59475:37864, +59476:37861, +59477:37848, +59478:37827, +59479:37853, +59480:37840, +59481:37832, +59482:37860, +59483:37914, +59484:37908, +59485:37907, +59486:37891, +59487:37895, +59488:37904, +59489:37942, +59490:37931, +59491:37941, +59492:37921, +59493:37946, +59494:37953, +59495:37970, +59496:37956, +59497:37979, +59498:37984, +59499:37986, +59500:37982, +59501:37994, +59502:37417, +59503:38000, +59504:38005, +59505:38007, +59506:38013, +59507:37978, +59508:38012, +59509:38014, +59510:38017, +59511:38015, +59512:38274, +59513:38279, +59514:38282, +59515:38292, +59516:38294, +59517:38296, +59518:38297, +59520:38304, +59521:38312, +59522:38311, +59523:38317, +59524:38332, +59525:38331, +59526:38329, +59527:38334, +59528:38346, +59529:28662, +59530:38339, +59531:38349, +59532:38348, +59533:38357, +59534:38356, +59535:38358, +59536:38364, +59537:38369, +59538:38373, +59539:38370, +59540:38433, +59541:38440, +59542:38446, +59543:38447, +59544:38466, +59545:38476, +59546:38479, +59547:38475, +59548:38519, +59549:38492, +59550:38494, +59551:38493, +59552:38495, +59553:38502, +59554:38514, +59555:38508, +59556:38541, +59557:38552, +59558:38549, +59559:38551, +59560:38570, +59561:38567, +59562:38577, +59563:38578, +59564:38576, +59565:38580, +59566:38582, +59567:38584, +59568:38585, +59569:38606, +59570:38603, +59571:38601, +59572:38605, +59573:35149, +59574:38620, +59575:38669, +59576:38613, +59577:38649, +59578:38660, +59579:38662, +59580:38664, +59581:38675, +59582:38670, +59583:38673, +59584:38671, +59585:38678, +59586:38681, +59587:38692, +59588:38698, +59589:38704, +59590:38713, +59591:38717, +59592:38718, +59593:38724, +59594:38726, +59595:38728, +59596:38722, +59597:38729, +59598:38748, +59599:38752, +59600:38756, +59601:38758, +59602:38760, +59603:21202, +59604:38763, +59605:38769, +59606:38777, +59607:38789, +59608:38780, +59609:38785, +59610:38778, +59611:38790, +59612:38795, +59613:38799, +59614:38800, +59615:38812, +59616:38824, +59617:38822, +59618:38819, +59619:38835, +59620:38836, +59621:38851, +59622:38854, +59623:38856, +59624:38859, +59625:38876, +59626:38893, +59627:40783, +59628:38898, +59629:31455, +59630:38902, +59631:38901, +59632:38927, +59633:38924, +59634:38968, +59635:38948, +59636:38945, +59637:38967, +59638:38973, +59639:38982, +59640:38991, +59641:38987, +59642:39019, +59643:39023, +59644:39024, +59712:39025, +59713:39028, +59714:39027, +59715:39082, +59716:39087, +59717:39089, +59718:39094, +59719:39108, +59720:39107, +59721:39110, +59722:39145, +59723:39147, +59724:39171, +59725:39177, +59726:39186, +59727:39188, +59728:39192, +59729:39201, +59730:39197, +59731:39198, +59732:39204, +59733:39200, +59734:39212, +59735:39214, +59736:39229, +59737:39230, +59738:39234, +59739:39241, +59740:39237, +59741:39248, +59742:39243, +59743:39249, +59744:39250, +59745:39244, +59746:39253, +59747:39319, +59748:39320, +59749:39333, +59750:39341, +59751:39342, +59752:39356, +59753:39391, +59754:39387, +59755:39389, +59756:39384, +59757:39377, +59758:39405, +59759:39406, +59760:39409, +59761:39410, +59762:39419, +59763:39416, +59764:39425, +59765:39439, +59766:39429, +59767:39394, +59768:39449, +59769:39467, +59770:39479, +59771:39493, +59772:39490, +59773:39488, +59774:39491, +59776:39486, +59777:39509, +59778:39501, +59779:39515, +59780:39511, +59781:39519, +59782:39522, +59783:39525, +59784:39524, +59785:39529, +59786:39531, +59787:39530, +59788:39597, +59789:39600, +59790:39612, +59791:39616, +59792:39631, +59793:39633, +59794:39635, +59795:39636, +59796:39646, +59797:39647, +59798:39650, +59799:39651, +59800:39654, +59801:39663, +59802:39659, +59803:39662, +59804:39668, +59805:39665, +59806:39671, +59807:39675, +59808:39686, +59809:39704, +59810:39706, +59811:39711, +59812:39714, +59813:39715, +59814:39717, +59815:39719, +59816:39720, +59817:39721, +59818:39722, +59819:39726, +59820:39727, +59821:39730, +59822:39748, +59823:39747, +59824:39759, +59825:39757, +59826:39758, +59827:39761, +59828:39768, +59829:39796, +59830:39827, +59831:39811, +59832:39825, +59833:39830, +59834:39831, +59835:39839, +59836:39840, +59837:39848, +59838:39860, +59839:39872, +59840:39882, +59841:39865, +59842:39878, +59843:39887, +59844:39889, +59845:39890, +59846:39907, +59847:39906, +59848:39908, +59849:39892, +59850:39905, +59851:39994, +59852:39922, +59853:39921, +59854:39920, +59855:39957, +59856:39956, +59857:39945, +59858:39955, +59859:39948, +59860:39942, +59861:39944, +59862:39954, +59863:39946, +59864:39940, +59865:39982, +59866:39963, +59867:39973, +59868:39972, +59869:39969, +59870:39984, +59871:40007, +59872:39986, +59873:40006, +59874:39998, +59875:40026, +59876:40032, +59877:40039, +59878:40054, +59879:40056, +59880:40167, +59881:40172, +59882:40176, +59883:40201, +59884:40200, +59885:40171, +59886:40195, +59887:40198, +59888:40234, +59889:40230, +59890:40367, +59891:40227, +59892:40223, +59893:40260, +59894:40213, +59895:40210, +59896:40257, +59897:40255, +59898:40254, +59899:40262, +59900:40264, +59968:40285, +59969:40286, +59970:40292, +59971:40273, +59972:40272, +59973:40281, +59974:40306, +59975:40329, +59976:40327, +59977:40363, +59978:40303, +59979:40314, +59980:40346, +59981:40356, +59982:40361, +59983:40370, +59984:40388, +59985:40385, +59986:40379, +59987:40376, +59988:40378, +59989:40390, +59990:40399, +59991:40386, +59992:40409, +59993:40403, +59994:40440, +59995:40422, +59996:40429, +59997:40431, +59998:40445, +59999:40474, +60000:40475, +60001:40478, +60002:40565, +60003:40569, +60004:40573, +60005:40577, +60006:40584, +60007:40587, +60008:40588, +60009:40594, +60010:40597, +60011:40593, +60012:40605, +60013:40613, +60014:40617, +60015:40632, +60016:40618, +60017:40621, +60018:38753, +60019:40652, +60020:40654, +60021:40655, +60022:40656, +60023:40660, +60024:40668, +60025:40670, +60026:40669, +60027:40672, +60028:40677, +60029:40680, +60030:40687, +60032:40692, +60033:40694, +60034:40695, +60035:40697, +60036:40699, +60037:40700, +60038:40701, +60039:40711, +60040:40712, +60041:30391, +60042:40725, +60043:40737, +60044:40748, +60045:40766, +60046:40778, +60047:40786, +60048:40788, +60049:40803, +60050:40799, +60051:40800, +60052:40801, +60053:40806, +60054:40807, +60055:40812, +60056:40810, +60057:40823, +60058:40818, +60059:40822, +60060:40853, +60061:40860, +60062:40864, +60063:22575, +60064:27079, +60065:36953, +60066:29796, +60067:20956, +60068:29081, +60736:32394, +60737:35100, +60738:37704, +60739:37512, +60740:34012, +60741:20425, +60742:28859, +60743:26161, +60744:26824, +60745:37625, +60746:26363, +60747:24389, +60748:20008, +60749:20193, +60750:20220, +60751:20224, +60752:20227, +60753:20281, +60754:20310, +60755:20370, +60756:20362, +60757:20378, +60758:20372, +60759:20429, +60760:20544, +60761:20514, +60762:20479, +60763:20510, +60764:20550, +60765:20592, +60766:20546, +60767:20628, +60768:20724, +60769:20696, +60770:20810, +60771:20836, +60772:20893, +60773:20926, +60774:20972, +60775:21013, +60776:21148, +60777:21158, +60778:21184, +60779:21211, +60780:21248, +60781:21255, +60782:21284, +60783:21362, +60784:21395, +60785:21426, +60786:21469, +60787:64014, +60788:21660, +60789:21642, +60790:21673, +60791:21759, +60792:21894, +60793:22361, +60794:22373, +60795:22444, +60796:22472, +60797:22471, +60798:64015, +60800:64016, +60801:22686, +60802:22706, +60803:22795, +60804:22867, +60805:22875, +60806:22877, +60807:22883, +60808:22948, +60809:22970, +60810:23382, +60811:23488, +60812:29999, +60813:23512, +60814:23532, +60815:23582, +60816:23718, +60817:23738, +60818:23797, +60819:23847, +60820:23891, +60821:64017, +60822:23874, +60823:23917, +60824:23992, +60825:23993, +60826:24016, +60827:24353, +60828:24372, +60829:24423, +60830:24503, +60831:24542, +60832:24669, +60833:24709, +60834:24714, +60835:24798, +60836:24789, +60837:24864, +60838:24818, +60839:24849, +60840:24887, +60841:24880, +60842:24984, +60843:25107, +60844:25254, +60845:25589, +60846:25696, +60847:25757, +60848:25806, +60849:25934, +60850:26112, +60851:26133, +60852:26171, +60853:26121, +60854:26158, +60855:26142, +60856:26148, +60857:26213, +60858:26199, +60859:26201, +60860:64018, +60861:26227, +60862:26265, +60863:26272, +60864:26290, +60865:26303, +60866:26362, +60867:26382, +60868:63785, +60869:26470, +60870:26555, +60871:26706, +60872:26560, +60873:26625, +60874:26692, +60875:26831, +60876:64019, +60877:26984, +60878:64020, +60879:27032, +60880:27106, +60881:27184, +60882:27243, +60883:27206, +60884:27251, +60885:27262, +60886:27362, +60887:27364, +60888:27606, +60889:27711, +60890:27740, +60891:27782, +60892:27759, +60893:27866, +60894:27908, +60895:28039, +60896:28015, +60897:28054, +60898:28076, +60899:28111, +60900:28152, +60901:28146, +60902:28156, +60903:28217, +60904:28252, +60905:28199, +60906:28220, +60907:28351, +60908:28552, +60909:28597, +60910:28661, +60911:28677, +60912:28679, +60913:28712, +60914:28805, +60915:28843, +60916:28943, +60917:28932, +60918:29020, +60919:28998, +60920:28999, +60921:64021, +60922:29121, +60923:29182, +60924:29361, +60992:29374, +60993:29476, +60994:64022, +60995:29559, +60996:29629, +60997:29641, +60998:29654, +60999:29667, +61000:29650, +61001:29703, +61002:29685, +61003:29734, +61004:29738, +61005:29737, +61006:29742, +61007:29794, +61008:29833, +61009:29855, +61010:29953, +61011:30063, +61012:30338, +61013:30364, +61014:30366, +61015:30363, +61016:30374, +61017:64023, +61018:30534, +61019:21167, +61020:30753, +61021:30798, +61022:30820, +61023:30842, +61024:31024, +61025:64024, +61026:64025, +61027:64026, +61028:31124, +61029:64027, +61030:31131, +61031:31441, +61032:31463, +61033:64028, +61034:31467, +61035:31646, +61036:64029, +61037:32072, +61038:32092, +61039:32183, +61040:32160, +61041:32214, +61042:32338, +61043:32583, +61044:32673, +61045:64030, +61046:33537, +61047:33634, +61048:33663, +61049:33735, +61050:33782, +61051:33864, +61052:33972, +61053:34131, +61054:34137, +61056:34155, +61057:64031, +61058:34224, +61059:64032, +61060:64033, +61061:34823, +61062:35061, +61063:35346, +61064:35383, +61065:35449, +61066:35495, +61067:35518, +61068:35551, +61069:64034, +61070:35574, +61071:35667, +61072:35711, +61073:36080, +61074:36084, +61075:36114, +61076:36214, +61077:64035, +61078:36559, +61079:64036, +61080:64037, +61081:36967, +61082:37086, +61083:64038, +61084:37141, +61085:37159, +61086:37338, +61087:37335, +61088:37342, +61089:37357, +61090:37358, +61091:37348, +61092:37349, +61093:37382, +61094:37392, +61095:37386, +61096:37434, +61097:37440, +61098:37436, +61099:37454, +61100:37465, +61101:37457, +61102:37433, +61103:37479, +61104:37543, +61105:37495, +61106:37496, +61107:37607, +61108:37591, +61109:37593, +61110:37584, +61111:64039, +61112:37589, +61113:37600, +61114:37587, +61115:37669, +61116:37665, +61117:37627, +61118:64040, +61119:37662, +61120:37631, +61121:37661, +61122:37634, +61123:37744, +61124:37719, +61125:37796, +61126:37830, +61127:37854, +61128:37880, +61129:37937, +61130:37957, +61131:37960, +61132:38290, +61133:63964, +61134:64041, +61135:38557, +61136:38575, +61137:38707, +61138:38715, +61139:38723, +61140:38733, +61141:38735, +61142:38737, +61143:38741, +61144:38999, +61145:39013, +61146:64042, +61147:64043, +61148:39207, +61149:64044, +61150:39326, +61151:39502, +61152:39641, +61153:39644, +61154:39797, +61155:39794, +61156:39823, +61157:39857, +61158:39867, +61159:39936, +61160:40304, +61161:40299, +61162:64045, +61163:40473, +61164:40657, +61167:8560, +61168:8561, +61169:8562, +61170:8563, +61171:8564, +61172:8565, +61173:8566, +61174:8567, +61175:8568, +61176:8569, +61177:65506, +61178:65508, +61179:65287, +61180:65282, +61504:57344, +61505:57345, +61506:57346, +61507:57347, +61508:57348, +61509:57349, +61510:57350, +61511:57351, +61512:57352, +61513:57353, +61514:57354, +61515:57355, +61516:57356, +61517:57357, +61518:57358, +61519:57359, +61520:57360, +61521:57361, +61522:57362, +61523:57363, +61524:57364, +61525:57365, +61526:57366, +61527:57367, +61528:57368, +61529:57369, +61530:57370, +61531:57371, +61532:57372, +61533:57373, +61534:57374, +61535:57375, +61536:57376, +61537:57377, +61538:57378, +61539:57379, +61540:57380, +61541:57381, +61542:57382, +61543:57383, +61544:57384, +61545:57385, +61546:57386, +61547:57387, +61548:57388, +61549:57389, +61550:57390, +61551:57391, +61552:57392, +61553:57393, +61554:57394, +61555:57395, +61556:57396, +61557:57397, +61558:57398, +61559:57399, +61560:57400, +61561:57401, +61562:57402, +61563:57403, +61564:57404, +61565:57405, +61566:57406, +61568:57407, +61569:57408, +61570:57409, +61571:57410, +61572:57411, +61573:57412, +61574:57413, +61575:57414, +61576:57415, +61577:57416, +61578:57417, +61579:57418, +61580:57419, +61581:57420, +61582:57421, +61583:57422, +61584:57423, +61585:57424, +61586:57425, +61587:57426, +61588:57427, +61589:57428, +61590:57429, +61591:57430, +61592:57431, +61593:57432, +61594:57433, +61595:57434, +61596:57435, +61597:57436, +61598:57437, +61599:57438, +61600:57439, +61601:57440, +61602:57441, +61603:57442, +61604:57443, +61605:57444, +61606:57445, +61607:57446, +61608:57447, +61609:57448, +61610:57449, +61611:57450, +61612:57451, +61613:57452, +61614:57453, +61615:57454, +61616:57455, +61617:57456, +61618:57457, +61619:57458, +61620:57459, +61621:57460, +61622:57461, +61623:57462, +61624:57463, +61625:57464, +61626:57465, +61627:57466, +61628:57467, +61629:57468, +61630:57469, +61631:57470, +61632:57471, +61633:57472, +61634:57473, +61635:57474, +61636:57475, +61637:57476, +61638:57477, +61639:57478, +61640:57479, +61641:57480, +61642:57481, +61643:57482, +61644:57483, +61645:57484, +61646:57485, +61647:57486, +61648:57487, +61649:57488, +61650:57489, +61651:57490, +61652:57491, +61653:57492, +61654:57493, +61655:57494, +61656:57495, +61657:57496, +61658:57497, +61659:57498, +61660:57499, +61661:57500, +61662:57501, +61663:57502, +61664:57503, +61665:57504, +61666:57505, +61667:57506, +61668:57507, +61669:57508, +61670:57509, +61671:57510, +61672:57511, +61673:57512, +61674:57513, +61675:57514, +61676:57515, +61677:57516, +61678:57517, +61679:57518, +61680:57519, +61681:57520, +61682:57521, +61683:57522, +61684:57523, +61685:57524, +61686:57525, +61687:57526, +61688:57527, +61689:57528, +61690:57529, +61691:57530, +61692:57531, +61760:57532, +61761:57533, +61762:57534, +61763:57535, +61764:57536, +61765:57537, +61766:57538, +61767:57539, +61768:57540, +61769:57541, +61770:57542, +61771:57543, +61772:57544, +61773:57545, +61774:57546, +61775:57547, +61776:57548, +61777:57549, +61778:57550, +61779:57551, +61780:57552, +61781:57553, +61782:57554, +61783:57555, +61784:57556, +61785:57557, +61786:57558, +61787:57559, +61788:57560, +61789:57561, +61790:57562, +61791:57563, +61792:57564, +61793:57565, +61794:57566, +61795:57567, +61796:57568, +61797:57569, +61798:57570, +61799:57571, +61800:57572, +61801:57573, +61802:57574, +61803:57575, +61804:57576, +61805:57577, +61806:57578, +61807:57579, +61808:57580, +61809:57581, +61810:57582, +61811:57583, +61812:57584, +61813:57585, +61814:57586, +61815:57587, +61816:57588, +61817:57589, +61818:57590, +61819:57591, +61820:57592, +61821:57593, +61822:57594, +61824:57595, +61825:57596, +61826:57597, +61827:57598, +61828:57599, +61829:57600, +61830:57601, +61831:57602, +61832:57603, +61833:57604, +61834:57605, +61835:57606, +61836:57607, +61837:57608, +61838:57609, +61839:57610, +61840:57611, +61841:57612, +61842:57613, +61843:57614, +61844:57615, +61845:57616, +61846:57617, +61847:57618, +61848:57619, +61849:57620, +61850:57621, +61851:57622, +61852:57623, +61853:57624, +61854:57625, +61855:57626, +61856:57627, +61857:57628, +61858:57629, +61859:57630, +61860:57631, +61861:57632, +61862:57633, +61863:57634, +61864:57635, +61865:57636, +61866:57637, +61867:57638, +61868:57639, +61869:57640, +61870:57641, +61871:57642, +61872:57643, +61873:57644, +61874:57645, +61875:57646, +61876:57647, +61877:57648, +61878:57649, +61879:57650, +61880:57651, +61881:57652, +61882:57653, +61883:57654, +61884:57655, +61885:57656, +61886:57657, +61887:57658, +61888:57659, +61889:57660, +61890:57661, +61891:57662, +61892:57663, +61893:57664, +61894:57665, +61895:57666, +61896:57667, +61897:57668, +61898:57669, +61899:57670, +61900:57671, +61901:57672, +61902:57673, +61903:57674, +61904:57675, +61905:57676, +61906:57677, +61907:57678, +61908:57679, +61909:57680, +61910:57681, +61911:57682, +61912:57683, +61913:57684, +61914:57685, +61915:57686, +61916:57687, +61917:57688, +61918:57689, +61919:57690, +61920:57691, +61921:57692, +61922:57693, +61923:57694, +61924:57695, +61925:57696, +61926:57697, +61927:57698, +61928:57699, +61929:57700, +61930:57701, +61931:57702, +61932:57703, +61933:57704, +61934:57705, +61935:57706, +61936:57707, +61937:57708, +61938:57709, +61939:57710, +61940:57711, +61941:57712, +61942:57713, +61943:57714, +61944:57715, +61945:57716, +61946:57717, +61947:57718, +61948:57719, +62016:57720, +62017:57721, +62018:57722, +62019:57723, +62020:57724, +62021:57725, +62022:57726, +62023:57727, +62024:57728, +62025:57729, +62026:57730, +62027:57731, +62028:57732, +62029:57733, +62030:57734, +62031:57735, +62032:57736, +62033:57737, +62034:57738, +62035:57739, +62036:57740, +62037:57741, +62038:57742, +62039:57743, +62040:57744, +62041:57745, +62042:57746, +62043:57747, +62044:57748, +62045:57749, +62046:57750, +62047:57751, +62048:57752, +62049:57753, +62050:57754, +62051:57755, +62052:57756, +62053:57757, +62054:57758, +62055:57759, +62056:57760, +62057:57761, +62058:57762, +62059:57763, +62060:57764, +62061:57765, +62062:57766, +62063:57767, +62064:57768, +62065:57769, +62066:57770, +62067:57771, +62068:57772, +62069:57773, +62070:57774, +62071:57775, +62072:57776, +62073:57777, +62074:57778, +62075:57779, +62076:57780, +62077:57781, +62078:57782, +62080:57783, +62081:57784, +62082:57785, +62083:57786, +62084:57787, +62085:57788, +62086:57789, +62087:57790, +62088:57791, +62089:57792, +62090:57793, +62091:57794, +62092:57795, +62093:57796, +62094:57797, +62095:57798, +62096:57799, +62097:57800, +62098:57801, +62099:57802, +62100:57803, +62101:57804, +62102:57805, +62103:57806, +62104:57807, +62105:57808, +62106:57809, +62107:57810, +62108:57811, +62109:57812, +62110:57813, +62111:57814, +62112:57815, +62113:57816, +62114:57817, +62115:57818, +62116:57819, +62117:57820, +62118:57821, +62119:57822, +62120:57823, +62121:57824, +62122:57825, +62123:57826, +62124:57827, +62125:57828, +62126:57829, +62127:57830, +62128:57831, +62129:57832, +62130:57833, +62131:57834, +62132:57835, +62133:57836, +62134:57837, +62135:57838, +62136:57839, +62137:57840, +62138:57841, +62139:57842, +62140:57843, +62141:57844, +62142:57845, +62143:57846, +62144:57847, +62145:57848, +62146:57849, +62147:57850, +62148:57851, +62149:57852, +62150:57853, +62151:57854, +62152:57855, +62153:57856, +62154:57857, +62155:57858, +62156:57859, +62157:57860, +62158:57861, +62159:57862, +62160:57863, +62161:57864, +62162:57865, +62163:57866, +62164:57867, +62165:57868, +62166:57869, +62167:57870, +62168:57871, +62169:57872, +62170:57873, +62171:57874, +62172:57875, +62173:57876, +62174:57877, +62175:57878, +62176:57879, +62177:57880, +62178:57881, +62179:57882, +62180:57883, +62181:57884, +62182:57885, +62183:57886, +62184:57887, +62185:57888, +62186:57889, +62187:57890, +62188:57891, +62189:57892, +62190:57893, +62191:57894, +62192:57895, +62193:57896, +62194:57897, +62195:57898, +62196:57899, +62197:57900, +62198:57901, +62199:57902, +62200:57903, +62201:57904, +62202:57905, +62203:57906, +62204:57907, +62272:57908, +62273:57909, +62274:57910, +62275:57911, +62276:57912, +62277:57913, +62278:57914, +62279:57915, +62280:57916, +62281:57917, +62282:57918, +62283:57919, +62284:57920, +62285:57921, +62286:57922, +62287:57923, +62288:57924, +62289:57925, +62290:57926, +62291:57927, +62292:57928, +62293:57929, +62294:57930, +62295:57931, +62296:57932, +62297:57933, +62298:57934, +62299:57935, +62300:57936, +62301:57937, +62302:57938, +62303:57939, +62304:57940, +62305:57941, +62306:57942, +62307:57943, +62308:57944, +62309:57945, +62310:57946, +62311:57947, +62312:57948, +62313:57949, +62314:57950, +62315:57951, +62316:57952, +62317:57953, +62318:57954, +62319:57955, +62320:57956, +62321:57957, +62322:57958, +62323:57959, +62324:57960, +62325:57961, +62326:57962, +62327:57963, +62328:57964, +62329:57965, +62330:57966, +62331:57967, +62332:57968, +62333:57969, +62334:57970, +62336:57971, +62337:57972, +62338:57973, +62339:57974, +62340:57975, +62341:57976, +62342:57977, +62343:57978, +62344:57979, +62345:57980, +62346:57981, +62347:57982, +62348:57983, +62349:57984, +62350:57985, +62351:57986, +62352:57987, +62353:57988, +62354:57989, +62355:57990, +62356:57991, +62357:57992, +62358:57993, +62359:57994, +62360:57995, +62361:57996, +62362:57997, +62363:57998, +62364:57999, +62365:58000, +62366:58001, +62367:58002, +62368:58003, +62369:58004, +62370:58005, +62371:58006, +62372:58007, +62373:58008, +62374:58009, +62375:58010, +62376:58011, +62377:58012, +62378:58013, +62379:58014, +62380:58015, +62381:58016, +62382:58017, +62383:58018, +62384:58019, +62385:58020, +62386:58021, +62387:58022, +62388:58023, +62389:58024, +62390:58025, +62391:58026, +62392:58027, +62393:58028, +62394:58029, +62395:58030, +62396:58031, +62397:58032, +62398:58033, +62399:58034, +62400:58035, +62401:58036, +62402:58037, +62403:58038, +62404:58039, +62405:58040, +62406:58041, +62407:58042, +62408:58043, +62409:58044, +62410:58045, +62411:58046, +62412:58047, +62413:58048, +62414:58049, +62415:58050, +62416:58051, +62417:58052, +62418:58053, +62419:58054, +62420:58055, +62421:58056, +62422:58057, +62423:58058, +62424:58059, +62425:58060, +62426:58061, +62427:58062, +62428:58063, +62429:58064, +62430:58065, +62431:58066, +62432:58067, +62433:58068, +62434:58069, +62435:58070, +62436:58071, +62437:58072, +62438:58073, +62439:58074, +62440:58075, +62441:58076, +62442:58077, +62443:58078, +62444:58079, +62445:58080, +62446:58081, +62447:58082, +62448:58083, +62449:58084, +62450:58085, +62451:58086, +62452:58087, +62453:58088, +62454:58089, +62455:58090, +62456:58091, +62457:58092, +62458:58093, +62459:58094, +62460:58095, +62528:58096, +62529:58097, +62530:58098, +62531:58099, +62532:58100, +62533:58101, +62534:58102, +62535:58103, +62536:58104, +62537:58105, +62538:58106, +62539:58107, +62540:58108, +62541:58109, +62542:58110, +62543:58111, +62544:58112, +62545:58113, +62546:58114, +62547:58115, +62548:58116, +62549:58117, +62550:58118, +62551:58119, +62552:58120, +62553:58121, +62554:58122, +62555:58123, +62556:58124, +62557:58125, +62558:58126, +62559:58127, +62560:58128, +62561:58129, +62562:58130, +62563:58131, +62564:58132, +62565:58133, +62566:58134, +62567:58135, +62568:58136, +62569:58137, +62570:58138, +62571:58139, +62572:58140, +62573:58141, +62574:58142, +62575:58143, +62576:58144, +62577:58145, +62578:58146, +62579:58147, +62580:58148, +62581:58149, +62582:58150, +62583:58151, +62584:58152, +62585:58153, +62586:58154, +62587:58155, +62588:58156, +62589:58157, +62590:58158, +62592:58159, +62593:58160, +62594:58161, +62595:58162, +62596:58163, +62597:58164, +62598:58165, +62599:58166, +62600:58167, +62601:58168, +62602:58169, +62603:58170, +62604:58171, +62605:58172, +62606:58173, +62607:58174, +62608:58175, +62609:58176, +62610:58177, +62611:58178, +62612:58179, +62613:58180, +62614:58181, +62615:58182, +62616:58183, +62617:58184, +62618:58185, +62619:58186, +62620:58187, +62621:58188, +62622:58189, +62623:58190, +62624:58191, +62625:58192, +62626:58193, +62627:58194, +62628:58195, +62629:58196, +62630:58197, +62631:58198, +62632:58199, +62633:58200, +62634:58201, +62635:58202, +62636:58203, +62637:58204, +62638:58205, +62639:58206, +62640:58207, +62641:58208, +62642:58209, +62643:58210, +62644:58211, +62645:58212, +62646:58213, +62647:58214, +62648:58215, +62649:58216, +62650:58217, +62651:58218, +62652:58219, +62653:58220, +62654:58221, +62655:58222, +62656:58223, +62657:58224, +62658:58225, +62659:58226, +62660:58227, +62661:58228, +62662:58229, +62663:58230, +62664:58231, +62665:58232, +62666:58233, +62667:58234, +62668:58235, +62669:58236, +62670:58237, +62671:58238, +62672:58239, +62673:58240, +62674:58241, +62675:58242, +62676:58243, +62677:58244, +62678:58245, +62679:58246, +62680:58247, +62681:58248, +62682:58249, +62683:58250, +62684:58251, +62685:58252, +62686:58253, +62687:58254, +62688:58255, +62689:58256, +62690:58257, +62691:58258, +62692:58259, +62693:58260, +62694:58261, +62695:58262, +62696:58263, +62697:58264, +62698:58265, +62699:58266, +62700:58267, +62701:58268, +62702:58269, +62703:58270, +62704:58271, +62705:58272, +62706:58273, +62707:58274, +62708:58275, +62709:58276, +62710:58277, +62711:58278, +62712:58279, +62713:58280, +62714:58281, +62715:58282, +62716:58283, +62784:58284, +62785:58285, +62786:58286, +62787:58287, +62788:58288, +62789:58289, +62790:58290, +62791:58291, +62792:58292, +62793:58293, +62794:58294, +62795:58295, +62796:58296, +62797:58297, +62798:58298, +62799:58299, +62800:58300, +62801:58301, +62802:58302, +62803:58303, +62804:58304, +62805:58305, +62806:58306, +62807:58307, +62808:58308, +62809:58309, +62810:58310, +62811:58311, +62812:58312, +62813:58313, +62814:58314, +62815:58315, +62816:58316, +62817:58317, +62818:58318, +62819:58319, +62820:58320, +62821:58321, +62822:58322, +62823:58323, +62824:58324, +62825:58325, +62826:58326, +62827:58327, +62828:58328, +62829:58329, +62830:58330, +62831:58331, +62832:58332, +62833:58333, +62834:58334, +62835:58335, +62836:58336, +62837:58337, +62838:58338, +62839:58339, +62840:58340, +62841:58341, +62842:58342, +62843:58343, +62844:58344, +62845:58345, +62846:58346, +62848:58347, +62849:58348, +62850:58349, +62851:58350, +62852:58351, +62853:58352, +62854:58353, +62855:58354, +62856:58355, +62857:58356, +62858:58357, +62859:58358, +62860:58359, +62861:58360, +62862:58361, +62863:58362, +62864:58363, +62865:58364, +62866:58365, +62867:58366, +62868:58367, +62869:58368, +62870:58369, +62871:58370, +62872:58371, +62873:58372, +62874:58373, +62875:58374, +62876:58375, +62877:58376, +62878:58377, +62879:58378, +62880:58379, +62881:58380, +62882:58381, +62883:58382, +62884:58383, +62885:58384, +62886:58385, +62887:58386, +62888:58387, +62889:58388, +62890:58389, +62891:58390, +62892:58391, +62893:58392, +62894:58393, +62895:58394, +62896:58395, +62897:58396, +62898:58397, +62899:58398, +62900:58399, +62901:58400, +62902:58401, +62903:58402, +62904:58403, +62905:58404, +62906:58405, +62907:58406, +62908:58407, +62909:58408, +62910:58409, +62911:58410, +62912:58411, +62913:58412, +62914:58413, +62915:58414, +62916:58415, +62917:58416, +62918:58417, +62919:58418, +62920:58419, +62921:58420, +62922:58421, +62923:58422, +62924:58423, +62925:58424, +62926:58425, +62927:58426, +62928:58427, +62929:58428, +62930:58429, +62931:58430, +62932:58431, +62933:58432, +62934:58433, +62935:58434, +62936:58435, +62937:58436, +62938:58437, +62939:58438, +62940:58439, +62941:58440, +62942:58441, +62943:58442, +62944:58443, +62945:58444, +62946:58445, +62947:58446, +62948:58447, +62949:58448, +62950:58449, +62951:58450, +62952:58451, +62953:58452, +62954:58453, +62955:58454, +62956:58455, +62957:58456, +62958:58457, +62959:58458, +62960:58459, +62961:58460, +62962:58461, +62963:58462, +62964:58463, +62965:58464, +62966:58465, +62967:58466, +62968:58467, +62969:58468, +62970:58469, +62971:58470, +62972:58471, +63040:58472, +63041:58473, +63042:58474, +63043:58475, +63044:58476, +63045:58477, +63046:58478, +63047:58479, +63048:58480, +63049:58481, +63050:58482, +63051:58483, +63052:58484, +63053:58485, +63054:58486, +63055:58487, +63056:58488, +63057:58489, +63058:58490, +63059:58491, +63060:58492, +63061:58493, +63062:58494, +63063:58495, +63064:58496, +63065:58497, +63066:58498, +63067:58499, +63068:58500, +63069:58501, +63070:58502, +63071:58503, +63072:58504, +63073:58505, +63074:58506, +63075:58507, +63076:58508, +63077:58509, +63078:58510, +63079:58511, +63080:58512, +63081:58513, +63082:58514, +63083:58515, +63084:58516, +63085:58517, +63086:58518, +63087:58519, +63088:58520, +63089:58521, +63090:58522, +63091:58523, +63092:58524, +63093:58525, +63094:58526, +63095:58527, +63096:58528, +63097:58529, +63098:58530, +63099:58531, +63100:58532, +63101:58533, +63102:58534, +63104:58535, +63105:58536, +63106:58537, +63107:58538, +63108:58539, +63109:58540, +63110:58541, +63111:58542, +63112:58543, +63113:58544, +63114:58545, +63115:58546, +63116:58547, +63117:58548, +63118:58549, +63119:58550, +63120:58551, +63121:58552, +63122:58553, +63123:58554, +63124:58555, +63125:58556, +63126:58557, +63127:58558, +63128:58559, +63129:58560, +63130:58561, +63131:58562, +63132:58563, +63133:58564, +63134:58565, +63135:58566, +63136:58567, +63137:58568, +63138:58569, +63139:58570, +63140:58571, +63141:58572, +63142:58573, +63143:58574, +63144:58575, +63145:58576, +63146:58577, +63147:58578, +63148:58579, +63149:58580, +63150:58581, +63151:58582, +63152:58583, +63153:58584, +63154:58585, +63155:58586, +63156:58587, +63157:58588, +63158:58589, +63159:58590, +63160:58591, +63161:58592, +63162:58593, +63163:58594, +63164:58595, +63165:58596, +63166:58597, +63167:58598, +63168:58599, +63169:58600, +63170:58601, +63171:58602, +63172:58603, +63173:58604, +63174:58605, +63175:58606, +63176:58607, +63177:58608, +63178:58609, +63179:58610, +63180:58611, +63181:58612, +63182:58613, +63183:58614, +63184:58615, +63185:58616, +63186:58617, +63187:58618, +63188:58619, +63189:58620, +63190:58621, +63191:58622, +63192:58623, +63193:58624, +63194:58625, +63195:58626, +63196:58627, +63197:58628, +63198:58629, +63199:58630, +63200:58631, +63201:58632, +63202:58633, +63203:58634, +63204:58635, +63205:58636, +63206:58637, +63207:58638, +63208:58639, +63209:58640, +63210:58641, +63211:58642, +63212:58643, +63213:58644, +63214:58645, +63215:58646, +63216:58647, +63217:58648, +63218:58649, +63219:58650, +63220:58651, +63221:58652, +63222:58653, +63223:58654, +63224:58655, +63225:58656, +63226:58657, +63227:58658, +63228:58659, +63296:58660, +63297:58661, +63298:58662, +63299:58663, +63300:58664, +63301:58665, +63302:58666, +63303:58667, +63304:58668, +63305:58669, +63306:58670, +63307:58671, +63308:58672, +63309:58673, +63310:58674, +63311:58675, +63312:58676, +63313:58677, +63314:58678, +63315:58679, +63316:58680, +63317:58681, +63318:58682, +63319:58683, +63320:58684, +63321:58685, +63322:58686, +63323:58687, +63324:58688, +63325:58689, +63326:58690, +63327:58691, +63328:58692, +63329:58693, +63330:58694, +63331:58695, +63332:58696, +63333:58697, +63334:58698, +63335:58699, +63336:58700, +63337:58701, +63338:58702, +63339:58703, +63340:58704, +63341:58705, +63342:58706, +63343:58707, +63344:58708, +63345:58709, +63346:58710, +63347:58711, +63348:58712, +63349:58713, +63350:58714, +63351:58715, +63352:58716, +63353:58717, +63354:58718, +63355:58719, +63356:58720, +63357:58721, +63358:58722, +63360:58723, +63361:58724, +63362:58725, +63363:58726, +63364:58727, +63365:58728, +63366:58729, +63367:58730, +63368:58731, +63369:58732, +63370:58733, +63371:58734, +63372:58735, +63373:58736, +63374:58737, +63375:58738, +63376:58739, +63377:58740, +63378:58741, +63379:58742, +63380:58743, +63381:58744, +63382:58745, +63383:58746, +63384:58747, +63385:58748, +63386:58749, +63387:58750, +63388:58751, +63389:58752, +63390:58753, +63391:58754, +63392:58755, +63393:58756, +63394:58757, +63395:58758, +63396:58759, +63397:58760, +63398:58761, +63399:58762, +63400:58763, +63401:58764, +63402:58765, +63403:58766, +63404:58767, +63405:58768, +63406:58769, +63407:58770, +63408:58771, +63409:58772, +63410:58773, +63411:58774, +63412:58775, +63413:58776, +63414:58777, +63415:58778, +63416:58779, +63417:58780, +63418:58781, +63419:58782, +63420:58783, +63421:58784, +63422:58785, +63423:58786, +63424:58787, +63425:58788, +63426:58789, +63427:58790, +63428:58791, +63429:58792, +63430:58793, +63431:58794, +63432:58795, +63433:58796, +63434:58797, +63435:58798, +63436:58799, +63437:58800, +63438:58801, +63439:58802, +63440:58803, +63441:58804, +63442:58805, +63443:58806, +63444:58807, +63445:58808, +63446:58809, +63447:58810, +63448:58811, +63449:58812, +63450:58813, +63451:58814, +63452:58815, +63453:58816, +63454:58817, +63455:58818, +63456:58819, +63457:58820, +63458:58821, +63459:58822, +63460:58823, +63461:58824, +63462:58825, +63463:58826, +63464:58827, +63465:58828, +63466:58829, +63467:58830, +63468:58831, +63469:58832, +63470:58833, +63471:58834, +63472:58835, +63473:58836, +63474:58837, +63475:58838, +63476:58839, +63477:58840, +63478:58841, +63479:58842, +63480:58843, +63481:58844, +63482:58845, +63483:58846, +63484:58847, +63552:58848, +63553:58849, +63554:58850, +63555:58851, +63556:58852, +63557:58853, +63558:58854, +63559:58855, +63560:58856, +63561:58857, +63562:58858, +63563:58859, +63564:58860, +63565:58861, +63566:58862, +63567:58863, +63568:58864, +63569:58865, +63570:58866, +63571:58867, +63572:58868, +63573:58869, +63574:58870, +63575:58871, +63576:58872, +63577:58873, +63578:58874, +63579:58875, +63580:58876, +63581:58877, +63582:58878, +63583:58879, +63584:58880, +63585:58881, +63586:58882, +63587:58883, +63588:58884, +63589:58885, +63590:58886, +63591:58887, +63592:58888, +63593:58889, +63594:58890, +63595:58891, +63596:58892, +63597:58893, +63598:58894, +63599:58895, +63600:58896, +63601:58897, +63602:58898, +63603:58899, +63604:58900, +63605:58901, +63606:58902, +63607:58903, +63608:58904, +63609:58905, +63610:58906, +63611:58907, +63612:58908, +63613:58909, +63614:58910, +63616:58911, +63617:58912, +63618:58913, +63619:58914, +63620:58915, +63621:58916, +63622:58917, +63623:58918, +63624:58919, +63625:58920, +63626:58921, +63627:58922, +63628:58923, +63629:58924, +63630:58925, +63631:58926, +63632:58927, +63633:58928, +63634:58929, +63635:58930, +63636:58931, +63637:58932, +63638:58933, +63639:58934, +63640:58935, +63641:58936, +63642:58937, +63643:58938, +63644:58939, +63645:58940, +63646:58941, +63647:58942, +63648:58943, +63649:58944, +63650:58945, +63651:58946, +63652:58947, +63653:58948, +63654:58949, +63655:58950, +63656:58951, +63657:58952, +63658:58953, +63659:58954, +63660:58955, +63661:58956, +63662:58957, +63663:58958, +63664:58959, +63665:58960, +63666:58961, +63667:58962, +63668:58963, +63669:58964, +63670:58965, +63671:58966, +63672:58967, +63673:58968, +63674:58969, +63675:58970, +63676:58971, +63677:58972, +63678:58973, +63679:58974, +63680:58975, +63681:58976, +63682:58977, +63683:58978, +63684:58979, +63685:58980, +63686:58981, +63687:58982, +63688:58983, +63689:58984, +63690:58985, +63691:58986, +63692:58987, +63693:58988, +63694:58989, +63695:58990, +63696:58991, +63697:58992, +63698:58993, +63699:58994, +63700:58995, +63701:58996, +63702:58997, +63703:58998, +63704:58999, +63705:59000, +63706:59001, +63707:59002, +63708:59003, +63709:59004, +63710:59005, +63711:59006, +63712:59007, +63713:59008, +63714:59009, +63715:59010, +63716:59011, +63717:59012, +63718:59013, +63719:59014, +63720:59015, +63721:59016, +63722:59017, +63723:59018, +63724:59019, +63725:59020, +63726:59021, +63727:59022, +63728:59023, +63729:59024, +63730:59025, +63731:59026, +63732:59027, +63733:59028, +63734:59029, +63735:59030, +63736:59031, +63737:59032, +63738:59033, +63739:59034, +63740:59035, +64064:8560, +64065:8561, +64066:8562, +64067:8563, +64068:8564, +64069:8565, +64070:8566, +64071:8567, +64072:8568, +64073:8569, +64074:8544, +64075:8545, +64076:8546, +64077:8547, +64078:8548, +64079:8549, +64080:8550, +64081:8551, +64082:8552, +64083:8553, +64084:65506, +64085:65508, +64086:65287, +64087:65282, +64088:12849, +64089:8470, +64090:8481, +64091:8757, +64092:32394, +64093:35100, +64094:37704, +64095:37512, +64096:34012, +64097:20425, +64098:28859, +64099:26161, +64100:26824, +64101:37625, +64102:26363, +64103:24389, +64104:20008, +64105:20193, +64106:20220, +64107:20224, +64108:20227, +64109:20281, +64110:20310, +64111:20370, +64112:20362, +64113:20378, +64114:20372, +64115:20429, +64116:20544, +64117:20514, +64118:20479, +64119:20510, +64120:20550, +64121:20592, +64122:20546, +64123:20628, +64124:20724, +64125:20696, +64126:20810, +64128:20836, +64129:20893, +64130:20926, +64131:20972, +64132:21013, +64133:21148, +64134:21158, +64135:21184, +64136:21211, +64137:21248, +64138:21255, +64139:21284, +64140:21362, +64141:21395, +64142:21426, +64143:21469, +64144:64014, +64145:21660, +64146:21642, +64147:21673, +64148:21759, +64149:21894, +64150:22361, +64151:22373, +64152:22444, +64153:22472, +64154:22471, +64155:64015, +64156:64016, +64157:22686, +64158:22706, +64159:22795, +64160:22867, +64161:22875, +64162:22877, +64163:22883, +64164:22948, +64165:22970, +64166:23382, +64167:23488, +64168:29999, +64169:23512, +64170:23532, +64171:23582, +64172:23718, +64173:23738, +64174:23797, +64175:23847, +64176:23891, +64177:64017, +64178:23874, +64179:23917, +64180:23992, +64181:23993, +64182:24016, +64183:24353, +64184:24372, +64185:24423, +64186:24503, +64187:24542, +64188:24669, +64189:24709, +64190:24714, +64191:24798, +64192:24789, +64193:24864, +64194:24818, +64195:24849, +64196:24887, +64197:24880, +64198:24984, +64199:25107, +64200:25254, +64201:25589, +64202:25696, +64203:25757, +64204:25806, +64205:25934, +64206:26112, +64207:26133, +64208:26171, +64209:26121, +64210:26158, +64211:26142, +64212:26148, +64213:26213, +64214:26199, +64215:26201, +64216:64018, +64217:26227, +64218:26265, +64219:26272, +64220:26290, +64221:26303, +64222:26362, +64223:26382, +64224:63785, +64225:26470, +64226:26555, +64227:26706, +64228:26560, +64229:26625, +64230:26692, +64231:26831, +64232:64019, +64233:26984, +64234:64020, +64235:27032, +64236:27106, +64237:27184, +64238:27243, +64239:27206, +64240:27251, +64241:27262, +64242:27362, +64243:27364, +64244:27606, +64245:27711, +64246:27740, +64247:27782, +64248:27759, +64249:27866, +64250:27908, +64251:28039, +64252:28015, +64320:28054, +64321:28076, +64322:28111, +64323:28152, +64324:28146, +64325:28156, +64326:28217, +64327:28252, +64328:28199, +64329:28220, +64330:28351, +64331:28552, +64332:28597, +64333:28661, +64334:28677, +64335:28679, +64336:28712, +64337:28805, +64338:28843, +64339:28943, +64340:28932, +64341:29020, +64342:28998, +64343:28999, +64344:64021, +64345:29121, +64346:29182, +64347:29361, +64348:29374, +64349:29476, +64350:64022, +64351:29559, +64352:29629, +64353:29641, +64354:29654, +64355:29667, +64356:29650, +64357:29703, +64358:29685, +64359:29734, +64360:29738, +64361:29737, +64362:29742, +64363:29794, +64364:29833, +64365:29855, +64366:29953, +64367:30063, +64368:30338, +64369:30364, +64370:30366, +64371:30363, +64372:30374, +64373:64023, +64374:30534, +64375:21167, +64376:30753, +64377:30798, +64378:30820, +64379:30842, +64380:31024, +64381:64024, +64382:64025, +64384:64026, +64385:31124, +64386:64027, +64387:31131, +64388:31441, +64389:31463, +64390:64028, +64391:31467, +64392:31646, +64393:64029, +64394:32072, +64395:32092, +64396:32183, +64397:32160, +64398:32214, +64399:32338, +64400:32583, +64401:32673, +64402:64030, +64403:33537, +64404:33634, +64405:33663, +64406:33735, +64407:33782, +64408:33864, +64409:33972, +64410:34131, +64411:34137, +64412:34155, +64413:64031, +64414:34224, +64415:64032, +64416:64033, +64417:34823, +64418:35061, +64419:35346, +64420:35383, +64421:35449, +64422:35495, +64423:35518, +64424:35551, +64425:64034, +64426:35574, +64427:35667, +64428:35711, +64429:36080, +64430:36084, +64431:36114, +64432:36214, +64433:64035, +64434:36559, +64435:64036, +64436:64037, +64437:36967, +64438:37086, +64439:64038, +64440:37141, +64441:37159, +64442:37338, +64443:37335, +64444:37342, +64445:37357, +64446:37358, +64447:37348, +64448:37349, +64449:37382, +64450:37392, +64451:37386, +64452:37434, +64453:37440, +64454:37436, +64455:37454, +64456:37465, +64457:37457, +64458:37433, +64459:37479, +64460:37543, +64461:37495, +64462:37496, +64463:37607, +64464:37591, +64465:37593, +64466:37584, +64467:64039, +64468:37589, +64469:37600, +64470:37587, +64471:37669, +64472:37665, +64473:37627, +64474:64040, +64475:37662, +64476:37631, +64477:37661, +64478:37634, +64479:37744, +64480:37719, +64481:37796, +64482:37830, +64483:37854, +64484:37880, +64485:37937, +64486:37957, +64487:37960, +64488:38290, +64489:63964, +64490:64041, +64491:38557, +64492:38575, +64493:38707, +64494:38715, +64495:38723, +64496:38733, +64497:38735, +64498:38737, +64499:38741, +64500:38999, +64501:39013, +64502:64042, +64503:64043, +64504:39207, +64505:64044, +64506:39326, +64507:39502, +64508:39641, +64576:39644, +64577:39797, +64578:39794, +64579:39823, +64580:39857, +64581:39867, +64582:39936, +64583:40304, +64584:40299, +64585:64045, +64586:40473, +64587:40657 +}; + +/** + * @author takahiro / https://github.com/takahirox + */ + +function DataViewEx ( buffer, littleEndian ) { + + this.dv = new DataView( buffer ); + this.offset = 0; + this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true; + this.encoder = new CharsetEncoder(); + +} + +DataViewEx.prototype = { + + constructor: DataViewEx, + + getInt8: function () { + + var value = this.dv.getInt8( this.offset ); + this.offset += 1; + return value; + + }, + + getInt8Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getInt8() ); + + } + + return a; + + }, + + getUint8: function () { + + var value = this.dv.getUint8( this.offset ); + this.offset += 1; + return value; + + }, + + getUint8Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getUint8() ); + + } + + return a; + + }, + + + getInt16: function () { + + var value = this.dv.getInt16( this.offset, this.littleEndian ); + this.offset += 2; + return value; + + }, + + getInt16Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getInt16() ); + + } + + return a; + + }, + + getUint16: function () { + + var value = this.dv.getUint16( this.offset, this.littleEndian ); + this.offset += 2; + return value; + + }, + + getUint16Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getUint16() ); + + } + + return a; + + }, + + getInt32: function () { + + var value = this.dv.getInt32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + }, + + getInt32Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getInt32() ); + + } + + return a; + + }, + + getUint32: function () { + + var value = this.dv.getUint32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + }, + + getUint32Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getUint32() ); + + } + + return a; + + }, + + getFloat32: function () { + + var value = this.dv.getFloat32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + }, + + getFloat32Array: function( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getFloat32() ); + + } + + return a; + + }, + + getFloat64: function () { + + var value = this.dv.getFloat64( this.offset, this.littleEndian ); + this.offset += 8; + return value; + + }, + + getFloat64Array: function( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getFloat64() ); + + } + + return a; + + }, + + getIndex: function ( type, isUnsigned ) { + + switch ( type ) { + + case 1: + return ( isUnsigned === true ) ? this.getUint8() : this.getInt8(); + + case 2: + return ( isUnsigned === true ) ? this.getUint16() : this.getInt16(); + + case 4: + return this.getInt32(); // No Uint32 + + default: + throw 'unknown number type ' + type + ' exception.'; + + } + + }, + + getIndexArray: function ( type, size, isUnsigned ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getIndex( type, isUnsigned ) ); + + } + + return a; + + }, + + getChars: function ( size ) { + + var str = ''; + + while ( size > 0 ) { + + var value = this.getUint8(); + size--; + + if ( value === 0 ) { + + break; + + } + + str += String.fromCharCode( value ); + + } + + while ( size > 0 ) { + + this.getUint8(); + size--; + + } + + return str; + + }, + + getSjisStringsAsUnicode: function ( size ) { + + var a = []; + + while ( size > 0 ) { + + var value = this.getUint8(); + size--; + + if ( value === 0 ) { + + break; + + } + + a.push( value ); + + } + + while ( size > 0 ) { + + this.getUint8(); + size--; + + } + + return this.encoder.s2u( new Uint8Array( a ) ); + + }, + + getUnicodeStrings: function ( size ) { + + var str = ''; + + while ( size > 0 ) { + + var value = this.getUint16(); + size -= 2; + + if ( value === 0 ) { + + break; + + } + + str += String.fromCharCode( value ); + + } + + while ( size > 0 ) { + + this.getUint8(); + size--; + + } + + return str; + + }, + + getTextBuffer: function () { + + var size = this.getUint32(); + return this.getUnicodeStrings( size ); + + } + +}; + +/** + * @author takahiro / https://github.com/takahirox + */ + +function DataCreationHelper () { +} + +DataCreationHelper.prototype = { + + constructor: DataCreationHelper, + + leftToRightVector3: function ( v ) { + + v[ 2 ] = -v[ 2 ]; + + }, + + leftToRightQuaternion: function ( q ) { + + q[ 0 ] = -q[ 0 ]; + q[ 1 ] = -q[ 1 ]; + + }, + + leftToRightEuler: function ( r ) { + + r[ 0 ] = -r[ 0 ]; + r[ 1 ] = -r[ 1 ]; + + }, + + leftToRightIndexOrder: function ( p ) { + + var tmp = p[ 2 ]; + p[ 2 ] = p[ 0 ]; + p[ 0 ] = tmp; + + }, + + leftToRightVector3Range: function ( v1, v2 ) { + + var tmp = -v2[ 2 ]; + v2[ 2 ] = -v1[ 2 ]; + v1[ 2 ] = tmp; + + }, + + leftToRightEulerRange: function ( r1, r2 ) { + + var tmp1 = -r2[ 0 ]; + var tmp2 = -r2[ 1 ]; + r2[ 0 ] = -r1[ 0 ]; + r2[ 1 ] = -r1[ 1 ]; + r1[ 0 ] = tmp1; + r1[ 1 ] = tmp2; + + } + +}; + +/** + * @author takahiro / https://github.com/takahirox + */ + +function Parser() { +} + +Parser.prototype.parsePmd = function ( buffer, leftToRight ) { + + var pmd = {}; + var dv = new DataViewEx( buffer ); + + pmd.metadata = {}; + pmd.metadata.format = 'pmd'; + pmd.metadata.coordinateSystem = 'left'; + + var parseHeader = function () { + + var metadata = pmd.metadata; + metadata.magic = dv.getChars( 3 ); + + if ( metadata.magic !== 'Pmd' ) { + + throw 'PMD file magic is not Pmd, but ' + metadata.magic; + + } + + metadata.version = dv.getFloat32(); + metadata.modelName = dv.getSjisStringsAsUnicode( 20 ); + metadata.comment = dv.getSjisStringsAsUnicode( 256 ); + + }; + + var parseVertices = function () { + + var parseVertex = function () { + + var p = {}; + p.position = dv.getFloat32Array( 3 ); + p.normal = dv.getFloat32Array( 3 ); + p.uv = dv.getFloat32Array( 2 ); + p.skinIndices = dv.getUint16Array( 2 ); + p.skinWeights = [ dv.getUint8() / 100 ]; + p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); + p.edgeFlag = dv.getUint8(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.vertexCount = dv.getUint32(); + + pmd.vertices = []; + + for ( var i = 0; i < metadata.vertexCount; i++ ) { + + pmd.vertices.push( parseVertex() ); + + } + + }; + + var parseFaces = function () { + + var parseFace = function () { + + var p = {}; + p.indices = dv.getUint16Array( 3 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.faceCount = dv.getUint32() / 3; + + pmd.faces = []; + + for ( var i = 0; i < metadata.faceCount; i++ ) { + + pmd.faces.push( parseFace() ); + + } + + }; + + var parseMaterials = function () { + + var parseMaterial = function () { + + var p = {}; + p.diffuse = dv.getFloat32Array( 4 ); + p.shininess = dv.getFloat32(); + p.specular = dv.getFloat32Array( 3 ); + p.ambient = dv.getFloat32Array( 3 ); + p.toonIndex = dv.getInt8(); + p.edgeFlag = dv.getUint8(); + p.faceCount = dv.getUint32() / 3; + p.fileName = dv.getSjisStringsAsUnicode( 20 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.materialCount = dv.getUint32(); + + pmd.materials = []; + + for ( var i = 0; i < metadata.materialCount; i++ ) { + + pmd.materials.push( parseMaterial() ); + + } + + }; + + var parseBones = function () { + + var parseBone = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.parentIndex = dv.getInt16(); + p.tailIndex = dv.getInt16(); + p.type = dv.getUint8(); + p.ikIndex = dv.getInt16(); + p.position = dv.getFloat32Array( 3 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.boneCount = dv.getUint16(); + + pmd.bones = []; + + for ( var i = 0; i < metadata.boneCount; i++ ) { + + pmd.bones.push( parseBone() ); + + } + + }; + + var parseIks = function () { + + var parseIk = function () { + + var p = {}; + p.target = dv.getUint16(); + p.effector = dv.getUint16(); + p.linkCount = dv.getUint8(); + p.iteration = dv.getUint16(); + p.maxAngle = dv.getFloat32(); + + p.links = []; + for ( var i = 0; i < p.linkCount; i++ ) { + + var link = {}; + link.index = dv.getUint16(); + p.links.push( link ); + + } + + return p; + + }; + + var metadata = pmd.metadata; + metadata.ikCount = dv.getUint16(); + + pmd.iks = []; + + for ( var i = 0; i < metadata.ikCount; i++ ) { + + pmd.iks.push( parseIk() ); + + } + + }; + + var parseMorphs = function () { + + var parseMorph = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.elementCount = dv.getUint32(); + p.type = dv.getUint8(); + + p.elements = []; + for ( var i = 0; i < p.elementCount; i++ ) { + + p.elements.push( { + index: dv.getUint32(), + position: dv.getFloat32Array( 3 ) + } ) ; + + } + + return p; + + }; + + var metadata = pmd.metadata; + metadata.morphCount = dv.getUint16(); + + pmd.morphs = []; + + for ( var i = 0; i < metadata.morphCount; i++ ) { + + pmd.morphs.push( parseMorph() ); + + } + + + }; + + var parseMorphFrames = function () { + + var parseMorphFrame = function () { + + var p = {}; + p.index = dv.getUint16(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.morphFrameCount = dv.getUint8(); + + pmd.morphFrames = []; + + for ( var i = 0; i < metadata.morphFrameCount; i++ ) { + + pmd.morphFrames.push( parseMorphFrame() ); + + } + + }; + + var parseBoneFrameNames = function () { + + var parseBoneFrameName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 50 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.boneFrameNameCount = dv.getUint8(); + + pmd.boneFrameNames = []; + + for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { + + pmd.boneFrameNames.push( parseBoneFrameName() ); + + } + + }; + + var parseBoneFrames = function () { + + var parseBoneFrame = function () { + + var p = {}; + p.boneIndex = dv.getInt16(); + p.frameIndex = dv.getUint8(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.boneFrameCount = dv.getUint32(); + + pmd.boneFrames = []; + + for ( var i = 0; i < metadata.boneFrameCount; i++ ) { + + pmd.boneFrames.push( parseBoneFrame() ); + + } + + }; + + var parseEnglishHeader = function () { + + var metadata = pmd.metadata; + metadata.englishCompatibility = dv.getUint8(); + + if ( metadata.englishCompatibility > 0 ) { + + metadata.englishModelName = dv.getSjisStringsAsUnicode( 20 ); + metadata.englishComment = dv.getSjisStringsAsUnicode( 256 ); + + } + + }; + + var parseEnglishBoneNames = function () { + + var parseEnglishBoneName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + return p; + + }; + + var metadata = pmd.metadata; + + if ( metadata.englishCompatibility === 0 ) { + + return; + + } + + pmd.englishBoneNames = []; + + for ( var i = 0; i < metadata.boneCount; i++ ) { + + pmd.englishBoneNames.push( parseEnglishBoneName() ); + + } + + }; + + var parseEnglishMorphNames = function () { + + var parseEnglishMorphName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + return p; + + }; + + var metadata = pmd.metadata; + + if ( metadata.englishCompatibility === 0 ) { + + return; + + } + + pmd.englishMorphNames = []; + + for ( var i = 0; i < metadata.morphCount - 1; i++ ) { + + pmd.englishMorphNames.push( parseEnglishMorphName() ); + + } + + }; + + var parseEnglishBoneFrameNames = function () { + + var parseEnglishBoneFrameName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 50 ); + return p; + + }; + + var metadata = pmd.metadata; + + if ( metadata.englishCompatibility === 0 ) { + + return; + + } + + pmd.englishBoneFrameNames = []; + + for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { + + pmd.englishBoneFrameNames.push( parseEnglishBoneFrameName() ); + + } + + }; + + var parseToonTextures = function () { + + var parseToonTexture = function () { + + var p = {}; + p.fileName = dv.getSjisStringsAsUnicode( 100 ); + return p; + + }; + + pmd.toonTextures = []; + + for ( var i = 0; i < 10; i++ ) { + + pmd.toonTextures.push( parseToonTexture() ); + + } + + }; + + var parseRigidBodies = function () { + + var parseRigidBody = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.boneIndex = dv.getInt16(); + p.groupIndex = dv.getUint8(); + p.groupTarget = dv.getUint16(); + p.shapeType = dv.getUint8(); + p.width = dv.getFloat32(); + p.height = dv.getFloat32(); + p.depth = dv.getFloat32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.weight = dv.getFloat32(); + p.positionDamping = dv.getFloat32(); + p.rotationDamping = dv.getFloat32(); + p.restitution = dv.getFloat32(); + p.friction = dv.getFloat32(); + p.type = dv.getUint8(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.rigidBodyCount = dv.getUint32(); + + pmd.rigidBodies = []; + + for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { + + pmd.rigidBodies.push( parseRigidBody() ); + + } + + }; + + var parseConstraints = function () { + + var parseConstraint = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.rigidBodyIndex1 = dv.getUint32(); + p.rigidBodyIndex2 = dv.getUint32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.translationLimitation1 = dv.getFloat32Array( 3 ); + p.translationLimitation2 = dv.getFloat32Array( 3 ); + p.rotationLimitation1 = dv.getFloat32Array( 3 ); + p.rotationLimitation2 = dv.getFloat32Array( 3 ); + p.springPosition = dv.getFloat32Array( 3 ); + p.springRotation = dv.getFloat32Array( 3 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.constraintCount = dv.getUint32(); + + pmd.constraints = []; + + for ( var i = 0; i < metadata.constraintCount; i++ ) { + + pmd.constraints.push( parseConstraint() ); + + } + + }; + + parseHeader(); + parseVertices(); + parseFaces(); + parseMaterials(); + parseBones(); + parseIks(); + parseMorphs(); + parseMorphFrames(); + parseBoneFrameNames(); + parseBoneFrames(); + parseEnglishHeader(); + parseEnglishBoneNames(); + parseEnglishMorphNames(); + parseEnglishBoneFrameNames(); + parseToonTextures(); + parseRigidBodies(); + parseConstraints(); + + if ( leftToRight === true ) this.leftToRightModel( pmd ); + + // console.log( pmd ); // for console debug + + return pmd; + +}; + +Parser.prototype.parsePmx = function ( buffer, leftToRight ) { + + var pmx = {}; + var dv = new DataViewEx( buffer ); + + pmx.metadata = {}; + pmx.metadata.format = 'pmx'; + pmx.metadata.coordinateSystem = 'left'; + + var parseHeader = function () { + + var metadata = pmx.metadata; + metadata.magic = dv.getChars( 4 ); + + // Note: don't remove the last blank space. + if ( metadata.magic !== 'PMX ' ) { + + throw 'PMX file magic is not PMX , but ' + metadata.magic; + + } + + metadata.version = dv.getFloat32(); + + if ( metadata.version !== 2.0 && metadata.version !== 2.1 ) { + + throw 'PMX version ' + metadata.version + ' is not supported.'; + + } + + metadata.headerSize = dv.getUint8(); + metadata.encoding = dv.getUint8(); + metadata.additionalUvNum = dv.getUint8(); + metadata.vertexIndexSize = dv.getUint8(); + metadata.textureIndexSize = dv.getUint8(); + metadata.materialIndexSize = dv.getUint8(); + metadata.boneIndexSize = dv.getUint8(); + metadata.morphIndexSize = dv.getUint8(); + metadata.rigidBodyIndexSize = dv.getUint8(); + metadata.modelName = dv.getTextBuffer(); + metadata.englishModelName = dv.getTextBuffer(); + metadata.comment = dv.getTextBuffer(); + metadata.englishComment = dv.getTextBuffer(); + + }; + + var parseVertices = function () { + + var parseVertex = function () { + + var p = {}; + p.position = dv.getFloat32Array( 3 ); + p.normal = dv.getFloat32Array( 3 ); + p.uv = dv.getFloat32Array( 2 ); + + p.auvs = []; + + for ( var i = 0; i < pmx.metadata.additionalUvNum; i++ ) { + + p.auvs.push( dv.getFloat32Array( 4 ) ); + + } + + p.type = dv.getUint8(); + + var indexSize = metadata.boneIndexSize; + + if ( p.type === 0 ) { // BDEF1 + + p.skinIndices = dv.getIndexArray( indexSize, 1 ); + p.skinWeights = [ 1.0 ]; + + } else if ( p.type === 1 ) { // BDEF2 + + p.skinIndices = dv.getIndexArray( indexSize, 2 ); + p.skinWeights = dv.getFloat32Array( 1 ); + p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); + + } else if ( p.type === 2 ) { // BDEF4 + + p.skinIndices = dv.getIndexArray( indexSize, 4 ); + p.skinWeights = dv.getFloat32Array( 4 ); + + } else if ( p.type === 3 ) { // SDEF + + p.skinIndices = dv.getIndexArray( indexSize, 2 ); + p.skinWeights = dv.getFloat32Array( 1 ); + p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); + + p.skinC = dv.getFloat32Array( 3 ); + p.skinR0 = dv.getFloat32Array( 3 ); + p.skinR1 = dv.getFloat32Array( 3 ); + + // SDEF is not supported yet and is handled as BDEF2 so far. + // TODO: SDEF support + p.type = 1; + + } else { + + throw 'unsupport bone type ' + p.type + ' exception.'; + + } + + p.edgeRatio = dv.getFloat32(); + return p; + + }; + + var metadata = pmx.metadata; + metadata.vertexCount = dv.getUint32(); + + pmx.vertices = []; + + for ( var i = 0; i < metadata.vertexCount; i++ ) { + + pmx.vertices.push( parseVertex() ); + + } + + }; + + var parseFaces = function () { + + var parseFace = function () { + + var p = {}; + p.indices = dv.getIndexArray( metadata.vertexIndexSize, 3, true ); + return p; + + }; + + var metadata = pmx.metadata; + metadata.faceCount = dv.getUint32() / 3; + + pmx.faces = []; + + for ( var i = 0; i < metadata.faceCount; i++ ) { + + pmx.faces.push( parseFace() ); + + } + + }; + + var parseTextures = function () { + + var parseTexture = function () { + + return dv.getTextBuffer(); + + }; + + var metadata = pmx.metadata; + metadata.textureCount = dv.getUint32(); + + pmx.textures = []; + + for ( var i = 0; i < metadata.textureCount; i++ ) { + + pmx.textures.push( parseTexture() ); + + } + + }; + + var parseMaterials = function () { + + var parseMaterial = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.diffuse = dv.getFloat32Array( 4 ); + p.specular = dv.getFloat32Array( 3 ); + p.shininess = dv.getFloat32(); + p.ambient = dv.getFloat32Array( 3 ); + p.flag = dv.getUint8(); + p.edgeColor = dv.getFloat32Array( 4 ); + p.edgeSize = dv.getFloat32(); + p.textureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); + p.envTextureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); + p.envFlag = dv.getUint8(); + p.toonFlag = dv.getUint8(); + + if ( p.toonFlag === 0 ) { + + p.toonIndex = dv.getIndex( pmx.metadata.textureIndexSize ); + + } else if ( p.toonFlag === 1 ) { + + p.toonIndex = dv.getInt8(); + + } else { + + throw 'unknown toon flag ' + p.toonFlag + ' exception.'; + + } + + p.comment = dv.getTextBuffer(); + p.faceCount = dv.getUint32() / 3; + return p; + + }; + + var metadata = pmx.metadata; + metadata.materialCount = dv.getUint32(); + + pmx.materials = []; + + for ( var i = 0; i < metadata.materialCount; i++ ) { + + pmx.materials.push( parseMaterial() ); + + } + + }; + + var parseBones = function () { + + var parseBone = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.position = dv.getFloat32Array( 3 ); + p.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + p.transformationClass = dv.getUint32(); + p.flag = dv.getUint16(); + + if ( p.flag & 0x1 ) { + + p.connectIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + + } else { + + p.offsetPosition = dv.getFloat32Array( 3 ); + + } + + if ( p.flag & 0x100 || p.flag & 0x200 ) { + + // Note: I don't think Grant is an appropriate name + // but I found that some English translated MMD tools use this term + // so I've named it Grant so far. + // I'd rename to more appropriate name from Grant later. + var grant = {}; + + grant.isLocal = ( p.flag & 0x80 ) !== 0 ? true : false; + grant.affectRotation = ( p.flag & 0x100 ) !== 0 ? true : false; + grant.affectPosition = ( p.flag & 0x200 ) !== 0 ? true : false; + grant.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + grant.ratio = dv.getFloat32(); + + p.grant = grant; + + } + + if ( p.flag & 0x400 ) { + + p.fixAxis = dv.getFloat32Array( 3 ); + + } + + if ( p.flag & 0x800 ) { + + p.localXVector = dv.getFloat32Array( 3 ); + p.localZVector = dv.getFloat32Array( 3 ); + + } + + if ( p.flag & 0x2000 ) { + + p.key = dv.getUint32(); + + } + + if ( p.flag & 0x20 ) { + + var ik = {}; + + ik.effector = dv.getIndex( pmx.metadata.boneIndexSize ); + ik.target = null; + ik.iteration = dv.getUint32(); + ik.maxAngle = dv.getFloat32(); + ik.linkCount = dv.getUint32(); + ik.links = []; + + for ( var i = 0; i < ik.linkCount; i++ ) { + + var link = {}; + link.index = dv.getIndex( pmx.metadata.boneIndexSize ); + link.angleLimitation = dv.getUint8(); + + if ( link.angleLimitation === 1 ) { + + link.lowerLimitationAngle = dv.getFloat32Array( 3 ); + link.upperLimitationAngle = dv.getFloat32Array( 3 ); + + } + + ik.links.push( link ); + + } + + p.ik = ik; + } + + return p; + + }; + + var metadata = pmx.metadata; + metadata.boneCount = dv.getUint32(); + + pmx.bones = []; + + for ( var i = 0; i < metadata.boneCount; i++ ) { + + pmx.bones.push( parseBone() ); + + } + + }; + + var parseMorphs = function () { + + var parseMorph = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.panel = dv.getUint8(); + p.type = dv.getUint8(); + p.elementCount = dv.getUint32(); + p.elements = []; + + for ( var i = 0; i < p.elementCount; i++ ) { + + if ( p.type === 0 ) { // group morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.morphIndexSize ); + m.ratio = dv.getFloat32(); + p.elements.push( m ); + + } else if ( p.type === 1 ) { // vertex morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); + m.position = dv.getFloat32Array( 3 ); + p.elements.push( m ); + + } else if ( p.type === 2 ) { // bone morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.boneIndexSize ); + m.position = dv.getFloat32Array( 3 ); + m.rotation = dv.getFloat32Array( 4 ); + p.elements.push( m ); + + } else if ( p.type === 3 ) { // uv morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); + m.uv = dv.getFloat32Array( 4 ); + p.elements.push( m ); + + } else if ( p.type === 4 ) { // additional uv1 + + // TODO: implement + + } else if ( p.type === 5 ) { // additional uv2 + + // TODO: implement + + } else if ( p.type === 6 ) { // additional uv3 + + // TODO: implement + + } else if ( p.type === 7 ) { // additional uv4 + + // TODO: implement + + } else if ( p.type === 8 ) { // material morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.materialIndexSize ); + m.type = dv.getUint8(); + m.diffuse = dv.getFloat32Array( 4 ); + m.specular = dv.getFloat32Array( 3 ); + m.shininess = dv.getFloat32(); + m.ambient = dv.getFloat32Array( 3 ); + m.edgeColor = dv.getFloat32Array( 4 ); + m.edgeSize = dv.getFloat32(); + m.textureColor = dv.getFloat32Array( 4 ); + m.sphereTextureColor = dv.getFloat32Array( 4 ); + m.toonColor = dv.getFloat32Array( 4 ); + p.elements.push( m ); + + } + + } + + return p; + + }; + + var metadata = pmx.metadata; + metadata.morphCount = dv.getUint32(); + + pmx.morphs = []; + + for ( var i = 0; i < metadata.morphCount; i++ ) { + + pmx.morphs.push( parseMorph() ); + + } + + }; + + var parseFrames = function () { + + var parseFrame = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.type = dv.getUint8(); + p.elementCount = dv.getUint32(); + p.elements = []; + + for ( var i = 0; i < p.elementCount; i++ ) { + + var e = {}; + e.target = dv.getUint8(); + e.index = ( e.target === 0 ) ? dv.getIndex( pmx.metadata.boneIndexSize ) : dv.getIndex( pmx.metadata.morphIndexSize ); + p.elements.push( e ); + + } + + return p; + + }; + + var metadata = pmx.metadata; + metadata.frameCount = dv.getUint32(); + + pmx.frames = []; + + for ( var i = 0; i < metadata.frameCount; i++ ) { + + pmx.frames.push( parseFrame() ); + + } + + }; + + var parseRigidBodies = function () { + + var parseRigidBody = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.boneIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + p.groupIndex = dv.getUint8(); + p.groupTarget = dv.getUint16(); + p.shapeType = dv.getUint8(); + p.width = dv.getFloat32(); + p.height = dv.getFloat32(); + p.depth = dv.getFloat32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.weight = dv.getFloat32(); + p.positionDamping = dv.getFloat32(); + p.rotationDamping = dv.getFloat32(); + p.restitution = dv.getFloat32(); + p.friction = dv.getFloat32(); + p.type = dv.getUint8(); + return p; + + }; + + var metadata = pmx.metadata; + metadata.rigidBodyCount = dv.getUint32(); + + pmx.rigidBodies = []; + + for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { + + pmx.rigidBodies.push( parseRigidBody() ); + + } + + }; + + var parseConstraints = function () { + + var parseConstraint = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.type = dv.getUint8(); + p.rigidBodyIndex1 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); + p.rigidBodyIndex2 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.translationLimitation1 = dv.getFloat32Array( 3 ); + p.translationLimitation2 = dv.getFloat32Array( 3 ); + p.rotationLimitation1 = dv.getFloat32Array( 3 ); + p.rotationLimitation2 = dv.getFloat32Array( 3 ); + p.springPosition = dv.getFloat32Array( 3 ); + p.springRotation = dv.getFloat32Array( 3 ); + return p; + + }; + + var metadata = pmx.metadata; + metadata.constraintCount = dv.getUint32(); + + pmx.constraints = []; + + for ( var i = 0; i < metadata.constraintCount; i++ ) { + + pmx.constraints.push( parseConstraint() ); + + } + + }; + + parseHeader(); + parseVertices(); + parseFaces(); + parseTextures(); + parseMaterials(); + parseBones(); + parseMorphs(); + parseFrames(); + parseRigidBodies(); + parseConstraints(); + + if ( leftToRight === true ) this.leftToRightModel( pmx ); + + // console.log( pmx ); // for console debug + + return pmx; + +}; + +Parser.prototype.parseVmd = function ( buffer, leftToRight ) { + + var vmd = {}; + var dv = new DataViewEx( buffer ); + + vmd.metadata = {}; + vmd.metadata.coordinateSystem = 'left'; + + var parseHeader = function () { + + var metadata = vmd.metadata; + metadata.magic = dv.getChars( 30 ); + + if ( metadata.magic !== 'Vocaloid Motion Data 0002' ) { + + throw 'VMD file magic is not Vocaloid Motion Data 0002, but ' + metadata.magic; + + } + + metadata.name = dv.getSjisStringsAsUnicode( 20 ); + + }; + + var parseMotions = function () { + + var parseMotion = function () { + + var p = {}; + p.boneName = dv.getSjisStringsAsUnicode( 15 ); + p.frameNum = dv.getUint32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 4 ); + p.interpolation = dv.getUint8Array( 64 ); + return p; + + }; + + var metadata = vmd.metadata; + metadata.motionCount = dv.getUint32(); + + vmd.motions = []; + for ( var i = 0; i < metadata.motionCount; i++ ) { + + vmd.motions.push( parseMotion() ); + + } + + }; + + var parseMorphs = function () { + + var parseMorph = function () { + + var p = {}; + p.morphName = dv.getSjisStringsAsUnicode( 15 ); + p.frameNum = dv.getUint32(); + p.weight = dv.getFloat32(); + return p; + + }; + + var metadata = vmd.metadata; + metadata.morphCount = dv.getUint32(); + + vmd.morphs = []; + for ( var i = 0; i < metadata.morphCount; i++ ) { + + vmd.morphs.push( parseMorph() ); + + } + + }; + + var parseCameras = function () { + + var parseCamera = function () { + + var p = {}; + p.frameNum = dv.getUint32(); + p.distance = dv.getFloat32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.interpolation = dv.getUint8Array( 24 ); + p.fov = dv.getUint32(); + p.perspective = dv.getUint8(); + return p; + + }; + + var metadata = vmd.metadata; + metadata.cameraCount = dv.getUint32(); + + vmd.cameras = []; + for ( var i = 0; i < metadata.cameraCount; i++ ) { + + vmd.cameras.push( parseCamera() ); + + } + + }; + + parseHeader(); + parseMotions(); + parseMorphs(); + parseCameras(); + + if ( leftToRight === true ) this.leftToRightVmd( vmd ); + + // console.log( vmd ); // for console debug + + return vmd; + +}; + +Parser.prototype.parseVpd = function ( text, leftToRight ) { + + var vpd = {}; + + vpd.metadata = {}; + vpd.metadata.coordinateSystem = 'left'; + + vpd.bones = []; + + var commentPatternG = /\/\/\w*(\r|\n|\r\n)/g; + var newlinePattern = /\r|\n|\r\n/; + + var lines = text.replace( commentPatternG, '' ).split( newlinePattern ); + + function throwError () { + + throw 'the file seems not vpd file.'; + + } + + function checkMagic () { + + if ( lines[ 0 ] !== 'Vocaloid Pose Data file' ) { + + throwError(); + + } + + } + + function parseHeader () { + + if ( lines.length < 4 ) { + + throwError(); + + } + + vpd.metadata.parentFile = lines[ 2 ]; + vpd.metadata.boneCount = parseInt( lines[ 3 ] ); + + } + + function parseBones () { + + var boneHeaderPattern = /^\s*(Bone[0-9]+)\s*\{\s*(.*)$/; + var boneVectorPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/; + var boneQuaternionPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/; + var boneFooterPattern = /^\s*}/; + + var bones = vpd.bones; + var n = null; + var v = null; + var q = null; + + for ( var i = 4; i < lines.length; i++ ) { + + var line = lines[ i ]; + + var result; + + result = line.match( boneHeaderPattern ); + + if ( result !== null ) { + + if ( n !== null ) { + + throwError(); + + } + + n = result[ 2 ]; + + } + + result = line.match( boneVectorPattern ); + + if ( result !== null ) { + + if ( v !== null ) { + + throwError(); + + } + + v = [ + + parseFloat( result[ 1 ] ), + parseFloat( result[ 2 ] ), + parseFloat( result[ 3 ] ) + + ]; + + } + + result = line.match( boneQuaternionPattern ); + + if ( result !== null ) { + + if ( q !== null ) { + + throwError(); + + } + + q = [ + + parseFloat( result[ 1 ] ), + parseFloat( result[ 2 ] ), + parseFloat( result[ 3 ] ), + parseFloat( result[ 4 ] ) + + ]; + + + } + + result = line.match( boneFooterPattern ); + + if ( result !== null ) { + + if ( n === null || v === null || q === null ) { + + throwError(); + + } + + bones.push( { + + name: n, + translation: v, + quaternion: q + + } ); + + n = null; + v = null; + q = null; + + } + + } + + if ( n !== null || v !== null || q !== null ) { + + throwError(); + + } + + } + + checkMagic(); + parseHeader(); + parseBones(); + + if ( leftToRight === true ) this.leftToRightVpd( vpd ); + + // console.log( vpd ); // for console debug + + return vpd; + +}; + +Parser.prototype.mergeVmds = function ( vmds ) { + + var v = {}; + v.metadata = {}; + v.metadata.name = vmds[ 0 ].metadata.name; + v.metadata.coordinateSystem = vmds[ 0 ].metadata.coordinateSystem; + v.metadata.motionCount = 0; + v.metadata.morphCount = 0; + v.metadata.cameraCount = 0; + v.motions = []; + v.morphs = []; + v.cameras = []; + + for ( var i = 0; i < vmds.length; i++ ) { + + var v2 = vmds[ i ]; + + v.metadata.motionCount += v2.metadata.motionCount; + v.metadata.morphCount += v2.metadata.morphCount; + v.metadata.cameraCount += v2.metadata.cameraCount; + + for ( var j = 0; j < v2.metadata.motionCount; j++ ) { + + v.motions.push( v2.motions[ j ] ); + + } + + for ( var j = 0; j < v2.metadata.morphCount; j++ ) { + + v.morphs.push( v2.morphs[ j ] ); + + } + + for ( var j = 0; j < v2.metadata.cameraCount; j++ ) { + + v.cameras.push( v2.cameras[ j ] ); + + } + + } + + return v; + +}; + +Parser.prototype.leftToRightModel = function ( model ) { + + if ( model.metadata.coordinateSystem === 'right' ) { + + return; + + } + + model.metadata.coordinateSystem = 'right'; + + var helper = new DataCreationHelper(); + + for ( var i = 0; i < model.metadata.vertexCount; i++ ) { + + helper.leftToRightVector3( model.vertices[ i ].position ); + helper.leftToRightVector3( model.vertices[ i ].normal ); + + } + + for ( var i = 0; i < model.metadata.faceCount; i++ ) { + + helper.leftToRightIndexOrder( model.faces[ i ].indices ); + + } + + for ( var i = 0; i < model.metadata.boneCount; i++ ) { + + helper.leftToRightVector3( model.bones[ i ].position ); + + } + + // TODO: support other morph for PMX + for ( var i = 0; i < model.metadata.morphCount; i++ ) { + + var m = model.morphs[ i ]; + + if ( model.metadata.format === 'pmx' && m.type !== 1 ) { + + // TODO: implement + continue; + + } + + for ( var j = 0; j < m.elements.length; j++ ) { + + helper.leftToRightVector3( m.elements[ j ].position ); + + } + + } + + for ( var i = 0; i < model.metadata.rigidBodyCount; i++ ) { + + helper.leftToRightVector3( model.rigidBodies[ i ].position ); + helper.leftToRightEuler( model.rigidBodies[ i ].rotation ); + + } + + for ( var i = 0; i < model.metadata.constraintCount; i++ ) { + + helper.leftToRightVector3( model.constraints[ i ].position ); + helper.leftToRightEuler( model.constraints[ i ].rotation ); + helper.leftToRightVector3Range( model.constraints[ i ].translationLimitation1, model.constraints[ i ].translationLimitation2 ); + helper.leftToRightEulerRange( model.constraints[ i ].rotationLimitation1, model.constraints[ i ].rotationLimitation2 ); + + } + +}; + +Parser.prototype.leftToRightVmd = function ( vmd ) { + + if ( vmd.metadata.coordinateSystem === 'right' ) { + + return; + + } + + vmd.metadata.coordinateSystem = 'right'; + + var helper = new DataCreationHelper(); + + for ( var i = 0; i < vmd.metadata.motionCount; i++ ) { + + helper.leftToRightVector3( vmd.motions[ i ].position ); + helper.leftToRightQuaternion( vmd.motions[ i ].rotation ); + + } + + for ( var i = 0; i < vmd.metadata.cameraCount; i++ ) { + + helper.leftToRightVector3( vmd.cameras[ i ].position ); + helper.leftToRightEuler( vmd.cameras[ i ].rotation ); + + } + +}; + +Parser.prototype.leftToRightVpd = function ( vpd ) { + + if ( vpd.metadata.coordinateSystem === 'right' ) { + + return; + + } + + vpd.metadata.coordinateSystem = 'right'; + + var helper = new DataCreationHelper(); + + for ( var i = 0; i < vpd.bones.length; i++ ) { + + helper.leftToRightVector3( vpd.bones[ i ].translation ); + helper.leftToRightQuaternion( vpd.bones[ i ].quaternion ); + + } + +}; + +var MMDParser = { + CharsetEncoder: CharsetEncoder, + Parser: Parser +}; + +export { MMDParser, CharsetEncoder, Parser }; diff --git a/jsm/libs/motion-controllers.module.js b/jsm/libs/motion-controllers.module.js new file mode 100644 index 0000000..9b2caae --- /dev/null +++ b/jsm/libs/motion-controllers.module.js @@ -0,0 +1,397 @@ +/** + * @webxr-input-profiles/motion-controllers 1.0.0 https://github.com/immersive-web/webxr-input-profiles + */ + +const Constants = { + Handedness: Object.freeze({ + NONE: 'none', + LEFT: 'left', + RIGHT: 'right' + }), + + ComponentState: Object.freeze({ + DEFAULT: 'default', + TOUCHED: 'touched', + PRESSED: 'pressed' + }), + + ComponentProperty: Object.freeze({ + BUTTON: 'button', + X_AXIS: 'xAxis', + Y_AXIS: 'yAxis', + STATE: 'state' + }), + + ComponentType: Object.freeze({ + TRIGGER: 'trigger', + SQUEEZE: 'squeeze', + TOUCHPAD: 'touchpad', + THUMBSTICK: 'thumbstick', + BUTTON: 'button' + }), + + ButtonTouchThreshold: 0.05, + + AxisTouchThreshold: 0.1, + + VisualResponseProperty: Object.freeze({ + TRANSFORM: 'transform', + VISIBILITY: 'visibility' + }) +}; + +/** + * @description Static helper function to fetch a JSON file and turn it into a JS object + * @param {string} path - Path to JSON file to be fetched + */ +async function fetchJsonFile(path) { + const response = await fetch(path); + if (!response.ok) { + throw new Error(response.statusText); + } else { + return response.json(); + } +} + +async function fetchProfilesList(basePath) { + if (!basePath) { + throw new Error('No basePath supplied'); + } + + const profileListFileName = 'profilesList.json'; + const profilesList = await fetchJsonFile(`${basePath}/${profileListFileName}`); + return profilesList; +} + +async function fetchProfile(xrInputSource, basePath, defaultProfile = null, getAssetPath = true) { + if (!xrInputSource) { + throw new Error('No xrInputSource supplied'); + } + + if (!basePath) { + throw new Error('No basePath supplied'); + } + + // Get the list of profiles + const supportedProfilesList = await fetchProfilesList(basePath); + + // Find the relative path to the first requested profile that is recognized + let match; + xrInputSource.profiles.some((profileId) => { + const supportedProfile = supportedProfilesList[profileId]; + if (supportedProfile) { + match = { + profileId, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !!supportedProfile.deprecated + }; + } + return !!match; + }); + + if (!match) { + if (!defaultProfile) { + throw new Error('No matching profile name found'); + } + + const supportedProfile = supportedProfilesList[defaultProfile]; + if (!supportedProfile) { + throw new Error(`No matching profile name found and default profile "${defaultProfile}" missing.`); + } + + match = { + profileId: defaultProfile, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !!supportedProfile.deprecated + }; + } + + const profile = await fetchJsonFile(match.profilePath); + + let assetPath; + if (getAssetPath) { + let layout; + if (xrInputSource.handedness === 'any') { + layout = profile.layouts[Object.keys(profile.layouts)[0]]; + } else { + layout = profile.layouts[xrInputSource.handedness]; + } + if (!layout) { + throw new Error( + `No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}` + ); + } + + if (layout.assetPath) { + assetPath = match.profilePath.replace('profile.json', layout.assetPath); + } + } + + return { profile, assetPath }; +} + +/** @constant {Object} */ +const defaultComponentValues = { + xAxis: 0, + yAxis: 0, + button: 0, + state: Constants.ComponentState.DEFAULT +}; + +/** + * @description Converts an X, Y coordinate from the range -1 to 1 (as reported by the Gamepad + * API) to the range 0 to 1 (for interpolation). Also caps the X, Y values to be bounded within + * a circle. This ensures that thumbsticks are not animated outside the bounds of their physical + * range of motion and touchpads do not report touch locations off their physical bounds. + * @param {number} x The original x coordinate in the range -1 to 1 + * @param {number} y The original y coordinate in the range -1 to 1 + */ +function normalizeAxes(x = 0, y = 0) { + let xAxis = x; + let yAxis = y; + + // Determine if the point is outside the bounds of the circle + // and, if so, place it on the edge of the circle + const hypotenuse = Math.sqrt((x * x) + (y * y)); + if (hypotenuse > 1) { + const theta = Math.atan2(y, x); + xAxis = Math.cos(theta); + yAxis = Math.sin(theta); + } + + // Scale and move the circle so values are in the interpolation range. The circle's origin moves + // from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5. + const result = { + normalizedXAxis: (xAxis * 0.5) + 0.5, + normalizedYAxis: (yAxis * 0.5) + 0.5 + }; + return result; +} + +/** + * Contains the description of how the 3D model should visually respond to a specific user input. + * This is accomplished by initializing the object with the name of a node in the 3D model and + * property that need to be modified in response to user input, the name of the nodes representing + * the allowable range of motion, and the name of the input which triggers the change. In response + * to the named input changing, this object computes the appropriate weighting to use for + * interpolating between the range of motion nodes. + */ +class VisualResponse { + constructor(visualResponseDescription) { + this.componentProperty = visualResponseDescription.componentProperty; + this.states = visualResponseDescription.states; + this.valueNodeName = visualResponseDescription.valueNodeName; + this.valueNodeProperty = visualResponseDescription.valueNodeProperty; + + if (this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM) { + this.minNodeName = visualResponseDescription.minNodeName; + this.maxNodeName = visualResponseDescription.maxNodeName; + } + + // Initializes the response's current value based on default data + this.value = 0; + this.updateFromComponent(defaultComponentValues); + } + + /** + * Computes the visual response's interpolation weight based on component state + * @param {Object} componentValues - The component from which to update + * @param {number} xAxis - The reported X axis value of the component + * @param {number} yAxis - The reported Y axis value of the component + * @param {number} button - The reported value of the component's button + * @param {string} state - The component's active state + */ + updateFromComponent({ + xAxis, yAxis, button, state + }) { + const { normalizedXAxis, normalizedYAxis } = normalizeAxes(xAxis, yAxis); + switch (this.componentProperty) { + case Constants.ComponentProperty.X_AXIS: + this.value = (this.states.includes(state)) ? normalizedXAxis : 0.5; + break; + case Constants.ComponentProperty.Y_AXIS: + this.value = (this.states.includes(state)) ? normalizedYAxis : 0.5; + break; + case Constants.ComponentProperty.BUTTON: + this.value = (this.states.includes(state)) ? button : 0; + break; + case Constants.ComponentProperty.STATE: + if (this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY) { + this.value = (this.states.includes(state)); + } else { + this.value = this.states.includes(state) ? 1.0 : 0.0; + } + break; + default: + throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`); + } + } +} + +class Component { + /** + * @param {Object} componentId - Id of the component + * @param {Object} componentDescription - Description of the component to be created + */ + constructor(componentId, componentDescription) { + if (!componentId + || !componentDescription + || !componentDescription.visualResponses + || !componentDescription.gamepadIndices + || Object.keys(componentDescription.gamepadIndices).length === 0) { + throw new Error('Invalid arguments supplied'); + } + + this.id = componentId; + this.type = componentDescription.type; + this.rootNodeName = componentDescription.rootNodeName; + this.touchPointNodeName = componentDescription.touchPointNodeName; + + // Build all the visual responses for this component + this.visualResponses = {}; + Object.keys(componentDescription.visualResponses).forEach((responseName) => { + const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]); + this.visualResponses[responseName] = visualResponse; + }); + + // Set default values + this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices); + + this.values = { + state: Constants.ComponentState.DEFAULT, + button: (this.gamepadIndices.button !== undefined) ? 0 : undefined, + xAxis: (this.gamepadIndices.xAxis !== undefined) ? 0 : undefined, + yAxis: (this.gamepadIndices.yAxis !== undefined) ? 0 : undefined + }; + } + + get data() { + const data = { id: this.id, ...this.values }; + return data; + } + + /** + * @description Poll for updated data based on current gamepad state + * @param {Object} gamepad - The gamepad object from which the component data should be polled + */ + updateFromGamepad(gamepad) { + // Set the state to default before processing other data sources + this.values.state = Constants.ComponentState.DEFAULT; + + // Get and normalize button + if (this.gamepadIndices.button !== undefined + && gamepad.buttons.length > this.gamepadIndices.button) { + const gamepadButton = gamepad.buttons[this.gamepadIndices.button]; + this.values.button = gamepadButton.value; + this.values.button = (this.values.button < 0) ? 0 : this.values.button; + this.values.button = (this.values.button > 1) ? 1 : this.values.button; + + // Set the state based on the button + if (gamepadButton.pressed || this.values.button === 1) { + this.values.state = Constants.ComponentState.PRESSED; + } else if (gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // Get and normalize x axis value + if (this.gamepadIndices.xAxis !== undefined + && gamepad.axes.length > this.gamepadIndices.xAxis) { + this.values.xAxis = gamepad.axes[this.gamepadIndices.xAxis]; + this.values.xAxis = (this.values.xAxis < -1) ? -1 : this.values.xAxis; + this.values.xAxis = (this.values.xAxis > 1) ? 1 : this.values.xAxis; + + // If the state is still default, check if the xAxis makes it touched + if (this.values.state === Constants.ComponentState.DEFAULT + && Math.abs(this.values.xAxis) > Constants.AxisTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // Get and normalize Y axis value + if (this.gamepadIndices.yAxis !== undefined + && gamepad.axes.length > this.gamepadIndices.yAxis) { + this.values.yAxis = gamepad.axes[this.gamepadIndices.yAxis]; + this.values.yAxis = (this.values.yAxis < -1) ? -1 : this.values.yAxis; + this.values.yAxis = (this.values.yAxis > 1) ? 1 : this.values.yAxis; + + // If the state is still default, check if the yAxis makes it touched + if (this.values.state === Constants.ComponentState.DEFAULT + && Math.abs(this.values.yAxis) > Constants.AxisTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // Update the visual response weights based on the current component data + Object.values(this.visualResponses).forEach((visualResponse) => { + visualResponse.updateFromComponent(this.values); + }); + } +} + +/** + * @description Builds a motion controller with components and visual responses based on the + * supplied profile description. Data is polled from the xrInputSource's gamepad. + * @author Nell Waliczek / https://github.com/NellWaliczek +*/ +class MotionController { + /** + * @param {Object} xrInputSource - The XRInputSource to build the MotionController around + * @param {Object} profile - The best matched profile description for the supplied xrInputSource + * @param {Object} assetUrl + */ + constructor(xrInputSource, profile, assetUrl) { + if (!xrInputSource) { + throw new Error('No xrInputSource supplied'); + } + + if (!profile) { + throw new Error('No profile supplied'); + } + + this.xrInputSource = xrInputSource; + this.assetUrl = assetUrl; + this.id = profile.profileId; + + // Build child components as described in the profile description + this.layoutDescription = profile.layouts[xrInputSource.handedness]; + this.components = {}; + Object.keys(this.layoutDescription.components).forEach((componentId) => { + const componentDescription = this.layoutDescription.components[componentId]; + this.components[componentId] = new Component(componentId, componentDescription); + }); + + // Initialize components based on current gamepad state + this.updateFromGamepad(); + } + + get gripSpace() { + return this.xrInputSource.gripSpace; + } + + get targetRaySpace() { + return this.xrInputSource.targetRaySpace; + } + + /** + * @description Returns a subset of component data for simplified debugging + */ + get data() { + const data = []; + Object.values(this.components).forEach((component) => { + data.push(component.data); + }); + return data; + } + + /** + * @description Poll for updated data based on current gamepad state + */ + updateFromGamepad() { + Object.values(this.components).forEach((component) => { + component.updateFromGamepad(this.xrInputSource.gamepad); + }); + } +} + +export { Constants, MotionController, fetchProfile, fetchProfilesList }; diff --git a/jsm/libs/opentype.module.js b/jsm/libs/opentype.module.js new file mode 100644 index 0000000..7103287 --- /dev/null +++ b/jsm/libs/opentype.module.js @@ -0,0 +1,14568 @@ +/** + * https://opentype.js.org v1.3.4 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens + */ + +/*! https://mths.be/codepointat v0.2.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} + +var TINF_OK = 0; +var TINF_DATA_ERROR = -3; + +function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ +} + +function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ +} + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +var sltree = new Tree(); +var sdtree = new Tree(); + +/* extra bits and base tables for length codes */ +var length_bits = new Uint8Array(30); +var length_base = new Uint16Array(30); + +/* extra bits and base tables for distance codes */ +var dist_bits = new Uint8Array(30); +var dist_base = new Uint16Array(30); + +/* special ordering of code length codes */ +var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +]); + +/* used by tinf_decode_trees, avoids allocations every call */ +var code_tree = new Tree(); +var lengths = new Uint8Array(288 + 32); + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* build extra bits and base tables */ +function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { bits[i] = 0; } + for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} + +/* build the fixed huffman trees */ +function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { lt.table[i] = 0; } + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } + for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } + for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } + for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { dt.table[i] = 0; } + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) { dt.trans[i] = i; } +} + +/* given an array of code lengths, build a tree */ +var offs = new Uint16Array(16); + +function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) { t.table[i] = 0; } + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +function tinf_read_bits(d, num, base) { + if (!num) + { return base; } + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) { lengths[i] = 0; } + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } +} + +/* inflate an uncompressed block of data */ +function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + { return TINF_DATA_ERROR; } + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; +} + +/* inflate stream from source to dest */ +function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + { throw new Error('Data error'); } + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + { return d.dest.slice(0, d.destLen); } + else + { return d.dest.subarray(0, d.destLen); } + } + + return d.dest; +} + +/* -------------------- * + * -- initialization -- * + * -------------------- */ + +/* build fixed huffman trees */ +tinf_build_fixed_trees(sltree, sdtree); + +/* build extra bits and base tables */ +tinf_build_bits_base(length_bits, length_base, 4, 3); +tinf_build_bits_base(dist_bits, dist_base, 2, 1); + +/* fix a special case */ +length_bits[28] = 0; +length_base[28] = 258; + +var tinyInflate = tinf_uncompress; + +// The Bounding Box object + +function derive(v0, v1, v2, v3, t) { + return Math.pow(1 - t, 3) * v0 + + 3 * Math.pow(1 - t, 2) * t * v1 + + 3 * (1 - t) * Math.pow(t, 2) * v2 + + Math.pow(t, 3) * v3; +} +/** + * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. + * It is used to calculate the bounding box of a glyph or text path. + * + * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. + * + * @exports opentype.BoundingBox + * @class + * @constructor + */ +function BoundingBox() { + this.x1 = Number.NaN; + this.y1 = Number.NaN; + this.x2 = Number.NaN; + this.y2 = Number.NaN; +} + +/** + * Returns true if the bounding box is empty, that is, no points have been added to the box yet. + */ +BoundingBox.prototype.isEmpty = function() { + return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); +}; + +/** + * Add the point to the bounding box. + * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. + * @param {number} x - The X coordinate of the point. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addPoint = function(x, y) { + if (typeof x === 'number') { + if (isNaN(this.x1) || isNaN(this.x2)) { + this.x1 = x; + this.x2 = x; + } + if (x < this.x1) { + this.x1 = x; + } + if (x > this.x2) { + this.x2 = x; + } + } + if (typeof y === 'number') { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) { + this.y1 = y; + } + if (y > this.y2) { + this.y2 = y; + } + } +}; + +/** + * Add a X coordinate to the bounding box. + * This extends the bounding box to include the X coordinate. + * This function is used internally inside of addBezier. + * @param {number} x - The X coordinate of the point. + */ +BoundingBox.prototype.addX = function(x) { + this.addPoint(x, null); +}; + +/** + * Add a Y coordinate to the bounding box. + * This extends the bounding box to include the Y coordinate. + * This function is used internally inside of addBezier. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addY = function(y) { + this.addPoint(null, y); +}; + +/** + * Add a Bézier curve to the bounding box. + * This extends the bounding box to include the entire Bézier. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the first control point. + * @param {number} y1 - The Y coordinate of the first control point. + * @param {number} x2 - The X coordinate of the second control point. + * @param {number} y2 - The Y coordinate of the second control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { + // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html + // and https://github.com/icons8/svg-path-bounding-box + + var p0 = [x0, y0]; + var p1 = [x1, y1]; + var p2 = [x2, y2]; + var p3 = [x, y]; + + this.addPoint(x0, y0); + this.addPoint(x, y); + + for (var i = 0; i <= 1; i++) { + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; + + if (a === 0) { + if (b === 0) { continue; } + var t = -c / b; + if (0 < t && t < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } + } + continue; + } + + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) { continue; } + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + } + } +}; + +/** + * Add a quadratic curve to the bounding box. + * This extends the bounding box to include the entire quadratic curve. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the control point. + * @param {number} y1 - The Y coordinate of the control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { + var cp1x = x0 + 2 / 3 * (x1 - x0); + var cp1y = y0 + 2 / 3 * (y1 - y0); + var cp2x = cp1x + 1 / 3 * (x - x0); + var cp2y = cp1y + 1 / 3 * (y - y0); + this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); +}; + +// Geometric objects + +/** + * A bézier path containing a set of path commands similar to a SVG path. + * Paths can be drawn on a context using `draw`. + * @exports opentype.Path + * @class + * @constructor + */ +function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; +} + +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); +}; + +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); +}; + +/** + * Draws cubic curve + * @function + * curveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + +/** + * Draws cubic curve + * @function + * bezierCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + * @see curveTo + */ +Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); +}; + +/** + * Draws quadratic curve + * @function + * quadraticCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + +/** + * Draws quadratic curve + * @function + * quadTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ +Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); +}; + +/** + * Closes the path + * @function closePath + * @memberof opentype.Path.prototype + */ + +/** + * Close the path + * @function close + * @memberof opentype.Path.prototype + */ +Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); +}; + +/** + * Add the given path or list of commands to the commands of this path. + * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. + */ +Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } else if (pathOrCommands instanceof BoundingBox) { + var box = pathOrCommands; + this.moveTo(box.x1, box.y1); + this.lineTo(box.x2, box.y1); + this.lineTo(box.x2, box.y2); + this.lineTo(box.x1, box.y2); + this.close(); + return; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); +}; + +/** + * Calculate the bounding box of the path. + * @returns {opentype.BoundingBox} + */ +Path.prototype.getBoundingBox = function() { + var box = new BoundingBox(); + + var startX = 0; + var startY = 0; + var prevX = 0; + var prevY = 0; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + switch (cmd.type) { + case 'M': + box.addPoint(cmd.x, cmd.y); + startX = prevX = cmd.x; + startY = prevY = cmd.y; + break; + case 'L': + box.addPoint(cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Q': + box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'C': + box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Z': + prevX = startX; + prevY = startY; + break; + default: + throw new Error('Unexpected path command ' + cmd.type); + } + } + if (box.isEmpty()) { + box.addPoint(0, 0); + } + return box; +}; + +/** + * Draw the path to a 2D context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. + */ +Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } +}; + +/** + * Convert the Path to a string of path data instructions + * See http://www.w3.org/TR/SVG/paths.html#PathData + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } + + function packValues() { + var arguments$1 = arguments; + + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments$1[i]; + if (v >= 0 && i > 0) { + s += ' '; + } + + s += floatToString(v); + } + + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } + + return d; +}; + +/** + * Convert the path to an SVG element, as a string. + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toSVG = function(decimalPlaces) { + var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; +}; +/** + * @constant + * @type {number} + */ +sizeOf.BYTE = constant(1); + +/** + * Convert a 8-bit signed integer to a list of 1 byte. + * @param {string} + * @returns {Array} + */ +encode.CHAR = function(v) { + return [v.charCodeAt(0)]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.CHAR = constant(1); + +/** + * Convert an ASCII string to a list of bytes. + * @param {string} + * @returns {Array} + */ +encode.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + v = ''; + console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.'); + } + var b = []; + for (var i = 0; i < v.length; i += 1) { + b[i] = v.charCodeAt(i); + } + + return b; +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + return 0; + } + return v.length; +}; + +/** + * Convert a 16-bit unsigned integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.USHORT = constant(2); + +/** + * Convert a 16-bit signed integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } + + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.SHORT = constant(2); + +/** + * Convert a 24-bit unsigned integer to a list of 3 bytes. + * @param {number} + * @returns {Array} + */ +encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.UINT24 = constant(3); + +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.ULONG = constant(4); + +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); + } + + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.LONG = constant(4); + +encode.FIXED = encode.ULONG; +sizeOf.FIXED = sizeOf.ULONG; + +encode.FWORD = encode.SHORT; +sizeOf.FWORD = sizeOf.SHORT; + +encode.UFWORD = encode.USHORT; +sizeOf.UFWORD = sizeOf.USHORT; + +/** + * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. + * @param {number} + * @returns {Array} + */ +encode.LONGDATETIME = function(v) { + return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.LONGDATETIME = constant(8); + +/** + * Convert a 4-char tag to a list of 4 bytes. + * @param {string} + * @returns {Array} + */ +encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.TAG = constant(4); + +// CFF data types /////////////////////////////////////////////////////////// + +encode.Card8 = encode.BYTE; +sizeOf.Card8 = sizeOf.BYTE; + +encode.Card16 = encode.USHORT; +sizeOf.Card16 = sizeOf.USHORT; + +encode.OffSize = encode.BYTE; +sizeOf.OffSize = sizeOf.BYTE; + +encode.SID = encode.USHORT; +sizeOf.SID = sizeOf.USHORT; + +// Convert a numeric operand or charstring number to a variable-size list of bytes. +/** + * Convert a numeric operand or charstring number to a variable-size list of bytes. + * @param {number} + * @returns {Array} + */ +encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } +}; + +/** + * @param {number} + * @returns {number} + */ +sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; +}; + +/** + * Convert a signed number between -32768 and +32767 to a three-byte value. + * This ensures we always use three bytes, but is not the most compact format. + * @param {number} + * @returns {Array} + */ +encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER16 = constant(3); + +/** + * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. + * This is useful if you want to be sure you always use four bytes, + * at the expense of wasting a few bytes for smaller numbers. + * @param {number} + * @returns {Array} + */ +encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER32 = constant(5); + +/** + * @param {number} + * @returns {Array} + */ +encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + for (var i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } + + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { + out.push(parseInt(nibbles.substr(i$1, 2), 16)); + } + + return out; +}; + +/** + * @param {number} + * @returns {number} + */ +sizeOf.REAL = function(v) { + return encode.REAL(v).length; +}; + +encode.NAME = encode.CHARARRAY; +sizeOf.NAME = sizeOf.CHARARRAY; + +encode.STRING = encode.CHARARRAY; +sizeOf.STRING = sizeOf.CHARARRAY; + +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } + + return String.fromCharCode.apply(null, codePoints); +}; + +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } + + return String.fromCharCode.apply(null, codePoints); +}; + +/** + * Convert a JavaScript string to UTF16-BE. + * @param {string} + * @returns {Array} + */ +encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + var codepoint = v.charCodeAt(i); + b[b.length] = (codepoint >> 8) & 0xFF; + b[b.length] = codepoint & 0xFF; + } + + return b; +}; + +/** + * @param {string} + * @returns {number} + */ +sizeOf.UTF16 = function(v) { + return v.length * 2; +}; + +// Data for converting old eight-bit Macintosh encodings to Unicode. +// This representation is optimized for decoding; encoding is slower +// and needs more memory. The assumption is that all opentype.js users +// want to open fonts, but saving a font will be comparatively rare +// so it can be more expensive. Keyed by IANA character set name. +// +// Python script for generating these strings: +// +// s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) +// print(s.encode('utf-8')) +/** + * @private + */ +var eightBitMacEncodings = { + 'x-mac-croatian': // Python: 'mac_croatian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + + '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + 'x-mac-cyrillic': // Python: 'mac_cyrillic' + 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'x-mac-greek': // Python: 'mac_greek' + 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'x-mac-icelandic': // Python: 'mac_iceland' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'x-mac-ce': // Python: 'mac_latin2' + 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + macintosh: // Python: 'mac_roman' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-romanian': // Python: 'mac_romanian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-turkish': // Python: 'mac_turkish' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' +}; + +/** + * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript + * string, or 'undefined' if the encoding is unsupported. For example, we do + * not support Chinese, Japanese or Korean because these would need large + * mapping tables. + * @param {DataView} dataView + * @param {number} offset + * @param {number} dataLength + * @param {string} encoding + * @returns {string} + */ +decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === undefined) { + return undefined; + } + + var result = ''; + for (var i = 0; i < dataLength; i++) { + var c = dataView.getUint8(offset + i); + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c <= 0x7F) { + result += String.fromCharCode(c); + } else { + result += table[c & 0x7F]; + } + } + + return result; +}; + +// Helper function for encode.MACSTRING. Returns a dictionary for mapping +// Unicode character codes to their 8-bit MacOS equivalent. This table +// is not exactly a super cheap data structure, but we do not care because +// encoding Macintosh strings is only rarely needed in typical applications. +var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); +var macEncodingCacheKeys; +var getMacEncodingTable = function (encoding) { + // Since we use encoding as a cache key for WeakMap, it has to be + // a String object and not a literal. And at least on NodeJS 2.10.1, + // WeakMap requires that the same String instance is passed for cache hits. + if (!macEncodingCacheKeys) { + macEncodingCacheKeys = {}; + for (var e in eightBitMacEncodings) { + /*jshint -W053 */ // Suppress "Do not use String as a constructor." + macEncodingCacheKeys[e] = new String(e); + } + } + + var cacheKey = macEncodingCacheKeys[encoding]; + if (cacheKey === undefined) { + return undefined; + } + + // We can't do "if (cache.has(key)) {return cache.get(key)}" here: + // since garbage collection may run at any time, it could also kick in + // between the calls to cache.has() and cache.get(). In that case, + // we would return 'undefined' even though we do support the encoding. + if (macEncodingTableCache) { + var cachedTable = macEncodingTableCache.get(cacheKey); + if (cachedTable !== undefined) { + return cachedTable; + } + } + + var decodingTable = eightBitMacEncodings[encoding]; + if (decodingTable === undefined) { + return undefined; + } + + var encodingTable = {}; + for (var i = 0; i < decodingTable.length; i++) { + encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; + } + + if (macEncodingTableCache) { + macEncodingTableCache.set(cacheKey, encodingTable); + } + + return encodingTable; +}; + +/** + * Encodes an old-style Macintosh string. Returns a byte array upon success. + * If the requested encoding is unsupported, or if the input string contains + * a character that cannot be expressed in the encoding, the function returns + * 'undefined'. + * @param {string} str + * @param {string} encoding + * @returns {Array} + */ +encode.MACSTRING = function(str, encoding) { + var table = getMacEncodingTable(encoding); + if (table === undefined) { + return undefined; + } + + var result = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c >= 0x80) { + c = table[c]; + if (c === undefined) { + // str contains a Unicode character that cannot be encoded + // in the requested encoding. + return undefined; + } + } + result[i] = c; + // result.push(c); + } + + return result; +}; + +/** + * @param {string} str + * @param {string} encoding + * @returns {number} + */ +sizeOf.MACSTRING = function(str, encoding) { + var b = encode.MACSTRING(str, encoding); + if (b !== undefined) { + return b.length; + } else { + return 0; + } +}; + +// Helper for encode.VARDELTAS +function isByteEncodable(value) { + return value >= -128 && value <= 127; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsZeroes(deltas, pos, result) { + var runLength = 0; + var numDeltas = deltas.length; + while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { + ++pos; + ++runLength; + } + result.push(0x80 | (runLength - 1)); + return pos; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsBytes(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + if (!isByteEncodable(value)) { + break; + } + + // Within a byte-encoded run of deltas, a single zero is best + // stored literally as 0x00 value. However, if we have two or + // more zeroes in a sequence, it is better to start a new run. + // Fore example, the sequence of deltas [15, 15, 0, 15, 15] + // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero + // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) + // when starting a new run. + if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { + break; + } + + ++pos; + ++runLength; + } + result.push(runLength - 1); + for (var i = offset; i < pos; ++i) { + result.push((deltas[i] + 256) & 0xff); + } + return pos; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsWords(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + + // Within a word-encoded run of deltas, it is easiest to start + // a new run (with a different encoding) whenever we encounter + // a zero value. For example, the sequence [0x6666, 0, 0x7777] + // needs 7 bytes when storing the zero inside the current run + // (42 66 66 00 00 77 77), and equally 7 bytes when starting a + // new run (40 66 66 80 40 77 77). + if (value === 0) { + break; + } + + // Within a word-encoded run of deltas, a single value in the + // range (-128..127) should be encoded within the current run + // because it is more compact. For example, the sequence + // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value + // literally (42 66 66 00 02 77 77), but 8 bytes when starting + // a new run (40 66 66 00 02 40 77 77). + if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { + break; + } + + ++pos; + ++runLength; + } + result.push(0x40 | (runLength - 1)); + for (var i = offset; i < pos; ++i) { + var val = deltas[i]; + result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); + } + return pos; +} + +/** + * Encode a list of variation adjustment deltas. + * + * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. + * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted + * when generating instances of variation fonts. + * + * @see https://www.microsoft.com/typography/otspec/gvar.htm + * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html + * @param {Array} + * @return {Array} + */ +encode.VARDELTAS = function(deltas) { + var pos = 0; + var result = []; + while (pos < deltas.length) { + var value = deltas[pos]; + if (value === 0) { + pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); + } else if (value >= -128 && value <= 127) { + pos = encodeVarDeltaRunAsBytes(deltas, pos, result); + } else { + pos = encodeVarDeltaRunAsWords(deltas, pos, result); + } + } + return result; +}; + +// Convert a list of values to a CFF INDEX structure. +// The values should be objects containing name / type / value. +/** + * @param {Array} l + * @returns {Array} + */ +encode.INDEX = function(l) { + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offsets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + for (var i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { + var encodedOffset = offsetEncoder(offsets[i$1]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; +}; + +/** + * Convert an object to a CFF DICT structure. + * The keys should be numeric. + * The values should be objects containing name / type / value. + * @param {Object} m + * @returns {Array} + */ +encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; + + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } + + return d; +}; + +/** + * @param {Object} + * @returns {number} + */ +sizeOf.DICT = function(m) { + return encode.DICT(m).length; +}; + +/** + * @param {number} + * @returns {Array} + */ +encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } +}; + +/** + * @param {Array} v + * @param {string} + * @returns {Array} + */ +encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } + + return d; +}; + +encode.OP = encode.BYTE; +sizeOf.OP = sizeOf.BYTE; + +// memoize charstring encoding using WeakMap if available +var wmm = typeof WeakMap === 'function' && new WeakMap(); + +/** + * Convert a list of CharString operations to bytes. + * @param {Array} + * @returns {Array} + */ +encode.CHARSTRING = function(ops) { + // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". + if (wmm) { + var cachedValue = wmm.get(ops); + if (cachedValue !== undefined) { + return cachedValue; + } + } + + var d = []; + var length = ops.length; + + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } + + if (wmm) { + wmm.set(ops, d); + } + + return d; +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; +}; + +// Utility functions //////////////////////////////////////////////////////// + +/** + * Convert an object containing name / type / value to bytes. + * @param {Object} + * @returns {Array} + */ +encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); +}; + +/** + * @param {Object} + * @returns {number} + */ +sizeOf.OBJECT = function(v) { + var sizeOfFunction = sizeOf[v.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); + return sizeOfFunction(v.value); +}; + +/** + * Convert a table object to bytes. + * A table contains a list of fields containing the metadata (name, type and default value). + * The table itself has the field values set as attributes. + * @param {opentype.Table} + * @returns {Array} + */ +encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + var subtables = []; + var subtableOffsets = []; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + + if (field.type === 'TABLE') { + subtableOffsets.push(d.length); + d = d.concat([0, 0]); + subtables.push(bytes); + } else { + d = d.concat(bytes); + } + } + + for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { + var o = subtableOffsets[i$1]; + var offset = d.length; + check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); + d[o] = offset >> 8; + d[o + 1] = offset & 0xff; + d = d.concat(subtables[i$1]); + } + + return d; +}; + +/** + * @param {opentype.Table} + * @returns {number} + */ +sizeOf.TABLE = function(table) { + var numBytes = 0; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var sizeOfFunction = sizeOf[field.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + numBytes += sizeOfFunction(value); + + // Subtables take 2 more bytes for offsets. + if (field.type === 'TABLE') { + numBytes += 2; + } + } + + return numBytes; +}; + +encode.RECORD = encode.TABLE; +sizeOf.RECORD = sizeOf.TABLE; + +// Merge in a list of bytes. +encode.LITERAL = function(v) { + return v; +}; + +sizeOf.LITERAL = function(v) { + return v.length; +}; + +// Table metadata + +/** + * @exports opentype.Table + * @class + * @param {string} tableName + * @param {Array} fields + * @param {Object} options + * @constructor + */ +function Table(tableName, fields, options) { + // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, + // as this will result in wrong encoding order of the coverage data on serialization to bytes. + // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. + if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { + for (var i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; + } + } + + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { + var k = optionKeys[i$1]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } +} + +/** + * Encodes the table and returns an array of bytes + * @return {Array} + */ +Table.prototype.encode = function() { + return encode.TABLE(this); +}; + +/** + * Get the size of the table. + * @return {number} + */ +Table.prototype.sizeOf = function() { + return sizeOf.TABLE(this); +}; + +/** + * @private + */ +function ushortList(itemName, list, count) { + if (count === undefined) { + count = list.length; + } + var fields = new Array(list.length + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < list.length; i++) { + fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; + } + return fields; +} + +/** + * @private + */ +function tableList(itemName, records, itemCallback) { + var count = records.length; + var fields = new Array(count + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; + } + return fields; +} + +/** + * @private + */ +function recordList(itemName, records, itemCallback) { + var count = records.length; + var fields = []; + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields = fields.concat(itemCallback(records[i], i)); + } + return fields; +} + +// Common Layout Tables + +/** + * @exports opentype.Coverage + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function Coverage(coverageTable) { + if (coverageTable.format === 1) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 1}] + .concat(ushortList('glyph', coverageTable.glyphs)) + ); + } else if (coverageTable.format === 2) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 2}] + .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { + return [ + {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, + {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, + {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ]; + })) + ); + } else { + check.assert(false, 'Coverage format must be 1 or 2.'); + } +} +Coverage.prototype = Object.create(Table.prototype); +Coverage.prototype.constructor = Coverage; + +function ScriptList(scriptListTable) { + Table.call(this, 'scriptListTable', + recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { + var script = scriptRecord.script; + var defaultLangSys = script.defaultLangSys; + check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); + return [ + {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, + {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ + {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] + .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} + ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { + var langSys = langSysRecord.langSys; + return [ + {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, + {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} + ].concat(ushortList('featureIndex', langSys.featureIndexes)))} + ]; + })))} + ]; + }) + ); +} +ScriptList.prototype = Object.create(Table.prototype); +ScriptList.prototype.constructor = ScriptList; + +/** + * @exports opentype.FeatureList + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function FeatureList(featureListTable) { + Table.call(this, 'featureListTable', + recordList('featureRecord', featureListTable, function(featureRecord, i) { + var feature = featureRecord.feature; + return [ + {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, + {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ + {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} + ]; + }) + ); +} +FeatureList.prototype = Object.create(Table.prototype); +FeatureList.prototype.constructor = FeatureList; + +/** + * @exports opentype.LookupList + * @class + * @param {opentype.Table} + * @param {Object} + * @constructor + * @extends opentype.Table + */ +function LookupList(lookupListTable, subtableMakers) { + Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { + var subtableCallback = subtableMakers[lookupTable.lookupType]; + check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); + return new Table('lookupTable', [ + {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} + ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); + })); +} +LookupList.prototype = Object.create(Table.prototype); +LookupList.prototype.constructor = LookupList; + +// Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) +// Don't use offsets inside Records (probable bug), only in Tables. +var table = { + Table: Table, + Record: Table, + Coverage: Coverage, + ScriptList: ScriptList, + FeatureList: FeatureList, + LookupList: LookupList, + ushortList: ushortList, + tableList: tableList, + recordList: recordList, +}; + +// Parsing utility functions + +// Retrieve an unsigned byte from the DataView. +function getByte(dataView, offset) { + return dataView.getUint8(offset); +} + +// Retrieve an unsigned 16-bit short from the DataView. +// The value is stored in big endian. +function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); +} + +// Retrieve a signed 16-bit short from the DataView. +// The value is stored in big endian. +function getShort(dataView, offset) { + return dataView.getInt16(offset, false); +} + +// Retrieve an unsigned 32-bit long from the DataView. +// The value is stored in big endian. +function getULong(dataView, offset) { + return dataView.getUint32(offset, false); +} + +// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. +// The value is stored in big endian. +function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; +} + +// Retrieve a 4-character tag from the DataView. +// Tags are used to identify tables. +function getTag(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; +} + +// Retrieve an offset from the DataView. +// Offsets are 1 to 4 bytes in length, depending on the offSize argument. +function getOffset(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; +} + +// Retrieve a number of bytes from start offset to the end offset from the DataView. +function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; +} + +// Convert the list of bytes to a string. +function bytesToString(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; +} + +var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 +}; + +// A stateful parser that changes the offset whenever a value is retrieved. +// The data is a DataView. +function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; +} + +Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseCard8 = Parser.prototype.parseByte; + +Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseCard16 = Parser.prototype.parseUShort; +Parser.prototype.parseSID = Parser.prototype.parseUShort; +Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + +Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseULong = function() { + var v = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + +Parser.prototype.parseFixed = function() { + var v = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); + } + + return string; +}; + +Parser.prototype.parseTag = function() { + return this.parseString(4); +}; + +// LONGDATETIME is a 64-bit integer. +// JavaScript and unix timestamps traditionally use 32 bits, so we +// only take the last 32 bits. +// + Since until 2038 those bits will be filled by zeros we can ignore them. +Parser.prototype.parseLongDateTime = function() { + var v = getULong(this.data, this.offset + this.relativeOffset + 4); + // Subtract seconds between 01/01/1904 and 01/01/1970 + // to convert Apple Mac timestamp to Standard Unix timestamp + v -= 2082844800; + this.relativeOffset += 8; + return v; +}; + +Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // Default returns the correct number if minor = 0xN000 where N is 0-9 + // Set minorBase to 1 for tables that use minor = N where N is 0-9 + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === undefined) { minorBase = 0x1000; } + return major + minor / minorBase / 10; +}; + +Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; + } + + this.relativeOffset += typeOffsets[type] * amount; +}; + +///// Parsing lists and records /////////////////////////////// + +// Parse a list of 32 bit unsigned integers. +Parser.prototype.parseULongList = function(count) { + if (count === undefined) { count = this.parseULong(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint32(offset); + offset += 4; + } + + this.relativeOffset += count * 4; + return offsets; +}; + +// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream +// or provided as an argument. +Parser.prototype.parseOffset16List = +Parser.prototype.parseUShortList = function(count) { + if (count === undefined) { count = this.parseUShort(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; +}; + +// Parses a list of 16 bit signed integers. +Parser.prototype.parseShortList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getInt16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return list; +}; + +// Parses a list of bytes. +Parser.prototype.parseByteList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getUint8(offset++); + } + + this.relativeOffset += count; + return list; +}; + +/** + * Parse a list of items. + * Record count is optional, if omitted it is read from the stream. + * itemCallback is one of the Parser methods. + */ +Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; + +Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; + +/** + * Parse a list of records. + * Record count is optional, if omitted it is read from the stream. + * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + */ +Parser.prototype.parseRecordList = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; +}; + +Parser.prototype.parseRecordList32 = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; +}; + +// Parse a data structure into an object +// Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } +Parser.prototype.parseStruct = function(description) { + if (typeof description === 'function') { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; + } +}; + +/** + * Parse a GPOS valueRecord + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat is optional, if omitted it is read from the stream. + */ +Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === undefined) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + // valueFormat2 in kerning pairs is most often 0 + // in this case return undefined instead of an empty object, to save space + return; + } + var valueRecord = {}; + + if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } + if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } + if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } + if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } + + // Device table (non-variable font) / VariationIndex table (variable font) not supported + // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls + if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } + + return valueRecord; +}; + +/** + * Parse a list of GPOS valueRecords + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat and valueCount are read from the stream. + */ +Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i = 0; i < valueCount; i++) { + values[i] = this.parseValueRecord(valueFormat); + } + return values; +}; + +Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; + +Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; + +/** + * Parse a list of offsets to lists of 16-bit integers, + * or a list of offsets to lists of offsets to any kind of items. + * If itemCallback is not provided, a list of list of UShort is assumed. + * If provided, itemCallback is called on each item and must parse the item. + * See examples in tables/gsub.js + */ +Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list = new Array(count); + for (var i = 0; i < count; i++) { + var start = offsets[i]; + if (start === 0) { + // NULL offset + // Add i as owned property to list. Convenient with assert. + list[i] = undefined; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list[i] = subList; + } else { + list[i] = this.parseUShortList(); + } + } + this.relativeOffset = relativeOffset; + return list; +}; + +///// Complex tables parsing ////////////////////////////////// + +// Parse a coverage table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +// parser.offset must point to the start of the table containing the coverage. +Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i = 0; i < count; i++) { + ranges[i] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; + } + return { + format: 2, + ranges: ranges + }; + } + throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); +}; + +// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); +}; + +///// Static methods /////////////////////////////////// +// These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. + +Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; +}; + +Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); + }; +}; + +Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); + }; +}; + +Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; +}; + +Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); + }; +}; + +Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); + }; +}; + +Parser.tag = Parser.prototype.parseTag; +Parser.byte = Parser.prototype.parseByte; +Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; +Parser.uShortList = Parser.prototype.parseUShortList; +Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; +Parser.uLongList = Parser.prototype.parseULongList; +Parser.struct = Parser.prototype.parseStruct; +Parser.coverage = Parser.prototype.parseCoverage; +Parser.classDef = Parser.prototype.parseClassDef; + +///// Script, Feature, Lookup lists /////////////////////////////////////////////// +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm + +var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList +}; + +Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; +}; + +Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; +}; + +Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + return { + lookupType: lookupType, + lookupFlag: lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined + }; + }))) || []; +}; + +Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; +}; + +var parse = { + getByte: getByte, + getCard8: getByte, + getUShort: getUShort, + getCard16: getUShort, + getShort: getShort, + getULong: getULong, + getFixed: getFixed, + getTag: getTag, + getOffset: getOffset, + getBytes: getBytes, + bytesToString: bytesToString, + Parser: Parser, +}; + +// The `cmap` table stores the mappings from characters to glyphs. + +function parseCmapTableFormat12(cmap, p) { + //Skip reserved. + p.parseUShort(); + + // Length in bytes of the sub-tables. + cmap.length = p.parseULong(); + cmap.language = p.parseULong(); + + var groupCount; + cmap.groupCount = groupCount = p.parseULong(); + cmap.glyphIndexMap = {}; + + for (var i = 0; i < groupCount; i += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); + + for (var c = startCharCode; c <= endCharCode; c += 1) { + cmap.glyphIndexMap[c] = startGlyphId; + startGlyphId++; + } + } +} + +function parseCmapTableFormat4(cmap, p, data, start, offset) { + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i = 0; i < segCount - 1; i += 1) { + var glyphIndex = (void 0); + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; + } + + cmap.glyphIndexMap[c] = glyphIndex; + } + } +} + +// Parse the `cmap` table. This table stores the mappings from characters to glyphs. +// There are many available formats, but we only support the Windows format 4 and 12. +// This function returns a `CmapEncoding` object or null if no supported format could be found. +function parseCmapTable(data, start) { + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i = cmap.numTables - 1; i >= 0; i -= 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || + (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } + } + + if (offset === -1) { + // There is no cmap table in the font that we support. + throw new Error('No valid cmap sub-tables found.'); + } + + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); + + if (cmap.format === 12) { + parseCmapTableFormat12(cmap, p); + } else if (cmap.format === 4) { + parseCmapTableFormat4(cmap, p, data, start, offset); + } else { + throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); + } + + return cmap; +} + +function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0, + glyphIndex: glyphIndex + }); +} + +function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); +} + +// Make cmap table, format 4 by default, 12 if needed only +function makeCmapTable(glyphs) { + // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) + var isPlan0Only = true; + var i; + + // Check if we need to add cmap format 12 or if format 4 only is fine + for (i = glyphs.length - 1; i > 0; i -= 1) { + var g = glyphs.get(i); + if (g.unicode > 65535) { + console.log('Adding CMAP format 12 (needed!)'); + isPlan0Only = false; + break; + } + } + + var cmapTable = [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, + + // CMAP 4 header + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} + ]; + + if (!isPlan0Only) + { cmapTable = cmapTable.concat([ + // CMAP 12 header + {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere + {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, + {name: 'cmap12Offset', type: 'ULONG', value: 0} + ]); } + + cmapTable = cmapTable.concat([ + // CMAP 4 Subtable + {name: 'format', type: 'USHORT', value: 4}, + {name: 'cmap4Length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + + var t = new table.Table('cmap', cmapTable); + + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function (a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount = t.segments.length; + var segCountToRemove = 0; + + // CMAP 4 + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + // CMAP 12 + var cmap12Groups = []; + + // Reminder this loop is not following the specification at 100% + // The specification -> find suites of characters and make a group + // Here we're doing one group for each letter + // Doing as the spec can save 8 times (or more) space + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + + // CMAP 4 + if (segment.end <= 65535 && segment.start <= 65535) { + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } else { + // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 + segCountToRemove += 1; + } + + // CMAP 12 + // Skip Terminator Segment + if (!isPlan0Only && segment.glyphIndex !== undefined) { + cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); + } + } + + // CMAP 4 Subtable + t.segCountX2 = (segCount - segCountToRemove) * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.cmap4Length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + if (!isPlan0Only) { + // CMAP 12 Subtable + var cmap12Length = 16 + // Subtable header + cmap12Groups.length * 4; + + t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; + t.fields = t.fields.concat([ + {name: 'cmap12Format', type: 'USHORT', value: 12}, + {name: 'cmap12Reserved', type: 'USHORT', value: 0}, + {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, + {name: 'cmap12Language', type: 'ULONG', value: 0}, + {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} + ]); + + t.fields = t.fields.concat(cmap12Groups); + } + + return t; +} + +var cmap = { parse: parseCmapTable, make: makeCmapTable }; + +// Glyph encoding + +var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + +var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + +var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + +var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + +/** + * This is the encoding used for fonts created from scratch. + * It loops through all glyphs and finds the appropriate unicode value. + * Since it's linear time, other encodings will be faster. + * @exports opentype.DefaultEncoding + * @class + * @constructor + * @param {opentype.Font} + */ +function DefaultEncoding(font) { + this.font = font; +} + +DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; + } + } + } + } + return null; +}; + +/** + * @exports opentype.CmapEncoding + * @class + * @constructor + * @param {Object} cmap - a object with the cmap encoded data + */ +function CmapEncoding(cmap) { + this.cmap = cmap; +} + +/** + * @param {string} c - the character + * @return {number} The glyph index. + */ +CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; +}; + +/** + * @exports opentype.CffEncoding + * @class + * @constructor + * @param {string} encoding - The encoding + * @param {Array} charset - The character set. + */ +function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; +} + +/** + * @param {string} s - The character + * @return {number} The index. + */ +CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); +}; + +/** + * @exports opentype.GlyphNames + * @class + * @constructor + * @param {Object} post + */ +function GlyphNames(post) { + switch (post.version) { + case 1: + this.names = standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < standardNames.length) { + this.names[i] = standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + } + + break; + case 3: + this.names = []; + break; + default: + this.names = []; + break; + } +} + +/** + * Gets the index of a glyph by name. + * @param {string} name - The glyph name + * @return {number} The index + */ +GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); +}; + +/** + * @param {number} gid + * @return {string} + */ +GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; +}; + +function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); + } + + for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { + glyph = font.glyphs.get(i$1); + if (font.cffEncoding) { + if (font.isCIDFont) { + glyph.name = 'gid' + i$1; + } else { + glyph.name = font.cffEncoding.charset[i$1]; + } + } else if (font.glyphNames.names) { + glyph.name = font.glyphNames.glyphIndexToName(i$1); + } + } +} + +function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; + + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + if (font._IndexToUnicodeMap[glyphIndex] === undefined) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c)] + }; + } else { + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); + } + } +} + +/** + * @alias opentype.addGlyphNames + * @param {opentype.Font} + * @param {Object} + */ +function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); + } +} + +// Drawing utility functions. + +// Draw a line on the given context from point `x1,y1` to point `x2,y2`. +function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +} + +var draw = { line: line }; + +// The Glyph object +// import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency + +function getPathDefinition(glyph, path) { + var _path = path || new Path(); + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } + }; +} +/** + * @typedef GlyphOptions + * @type Object + * @property {string} [name] - The glyph name + * @property {number} [unicode] + * @property {Array} [unicodes] + * @property {number} [xMin] + * @property {number} [yMin] + * @property {number} [xMax] + * @property {number} [yMax] + * @property {number} [advanceWidth] + */ + +// A Glyph is an individual mark that often corresponds to a character. +// Some glyphs, such as ligatures, are a combination of many characters. +// Glyphs are the basic building blocks of a font. +// +// The `Glyph` class contains utility methods for drawing the path and its points. +/** + * @exports opentype.Glyph + * @class + * @param {GlyphOptions} + * @constructor + */ +function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); +} + +/** + * @param {GlyphOptions} + */ +Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + + // These three values cannot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if ('xMin' in options) { + this.xMin = options.xMin; + } + + if ('yMin' in options) { + this.yMin = options.yMin; + } + + if ('xMax' in options) { + this.xMax = options.xMax; + } + + if ('yMax' in options) { + this.yMax = options.yMax; + } + + if ('advanceWidth' in options) { + this.advanceWidth = options.advanceWidth; + } + + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); +}; + +/** + * @param {number} + */ +Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + + this.unicodes.push(unicode); +}; + +/** + * Calculate the minimum bounding box for this glyph. + * @return {opentype.BoundingBox} + */ +Glyph.prototype.getBoundingBox = function() { + return this.path.getBoundingBox(); +}; + +/** + * Convert the glyph to a Path we can draw on a drawing context. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + * @param {opentype.Font} if hinting is to be used, the font + * @return {opentype.Path} + */ +Glyph.prototype.getPath = function(x, y, fontSize, options, font) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var commands; + var hPoints; + if (!options) { options = { }; } + var xScale = options.xScale; + var yScale = options.yScale; + + if (options.hinting && font && font.hinting) { + // in case of hinting, the hinting engine takes care + // of scaling the points (not the path) before hinting. + hPoints = this.path && font.hinting.exec(this, fontSize); + // in case the hinting engine failed hPoints is undefined + // and thus reverts to plain rending + } + + if (hPoints) { + // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency + commands = font.hinting.getCommands(hPoints); + x = Math.round(x); + y = Math.round(y); + // TODO in case of hinting xyScaling is not yet supported + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; + if (xScale === undefined) { xScale = scale; } + if (yScale === undefined) { yScale = scale; } + } + + var p = new Path(); + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } + } + + return p; +}; + +/** + * Split the glyph into contours. + * This function is here for backwards compatibility, and to + * provide raw access to the TrueType glyph outlines. + * @return {Array} + */ +Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +}; + +/** + * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. + * @return {Object} + */ +Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing + }; + + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; + } + + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } + + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; + } + + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } + + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; +}; + +/** + * Draw the glyph on the given context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + */ +Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { + this.getPath(x, y, fontSize, options).draw(ctx); +}; + +/** + * Draw the points of the glyph. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + function drawCircles(l, x, y, scale) { + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); + } + + ctx.closePath(); + ctx.fill(); + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; + + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } + + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } + + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } + + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); +}; + +/** + * Draw lines indicating important font measurements. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); +}; + +// The GlyphSet object + +// Define a property on the glyph that depends on the path being loaded. +function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + // Request the path property to make sure the path is loaded. + glyph.path; // jshint ignore:line + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); +} + +/** + * A GlyphSet represents all glyphs available in the font, but modelled using + * a deferred glyph loader, for retrieving glyphs only once they are absolutely + * necessary, to keep the memory footprint down. + * @exports opentype.GlyphSet + * @class + * @param {opentype.Font} + * @param {Array} + */ +function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i] = glyph; + } + } + + this.length = (glyphs && glyphs.length) || 0; +} + +/** + * @param {number} index + * @return {opentype.Glyph} + */ +GlyphSet.prototype.get = function(index) { + // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. + if (this.glyphs[index] === undefined) { + this.font._push(index); + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; + + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) + { glyph.addUnicode(unicodeObj.unicodes[j]); } + } + + if (this.font.cffEncoding) { + if (this.font.isCIDFont) { + glyph.name = 'gid' + index; + } else { + glyph.name = this.font.cffEncoding.charset[index]; + } + } else if (this.font.glyphNames.names) { + glyph.name = this.font.glyphNames.glyphIndexToName(index); + } + + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + } + + return this.glyphs[index]; +}; + +/** + * @param {number} index + * @param {Object} + */ +GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; +}; + +/** + * @alias opentype.glyphLoader + * @param {opentype.Font} font + * @param {number} index + * @return {opentype.Glyph} + */ +function glyphLoader(font, index) { + return new Glyph({index: index, font: font}); +} + +/** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + * @alias opentype.ttfGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseGlyph + * @param {Object} data + * @param {number} position + * @param {Function} buildPath + * @return {opentype.Glyph} + */ +function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + defineDependentProperty(glyph, 'xMin', '_xMin'); + defineDependentProperty(glyph, 'xMax', '_xMax'); + defineDependentProperty(glyph, 'yMin', '_yMin'); + defineDependentProperty(glyph, 'yMax', '_yMax'); + + return glyph; + }; +} +/** + * @alias opentype.cffGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseCFFCharstring + * @param {string} charstring + * @return {opentype.Glyph} + */ +function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; +} + +var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; + +// The `CFF` table contains the glyph outlines in PostScript format. + +// Custom equals function that can also check lists. +function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } + + return true; + } else { + return false; + } +} + +// Subroutines are encoded using the negative half of the number space. +// See type 2 chapter 4.7 "Subroutine operators". +function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; +} + +// Parse a `CFF` INDEX array. +// An index array consists of a list of offsets, then a list of objects at those offsets. +function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); + if (conversionFn) { + value = conversionFn(value); + } + + objects.push(value); + } + + return {objects: objects, startOffset: start, endOffset: endOffset}; +} + +function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + return {offsets: offsets, startOffset: start, endOffset: endOffset}; +} +function getCffIndexObject(i, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + } + + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + return value; +} + +// Parse a `CFF` DICT real value. +function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; + } + + s += lookup[n1]; + + if (n2 === eof) { + break; + } + + s += lookup[n2]; + } + + return parseFloat(s); +} + +// Parse a `CFF` DICT operand. +function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + if (b0 === 30) { + return parseFloatOperand(parser); + } + + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); +} + +// Convert the entries returned by `parseDict` to a proper dictionary. +// If a value is a list of one, it is unpacked. +function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value = (void 0); + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error('Object ' + o + ' already has key ' + key); + } + + o[key] = value; + } + + return o; +} + +// Parse a `CFF` DICT object. +// A dictionary contains key-value pairs in a compact tokenized format. +function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; + + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); +} + +// Given a String Index (SID), return the value of the string. +// Strings below index 392 are standard CFF strings and are not encoded in the font. +function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + + return index; +} + +// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. +// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. +function interpretDict(dict, meta, strings) { + var newDict = {}; + var value; + + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; + + if (Array.isArray(m.type)) { + var values = []; + values.length = m.type.length; + for (var j = 0; j < m.type.length; j++) { + value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; + if (value === undefined) { + value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; + } + if (m.type[j] === 'SID') { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m.name] = values; + } else { + value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + newDict[m.name] = value; + } + } + + return newDict; +} + +// Parse the CFF header. +function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; +} + +var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + { + name: 'fontMatrix', + op: 1207, + type: ['real', 'real', 'real', 'real', 'real', 'real'], + value: [0.001, 0, 0, 0.001, 0, 0] + }, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, + {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, + {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, + {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, + {name: 'cidFontType', op: 1233, type: 'number', value: 0}, + {name: 'cidCount', op: 1234, type: 'number', value: 8720}, + {name: 'uidBase', op: 1235, type: 'number'}, + {name: 'fdArray', op: 1236, type: 'offset'}, + {name: 'fdSelect', op: 1237, type: 'offset'}, + {name: 'fontName', op: 1238, type: 'SID'} +]; + +var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} +]; + +// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. +// The top dictionary contains the essential metadata for the font, together with the private dictionary. +function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); +} + +// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. +function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); +} + +// Returns a list of "Top DICT"s found using an INDEX list. +// Used to read both the usual high-level Top DICTs and also the FDArray +// discovered inside CID-keyed fonts. When a Top DICT has a reference to +// a Private DICT that is read and saved into the Top DICT. +// +// In addition to the expected/optional values as outlined in TOP_DICT_META +// the following values might be saved into the Top DICT. +// +// _subrs [] array of local CFF subroutines from Private DICT +// _subrsBias bias value computed from number of subroutines +// (see calcCFFSubroutineBias() and parseCFFCharstring()) +// _defaultWidthX default widths for CFF characters +// _nominalWidthX bias added to width embedded within glyph description +// +// _privateDict saved copy of parsed Private DICT from Top DICT +function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + topDict._defaultWidthX = 0; + topDict._nominalWidthX = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; +} + +// Parse the CFF charset table, which contains internal names for all the glyphs. +// This function will return a list of glyph names. +// See Adobe TN #5176 chapter 13, "Charsets". +function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (var i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; +} + +// Parse the CFF encoding data. Only one encoding can be specified per font. +// See Adobe TN #5176 chapter 12, "Encodings". +function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new CffEncoding(enc, charset); +} + +// Take in charstring code and return a Glyph object. +// The encoding is described in the Type 2 Charstring Format +// https://www.microsoft.com/typography/OTSPEC/charstr2.htm +function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; + + function newContour(x, y) { + if (open) { + p.closePath(); + } + + p.moveTo(x, y); + open = true; + } + + function parseStems() { + var hasWidthArg; + + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } + + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + x += stack.shift(); + p.lineTo(x, y); + } + + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 10: // callsubr + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + if (open) { + p.closePath(); + open = false; + } + + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } + } + + parse(code); + + glyph.advanceWidth = width; + return p; +} + +function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + // Simple list of nGlyphs elements + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + // Ranges + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + if (next > nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); + } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; + } + if (next !== nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); + } + } else { + throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); + } + return fdSelect; +} + +// Parse the `CFF` table, which contains the glyph outlines in PostScript format. +function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); + if (topDictArray.length !== 1) { + throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); + } + + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; + + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; + } + + if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { + font.isCIDFont = true; + } + + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); + } + + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.offsets.length; + } else { + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } + + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { + // Standard encoding + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + // Expert encoding + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i) { + var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + }; + } else { + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } + } +} + +// Convert a string to a String ID (SID). +// The list of strings is modified in place. +function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + cffStandardStrings.length; + } else { + sid = cffStandardStrings.length + strings.length; + strings.push(s); + } + + return sid; +} + +function makeHeader() { + return new table.Record('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); +} + +function makeNameIndex(fontNames) { + var t = new table.Record('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; +} + +// Given a dictionary's metadata, create a DICT structure. +function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } + + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } + + return m; +} + +// The Top DICT houses the global font attributes. +function makeTopDict(attrs, strings) { + var t = new table.Record('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; +} + +function makeTopDictIndex(topDict) { + var t = new table.Record('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; +} + +function makeStringIndex(strings) { + var t = new table.Record('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } + + return t; +} + +function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Record('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); +} + +function makeCharsets(glyphNames, strings) { + var t = new table.Record('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; +} + +function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx = (void 0); + var dy = (void 0); + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + // Since all coordinates are relative, we round() them ASAP to avoid propagating errors. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: Math.round(_13 * x + _23 * cmd.x1), + y1: Math.round(_13 * y + _23 * cmd.y1), + x2: Math.round(_13 * cmd.x + _23 * cmd.x1), + y2: Math.round(_13 * cmd.y + _23 * cmd.y1) + }; + } + + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; +} + +function makeCharStringsIndex(glyphs) { + var t = new table.Record('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); + + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } + + return t; +} + +function makePrivateDict(attrs, strings) { + var t = new table.Record('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; +} + +function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'RECORD'}, + {name: 'nameIndex', type: 'RECORD'}, + {name: 'topDictIndex', type: 'RECORD'}, + {name: 'stringIndex', type: 'RECORD'}, + {name: 'globalSubrIndex', type: 'RECORD'}, + {name: 'charsets', type: 'RECORD'}, + {name: 'charStringsIndex', type: 'RECORD'}, + {name: 'privateDict', type: 'RECORD'} + ]); + + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontBBox: options.fontBBox || [0, 0, 0, 0], + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + t.privateDict = makePrivateDict(privateAttrs, strings); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; +} + +var cff = { parse: parseCFFTable, make: makeCFFTable }; + +// The `head` table contains global information about the font. + +// Parse the header `head` table +function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); + head.glyphDataFormat = p.parseShort(); + return head; +} + +function makeHeadTable(options) { + // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 + var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; + var createdTimestamp = timestamp; + + if (options.createdTimestamp) { + createdTimestamp = options.createdTimestamp + 2082844800; + } + + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, + {name: 'modified', type: 'LONGDATETIME', value: timestamp}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); +} + +var head = { parse: parseHeadTable, make: makeHeadTable }; + +// The `hhea` table contains information for horizontal layout. + +// Parse the horizontal header `hhea` table +function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; +} + +function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); +} + +var hhea = { parse: parseHheaTable, make: makeHheaTable }; + +// The `hmtx` table contains the horizontal metrics for all glyphs. + +function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } +} + +function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; + + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + font._hmtxTableData[i] = { + advanceWidth: advanceWidth, + leftSideBearing: leftSideBearing + }; + } +} + +// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. +// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. +function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) + { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } + else + { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } +} + +function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } + + return t; +} + +var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + +// The `ltag` table stores IETF BCP-47 language tags. It allows supporting + +function makeLtagTable(tags) { + var result = new table.Table('ltag', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'numTags', type: 'ULONG', value: tags.length} + ]); + + var stringPool = ''; + var stringPoolOffset = 12 + tags.length * 4; + for (var i = 0; i < tags.length; ++i) { + var pos = stringPool.indexOf(tags[i]); + if (pos < 0) { + pos = stringPool.length; + stringPool += tags[i]; + } + + result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + return result; +} + +function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported ltag table version.'); + // The 'ltag' specification does not define any flags; skip the field. + p.skip('uLong', 1); + var numTags = p.parseULong(); + + var tags = []; + for (var i = 0; i < numTags; i++) { + var tag = ''; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); + } + + tags.push(tag); + } + + return tags; +} + +var ltag = { make: makeLtagTable, parse: parseLtagTable }; + +// The `maxp` table establishes the memory requirements for the font. + +// Parse the maximum profile `maxp` table. +function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; +} + +function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); +} + +var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; + +// The `name` naming table. + +// NameIDs for the name table. +var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'license', // 13 + 'licenseURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 +]; + +var macLanguages = { + 0: 'en', + 1: 'fr', + 2: 'de', + 3: 'it', + 4: 'nl', + 5: 'sv', + 6: 'es', + 7: 'da', + 8: 'pt', + 9: 'no', + 10: 'he', + 11: 'ja', + 12: 'ar', + 13: 'fi', + 14: 'el', + 15: 'is', + 16: 'mt', + 17: 'tr', + 18: 'hr', + 19: 'zh-Hant', + 20: 'ur', + 21: 'hi', + 22: 'th', + 23: 'ko', + 24: 'lt', + 25: 'pl', + 26: 'hu', + 27: 'es', + 28: 'lv', + 29: 'se', + 30: 'fo', + 31: 'fa', + 32: 'ru', + 33: 'zh', + 34: 'nl-BE', + 35: 'ga', + 36: 'sq', + 37: 'ro', + 38: 'cz', + 39: 'sk', + 40: 'si', + 41: 'yi', + 42: 'sr', + 43: 'mk', + 44: 'bg', + 45: 'uk', + 46: 'be', + 47: 'uz', + 48: 'kk', + 49: 'az-Cyrl', + 50: 'az-Arab', + 51: 'hy', + 52: 'ka', + 53: 'mo', + 54: 'ky', + 55: 'tg', + 56: 'tk', + 57: 'mn-CN', + 58: 'mn', + 59: 'ps', + 60: 'ks', + 61: 'ku', + 62: 'sd', + 63: 'bo', + 64: 'ne', + 65: 'sa', + 66: 'mr', + 67: 'bn', + 68: 'as', + 69: 'gu', + 70: 'pa', + 71: 'or', + 72: 'ml', + 73: 'kn', + 74: 'ta', + 75: 'te', + 76: 'si', + 77: 'my', + 78: 'km', + 79: 'lo', + 80: 'vi', + 81: 'id', + 82: 'tl', + 83: 'ms', + 84: 'ms-Arab', + 85: 'am', + 86: 'ti', + 87: 'om', + 88: 'so', + 89: 'sw', + 90: 'rw', + 91: 'rn', + 92: 'ny', + 93: 'mg', + 94: 'eo', + 128: 'cy', + 129: 'eu', + 130: 'ca', + 131: 'la', + 132: 'qu', + 133: 'gn', + 134: 'ay', + 135: 'tt', + 136: 'ug', + 137: 'dz', + 138: 'jv', + 139: 'su', + 140: 'gl', + 141: 'af', + 142: 'br', + 143: 'iu', + 144: 'gd', + 145: 'gv', + 146: 'ga', + 147: 'to', + 148: 'el-polyton', + 149: 'kl', + 150: 'az', + 151: 'nn' +}; + +// MacOS language ID → MacOS script ID +// +// Note that the script ID is not sufficient to determine what encoding +// to use in TrueType files. For some languages, MacOS used a modification +// of a mainstream script. For example, an Icelandic name would be stored +// with smRoman in the TrueType naming table, but the actual encoding +// is a special Icelandic version of the normal Macintosh Roman encoding. +// As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal +// Syllables but MacOS had run out of available script codes, so this was +// done as a (pretty radical) "modification" of Ethiopic. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageToScript = { + 0: 0, // langEnglish → smRoman + 1: 0, // langFrench → smRoman + 2: 0, // langGerman → smRoman + 3: 0, // langItalian → smRoman + 4: 0, // langDutch → smRoman + 5: 0, // langSwedish → smRoman + 6: 0, // langSpanish → smRoman + 7: 0, // langDanish → smRoman + 8: 0, // langPortuguese → smRoman + 9: 0, // langNorwegian → smRoman + 10: 5, // langHebrew → smHebrew + 11: 1, // langJapanese → smJapanese + 12: 4, // langArabic → smArabic + 13: 0, // langFinnish → smRoman + 14: 6, // langGreek → smGreek + 15: 0, // langIcelandic → smRoman (modified) + 16: 0, // langMaltese → smRoman + 17: 0, // langTurkish → smRoman (modified) + 18: 0, // langCroatian → smRoman (modified) + 19: 2, // langTradChinese → smTradChinese + 20: 4, // langUrdu → smArabic + 21: 9, // langHindi → smDevanagari + 22: 21, // langThai → smThai + 23: 3, // langKorean → smKorean + 24: 29, // langLithuanian → smCentralEuroRoman + 25: 29, // langPolish → smCentralEuroRoman + 26: 29, // langHungarian → smCentralEuroRoman + 27: 29, // langEstonian → smCentralEuroRoman + 28: 29, // langLatvian → smCentralEuroRoman + 29: 0, // langSami → smRoman + 30: 0, // langFaroese → smRoman (modified) + 31: 4, // langFarsi → smArabic (modified) + 32: 7, // langRussian → smCyrillic + 33: 25, // langSimpChinese → smSimpChinese + 34: 0, // langFlemish → smRoman + 35: 0, // langIrishGaelic → smRoman (modified) + 36: 0, // langAlbanian → smRoman + 37: 0, // langRomanian → smRoman (modified) + 38: 29, // langCzech → smCentralEuroRoman + 39: 29, // langSlovak → smCentralEuroRoman + 40: 0, // langSlovenian → smRoman (modified) + 41: 5, // langYiddish → smHebrew + 42: 7, // langSerbian → smCyrillic + 43: 7, // langMacedonian → smCyrillic + 44: 7, // langBulgarian → smCyrillic + 45: 7, // langUkrainian → smCyrillic (modified) + 46: 7, // langByelorussian → smCyrillic + 47: 7, // langUzbek → smCyrillic + 48: 7, // langKazakh → smCyrillic + 49: 7, // langAzerbaijani → smCyrillic + 50: 4, // langAzerbaijanAr → smArabic + 51: 24, // langArmenian → smArmenian + 52: 23, // langGeorgian → smGeorgian + 53: 7, // langMoldavian → smCyrillic + 54: 7, // langKirghiz → smCyrillic + 55: 7, // langTajiki → smCyrillic + 56: 7, // langTurkmen → smCyrillic + 57: 27, // langMongolian → smMongolian + 58: 7, // langMongolianCyr → smCyrillic + 59: 4, // langPashto → smArabic + 60: 4, // langKurdish → smArabic + 61: 4, // langKashmiri → smArabic + 62: 4, // langSindhi → smArabic + 63: 26, // langTibetan → smTibetan + 64: 9, // langNepali → smDevanagari + 65: 9, // langSanskrit → smDevanagari + 66: 9, // langMarathi → smDevanagari + 67: 13, // langBengali → smBengali + 68: 13, // langAssamese → smBengali + 69: 11, // langGujarati → smGujarati + 70: 10, // langPunjabi → smGurmukhi + 71: 12, // langOriya → smOriya + 72: 17, // langMalayalam → smMalayalam + 73: 16, // langKannada → smKannada + 74: 14, // langTamil → smTamil + 75: 15, // langTelugu → smTelugu + 76: 18, // langSinhalese → smSinhalese + 77: 19, // langBurmese → smBurmese + 78: 20, // langKhmer → smKhmer + 79: 22, // langLao → smLao + 80: 30, // langVietnamese → smVietnamese + 81: 0, // langIndonesian → smRoman + 82: 0, // langTagalog → smRoman + 83: 0, // langMalayRoman → smRoman + 84: 4, // langMalayArabic → smArabic + 85: 28, // langAmharic → smEthiopic + 86: 28, // langTigrinya → smEthiopic + 87: 28, // langOromo → smEthiopic + 88: 0, // langSomali → smRoman + 89: 0, // langSwahili → smRoman + 90: 0, // langKinyarwanda → smRoman + 91: 0, // langRundi → smRoman + 92: 0, // langNyanja → smRoman + 93: 0, // langMalagasy → smRoman + 94: 0, // langEsperanto → smRoman + 128: 0, // langWelsh → smRoman (modified) + 129: 0, // langBasque → smRoman + 130: 0, // langCatalan → smRoman + 131: 0, // langLatin → smRoman + 132: 0, // langQuechua → smRoman + 133: 0, // langGuarani → smRoman + 134: 0, // langAymara → smRoman + 135: 7, // langTatar → smCyrillic + 136: 4, // langUighur → smArabic + 137: 26, // langDzongkha → smTibetan + 138: 0, // langJavaneseRom → smRoman + 139: 0, // langSundaneseRom → smRoman + 140: 0, // langGalician → smRoman + 141: 0, // langAfrikaans → smRoman + 142: 0, // langBreton → smRoman (modified) + 143: 28, // langInuktitut → smEthiopic (modified) + 144: 0, // langScottishGaelic → smRoman (modified) + 145: 0, // langManxGaelic → smRoman (modified) + 146: 0, // langIrishGaelicScript → smRoman (modified) + 147: 0, // langTongan → smRoman + 148: 6, // langGreekAncient → smRoman + 149: 0, // langGreenlandic → smRoman + 150: 0, // langAzerbaijanRoman → smRoman + 151: 0 // langNynorsk → smRoman +}; + +// While Microsoft indicates a region/country for all its language +// IDs, we omit the region code if it's equal to the "most likely +// region subtag" according to Unicode CLDR. For scripts, we omit +// the subtag if it is equal to the Suppress-Script entry in the +// IANA language subtag registry for IETF BCP 47. +// +// For example, Microsoft states that its language code 0x041A is +// Croatian in Croatia. We transform this to the BCP 47 language code 'hr' +// and not 'hr-HR' because Croatia is the default country for Croatian, +// according to Unicode CLDR. As another example, Microsoft states +// that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform +// this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script +// for the Croatian language, according to IANA. +// +// http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html +// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry +var windowsLanguages = { + 0x0436: 'af', + 0x041C: 'sq', + 0x0484: 'gsw', + 0x045E: 'am', + 0x1401: 'ar-DZ', + 0x3C01: 'ar-BH', + 0x0C01: 'ar', + 0x0801: 'ar-IQ', + 0x2C01: 'ar-JO', + 0x3401: 'ar-KW', + 0x3001: 'ar-LB', + 0x1001: 'ar-LY', + 0x1801: 'ary', + 0x2001: 'ar-OM', + 0x4001: 'ar-QA', + 0x0401: 'ar-SA', + 0x2801: 'ar-SY', + 0x1C01: 'aeb', + 0x3801: 'ar-AE', + 0x2401: 'ar-YE', + 0x042B: 'hy', + 0x044D: 'as', + 0x082C: 'az-Cyrl', + 0x042C: 'az', + 0x046D: 'ba', + 0x042D: 'eu', + 0x0423: 'be', + 0x0845: 'bn', + 0x0445: 'bn-IN', + 0x201A: 'bs-Cyrl', + 0x141A: 'bs', + 0x047E: 'br', + 0x0402: 'bg', + 0x0403: 'ca', + 0x0C04: 'zh-HK', + 0x1404: 'zh-MO', + 0x0804: 'zh', + 0x1004: 'zh-SG', + 0x0404: 'zh-TW', + 0x0483: 'co', + 0x041A: 'hr', + 0x101A: 'hr-BA', + 0x0405: 'cs', + 0x0406: 'da', + 0x048C: 'prs', + 0x0465: 'dv', + 0x0813: 'nl-BE', + 0x0413: 'nl', + 0x0C09: 'en-AU', + 0x2809: 'en-BZ', + 0x1009: 'en-CA', + 0x2409: 'en-029', + 0x4009: 'en-IN', + 0x1809: 'en-IE', + 0x2009: 'en-JM', + 0x4409: 'en-MY', + 0x1409: 'en-NZ', + 0x3409: 'en-PH', + 0x4809: 'en-SG', + 0x1C09: 'en-ZA', + 0x2C09: 'en-TT', + 0x0809: 'en-GB', + 0x0409: 'en', + 0x3009: 'en-ZW', + 0x0425: 'et', + 0x0438: 'fo', + 0x0464: 'fil', + 0x040B: 'fi', + 0x080C: 'fr-BE', + 0x0C0C: 'fr-CA', + 0x040C: 'fr', + 0x140C: 'fr-LU', + 0x180C: 'fr-MC', + 0x100C: 'fr-CH', + 0x0462: 'fy', + 0x0456: 'gl', + 0x0437: 'ka', + 0x0C07: 'de-AT', + 0x0407: 'de', + 0x1407: 'de-LI', + 0x1007: 'de-LU', + 0x0807: 'de-CH', + 0x0408: 'el', + 0x046F: 'kl', + 0x0447: 'gu', + 0x0468: 'ha', + 0x040D: 'he', + 0x0439: 'hi', + 0x040E: 'hu', + 0x040F: 'is', + 0x0470: 'ig', + 0x0421: 'id', + 0x045D: 'iu', + 0x085D: 'iu-Latn', + 0x083C: 'ga', + 0x0434: 'xh', + 0x0435: 'zu', + 0x0410: 'it', + 0x0810: 'it-CH', + 0x0411: 'ja', + 0x044B: 'kn', + 0x043F: 'kk', + 0x0453: 'km', + 0x0486: 'quc', + 0x0487: 'rw', + 0x0441: 'sw', + 0x0457: 'kok', + 0x0412: 'ko', + 0x0440: 'ky', + 0x0454: 'lo', + 0x0426: 'lv', + 0x0427: 'lt', + 0x082E: 'dsb', + 0x046E: 'lb', + 0x042F: 'mk', + 0x083E: 'ms-BN', + 0x043E: 'ms', + 0x044C: 'ml', + 0x043A: 'mt', + 0x0481: 'mi', + 0x047A: 'arn', + 0x044E: 'mr', + 0x047C: 'moh', + 0x0450: 'mn', + 0x0850: 'mn-CN', + 0x0461: 'ne', + 0x0414: 'nb', + 0x0814: 'nn', + 0x0482: 'oc', + 0x0448: 'or', + 0x0463: 'ps', + 0x0415: 'pl', + 0x0416: 'pt', + 0x0816: 'pt-PT', + 0x0446: 'pa', + 0x046B: 'qu-BO', + 0x086B: 'qu-EC', + 0x0C6B: 'qu', + 0x0418: 'ro', + 0x0417: 'rm', + 0x0419: 'ru', + 0x243B: 'smn', + 0x103B: 'smj-NO', + 0x143B: 'smj', + 0x0C3B: 'se-FI', + 0x043B: 'se', + 0x083B: 'se-SE', + 0x203B: 'sms', + 0x183B: 'sma-NO', + 0x1C3B: 'sms', + 0x044F: 'sa', + 0x1C1A: 'sr-Cyrl-BA', + 0x0C1A: 'sr', + 0x181A: 'sr-Latn-BA', + 0x081A: 'sr-Latn', + 0x046C: 'nso', + 0x0432: 'tn', + 0x045B: 'si', + 0x041B: 'sk', + 0x0424: 'sl', + 0x2C0A: 'es-AR', + 0x400A: 'es-BO', + 0x340A: 'es-CL', + 0x240A: 'es-CO', + 0x140A: 'es-CR', + 0x1C0A: 'es-DO', + 0x300A: 'es-EC', + 0x440A: 'es-SV', + 0x100A: 'es-GT', + 0x480A: 'es-HN', + 0x080A: 'es-MX', + 0x4C0A: 'es-NI', + 0x180A: 'es-PA', + 0x3C0A: 'es-PY', + 0x280A: 'es-PE', + 0x500A: 'es-PR', + + // Microsoft has defined two different language codes for + // “Spanish with modern sorting” and “Spanish with traditional + // sorting”. This makes sense for collation APIs, and it would be + // possible to express this in BCP 47 language tags via Unicode + // extensions (eg., es-u-co-trad is Spanish with traditional + // sorting). However, for storing names in fonts, the distinction + // does not make sense, so we give “es” in both cases. + 0x0C0A: 'es', + 0x040A: 'es', + + 0x540A: 'es-US', + 0x380A: 'es-UY', + 0x200A: 'es-VE', + 0x081D: 'sv-FI', + 0x041D: 'sv', + 0x045A: 'syr', + 0x0428: 'tg', + 0x085F: 'tzm', + 0x0449: 'ta', + 0x0444: 'tt', + 0x044A: 'te', + 0x041E: 'th', + 0x0451: 'bo', + 0x041F: 'tr', + 0x0442: 'tk', + 0x0480: 'ug', + 0x0422: 'uk', + 0x042E: 'hsb', + 0x0420: 'ur', + 0x0843: 'uz-Cyrl', + 0x0443: 'uz', + 0x042A: 'vi', + 0x0452: 'cy', + 0x0488: 'wo', + 0x0485: 'sah', + 0x0478: 'ii', + 0x046A: 'yo' +}; + +// Returns a IETF BCP 47 language code, for example 'zh-Hant' +// for 'Chinese in the traditional script'. +function getLanguageCode(platformID, languageID, ltag) { + switch (platformID) { + case 0: // Unicode + if (languageID === 0xFFFF) { + return 'und'; + } else if (ltag) { + return ltag[languageID]; + } + + break; + + case 1: // Macintosh + return macLanguages[languageID]; + + case 3: // Windows + return windowsLanguages[languageID]; + } + + return undefined; +} + +var utf16 = 'utf-16'; + +// MacOS script ID → encoding. This table stores the default case, +// which can be overridden by macLanguageEncodings. +var macScriptEncodings = { + 0: 'macintosh', // smRoman + 1: 'x-mac-japanese', // smJapanese + 2: 'x-mac-chinesetrad', // smTradChinese + 3: 'x-mac-korean', // smKorean + 6: 'x-mac-greek', // smGreek + 7: 'x-mac-cyrillic', // smCyrillic + 9: 'x-mac-devanagai', // smDevanagari + 10: 'x-mac-gurmukhi', // smGurmukhi + 11: 'x-mac-gujarati', // smGujarati + 12: 'x-mac-oriya', // smOriya + 13: 'x-mac-bengali', // smBengali + 14: 'x-mac-tamil', // smTamil + 15: 'x-mac-telugu', // smTelugu + 16: 'x-mac-kannada', // smKannada + 17: 'x-mac-malayalam', // smMalayalam + 18: 'x-mac-sinhalese', // smSinhalese + 19: 'x-mac-burmese', // smBurmese + 20: 'x-mac-khmer', // smKhmer + 21: 'x-mac-thai', // smThai + 22: 'x-mac-lao', // smLao + 23: 'x-mac-georgian', // smGeorgian + 24: 'x-mac-armenian', // smArmenian + 25: 'x-mac-chinesesimp', // smSimpChinese + 26: 'x-mac-tibetan', // smTibetan + 27: 'x-mac-mongolian', // smMongolian + 28: 'x-mac-ethiopic', // smEthiopic + 29: 'x-mac-ce', // smCentralEuroRoman + 30: 'x-mac-vietnamese', // smVietnamese + 31: 'x-mac-extarabic' // smExtArabic +}; + +// MacOS language ID → encoding. This table stores the exceptional +// cases, which override macScriptEncodings. For writing MacOS naming +// tables, we need to emit a MacOS script ID. Therefore, we cannot +// merge macScriptEncodings into macLanguageEncodings. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageEncodings = { + 15: 'x-mac-icelandic', // langIcelandic + 17: 'x-mac-turkish', // langTurkish + 18: 'x-mac-croatian', // langCroatian + 24: 'x-mac-ce', // langLithuanian + 25: 'x-mac-ce', // langPolish + 26: 'x-mac-ce', // langHungarian + 27: 'x-mac-ce', // langEstonian + 28: 'x-mac-ce', // langLatvian + 30: 'x-mac-icelandic', // langFaroese + 37: 'x-mac-romanian', // langRomanian + 38: 'x-mac-ce', // langCzech + 39: 'x-mac-ce', // langSlovak + 40: 'x-mac-ce', // langSlovenian + 143: 'x-mac-inuit', // langInuktitut + 146: 'x-mac-gaelic' // langIrishGaelicScript +}; + +function getEncoding(platformID, encodingID, languageID) { + switch (platformID) { + case 0: // Unicode + return utf16; + + case 1: // Apple Macintosh + return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + + case 3: // Microsoft Windows + if (encodingID === 1 || encodingID === 10) { + return utf16; + } + + break; + } + + return undefined; +} + +// Parse the naming `name` table. +// FIXME: Format 1 additional fields are not supported yet. +// ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. +function parseNameTable(data, start, ltag) { + var name = {}; + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID] || nameID; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + var language = getLanguageCode(platformID, languageID, ltag); + var encoding = getEncoding(platformID, encodingID, languageID); + if (encoding !== undefined && language !== undefined) { + var text = (void 0); + if (encoding === utf16) { + text = decode.UTF16(data, stringOffset + offset, byteLength); + } else { + text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); + } + + if (text) { + var translations = name[property]; + if (translations === undefined) { + translations = name[property] = {}; + } + + translations[language] = text; + } + } + } + + var langTagCount = 0; + if (format === 1) { + // FIXME: Also handle Microsoft's 'name' table 1. + langTagCount = p.parseUShort(); + } + + return name; +} + +// {23: 'foo'} → {'foo': 23} +// ['bar', 'baz'] → {'bar': 0, 'baz': 1} +function reverseDict(dict) { + var result = {}; + for (var key in dict) { + result[dict[key]] = parseInt(key); + } + + return result; +} + +function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Record('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); +} + +// Finds the position of needle in haystack, or -1 if not there. +// Like String.indexOf(), but for arrays. +function findSubArray(needle, haystack) { + var needleLength = needle.length; + var limit = haystack.length - needleLength + 1; + + loop: + for (var pos = 0; pos < limit; pos++) { + for (; pos < limit; pos++) { + for (var k = 0; k < needleLength; k++) { + if (haystack[pos + k] !== needle[k]) { + continue loop; + } + } + + return pos; + } + } + + return -1; +} + +function addStringToPool(s, pool) { + var offset = findSubArray(s, pool); + if (offset < 0) { + offset = pool.length; + var i = 0; + var len = s.length; + for (; i < len; ++i) { + pool.push(s[i]); + } + + } + + return offset; +} + +function makeNameTable(names, ltag) { + var nameID; + var nameIDs = []; + + var namesWithNumericKeys = {}; + var nameTableIds = reverseDict(nameTableNames); + for (var key in names) { + var id = nameTableIds[key]; + if (id === undefined) { + id = key; + } + + nameID = parseInt(id); + + if (isNaN(nameID)) { + throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); + } + + namesWithNumericKeys[nameID] = names[key]; + nameIDs.push(nameID); + } + + var macLanguageIds = reverseDict(macLanguages); + var windowsLanguageIds = reverseDict(windowsLanguages); + + var nameRecords = []; + var stringPool = []; + + for (var i = 0; i < nameIDs.length; i++) { + nameID = nameIDs[i]; + var translations = namesWithNumericKeys[nameID]; + for (var lang in translations) { + var text = translations[lang]; + + // For MacOS, we try to emit the name in the form that was introduced + // in the initial version of the TrueType spec (in the late 1980s). + // However, this can fail for various reasons: the requested BCP 47 + // language code might not have an old-style Mac equivalent; + // we might not have a codec for the needed character encoding; + // or the name might contain characters that cannot be expressed + // in the old-style Macintosh encoding. In case of failure, we emit + // the name in a more modern fashion (Unicode encoding with BCP 47 + // language tags) that is recognized by MacOS 10.5, released in 2009. + // If fonts were only read by operating systems, we could simply + // emit all names in the modern form; this would be much easier. + // However, there are many applications and libraries that read + // 'name' tables directly, and these will usually only recognize + // the ancient form (silently skipping the unrecognized names). + var macPlatform = 1; // Macintosh + var macLanguage = macLanguageIds[lang]; + var macScript = macLanguageToScript[macLanguage]; + var macEncoding = getEncoding(macPlatform, macScript, macLanguage); + var macName = encode.MACSTRING(text, macEncoding); + if (macName === undefined) { + macPlatform = 0; // Unicode + macLanguage = ltag.indexOf(lang); + if (macLanguage < 0) { + macLanguage = ltag.length; + ltag.push(lang); + } + + macScript = 4; // Unicode 2.0 and later + macName = encode.UTF16(text); + } + + var macNameOffset = addStringToPool(macName, stringPool); + nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, + nameID, macName.length, macNameOffset)); + + var winLanguage = windowsLanguageIds[lang]; + if (winLanguage !== undefined) { + var winName = encode.UTF16(text); + var winNameOffset = addStringToPool(winName, stringPool); + nameRecords.push(makeNameRecord(3, 1, winLanguage, + nameID, winName.length, winNameOffset)); + } + } + } + + nameRecords.sort(function(a, b) { + return ((a.platformID - b.platformID) || + (a.encodingID - b.encodingID) || + (a.languageID - b.languageID) || + (a.nameID - b.nameID)); + }); + + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: nameRecords.length}, + {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} + ]); + + for (var r = 0; r < nameRecords.length; r++) { + t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); + } + + t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); + return t; +} + +var _name = { parse: parseNameTable, make: makeNameTable }; + +// The `OS/2` table contains metrics required in OpenType fonts. + +var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles +]; + +function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; +} + +// Parse the OS/2 and Windows metrics `OS/2` table +function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; +} + +function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); +} + +var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; + +// The `post` table stores additional PostScript information, such as glyph names. + +// Parse the PostScript `post` table +function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } + + post.names = []; + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + if (post.glyphNameIndex[i$1] >= standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } + + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { + post.offset[i$2] = p.parseChar(); + } + + break; + } + return post; +} + +function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); +} + +var post = { parse: parsePostTable, make: makePostTable }; + +// The `GSUB` table contains ligatures, among other things. + +var subtableParsers = new Array(9); // subtableParsers[0] is unused + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS +subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS +subtableParsers[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS +subtableParsers[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS +subtableParsers[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; +}; + +var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF +subtableParsers[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + + if (substFormat === 1) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat: substFormat, + coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC +subtableParsers[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES +subtableParsers[7] = function parseLookup7() { + // Extension Substitution subtable + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser(this.data, this.offset + this.parseULong()); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers[extensionLookupType].call(extensionParser) + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS +subtableParsers[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/gsub.htm +function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; + } + +} + +// GSUB Writing ////////////////////////////////////////////// +var subtableMakers = new Array(9); + +subtableMakers[1] = function makeLookup1(subtable) { + if (subtable.substFormat === 1) { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} + ]); + } else { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 2}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.ushortList('substitute', subtable.substitute))); + } +}; + +subtableMakers[2] = function makeLookup2(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { + return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); + }))); +}; + +subtableMakers[3] = function makeLookup3(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { + return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); + }))); +}; + +subtableMakers[4] = function makeLookup4(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { + return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { + return new table.Table('ligatureTable', + [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] + .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) + ); + })); + }))); +}; + +subtableMakers[6] = function makeLookup6(subtable) { + if (subtable.substFormat === 1) { + var returnTable = new table.Table('chainContextTable', [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { + return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { + var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) + .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) + .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) + .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); + + chainRule.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + return new table.Table('chainRuleTable', tableData); + })); + }))); + return returnTable; + } else if (subtable.substFormat === 2) { + check.assert(false, 'lookup type 6 format 2 is not yet supported.'); + } else if (subtable.substFormat === 3) { + var tableData = [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ]; + + tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length}); + subtable.backtrackCoverage.forEach(function (coverage, i) { + tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length}); + subtable.inputCoverage.forEach(function (coverage, i) { + tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length}); + subtable.lookaheadCoverage.forEach(function (coverage, i) { + tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + + tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); + subtable.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + + var returnTable$1 = new table.Table('chainContextTable', tableData); + + return returnTable$1; + } + + check.assert(false, 'lookup type 6 format must be 1, 2 or 3.'); +}; + +function makeGsubTable(gsub) { + return new table.Table('GSUB', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} + ]); +} + +var gsub = { parse: parseGsubTable, make: makeGsubTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +// Parse the metadata `meta` table. +// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html +function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported META table version.'); + p.parseULong(); // flags - currently unused and set to 0 + p.parseULong(); // tableOffset + var numDataMaps = p.parseULong(); + + var tags = {}; + for (var i = 0; i < numDataMaps; i++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text = decode.UTF8(data, start + dataOffset, dataLength); + + tags[tag] = text; + } + return tags; +} + +function makeMetaTable(tags) { + var numTags = Object.keys(tags).length; + var stringPool = ''; + var stringPoolOffset = 16 + numTags * 12; + + var result = new table.Table('meta', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'offset', type: 'ULONG', value: stringPoolOffset}, + {name: 'numTags', type: 'ULONG', value: numTags} + ]); + + for (var tag in tags) { + var pos = stringPool.length; + stringPool += tags[tag]; + + result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); + result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + + return result; +} + +var meta = { parse: parseMetaTable, make: makeMetaTable }; + +// The `COLR` table adds support for multi-colored glyphs + +function parseColrTable(data, start) { + var p = new Parser(data, start); + var version = p.parseUShort(); + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var numBaseGlyphRecords = p.parseUShort(); + var baseGlyphRecordsOffset = p.parseOffset32(); + var layerRecordsOffset = p.parseOffset32(); + var numLayerRecords = p.parseUShort(); + p.relativeOffset = baseGlyphRecordsOffset; + var baseGlyphRecords = p.parseRecordList(numBaseGlyphRecords, { + glyphID: Parser.uShort, + firstLayerIndex: Parser.uShort, + numLayers: Parser.uShort, + }); + p.relativeOffset = layerRecordsOffset; + var layerRecords = p.parseRecordList(numLayerRecords, { + glyphID: Parser.uShort, + paletteIndex: Parser.uShort + }); + + return { + version: version, + baseGlyphRecords: baseGlyphRecords, + layerRecords: layerRecords, + }; +} + +function makeColrTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0x0000; + var baseGlyphRecords = ref.baseGlyphRecords; if ( baseGlyphRecords === void 0 ) baseGlyphRecords = []; + var layerRecords = ref.layerRecords; if ( layerRecords === void 0 ) layerRecords = []; + + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var baseGlyphRecordsOffset = 14; + var layerRecordsOffset = baseGlyphRecordsOffset + (baseGlyphRecords.length * 6); + return new table.Table('COLR', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numBaseGlyphRecords', type: 'USHORT', value: baseGlyphRecords.length }, + { name: 'baseGlyphRecordsOffset', type: 'ULONG', value: baseGlyphRecordsOffset }, + { name: 'layerRecordsOffset', type: 'ULONG', value: layerRecordsOffset }, + { name: 'numLayerRecords', type: 'USHORT', value: layerRecords.length } ].concat( baseGlyphRecords.map(function (glyph, i) { return [ + { name: 'glyphID_' + i, type: 'USHORT', value: glyph.glyphID }, + { name: 'firstLayerIndex_' + i, type: 'USHORT', value: glyph.firstLayerIndex }, + { name: 'numLayers_' + i, type: 'USHORT', value: glyph.numLayers } ]; }).flat(), + layerRecords.map(function (layer, i) { return [ + { name: 'LayerGlyphID_' + i, type: 'USHORT', value: layer.glyphID }, + { name: 'paletteIndex_' + i, type: 'USHORT', value: layer.paletteIndex } ]; }).flat() )); +} + +var colr = { parse: parseColrTable, make: makeColrTable }; + +// The `CPAL` define a contiguous list of colors (colorRecords) + +// Parse the header `head` table +function parseCpalTable(data, start) { + var p = new Parser(data, start); + var version = p.parseShort(); + var numPaletteEntries = p.parseShort(); + var numPalettes = p.parseShort(); + var numColorRecords = p.parseShort(); + var colorRecordsArrayOffset = p.parseOffset32(); + var colorRecordIndices = p.parseUShortList(numPalettes); + p.relativeOffset = colorRecordsArrayOffset; + var colorRecords = p.parseULongList(numColorRecords); + return { + version: version, + numPaletteEntries: numPaletteEntries, + colorRecords: colorRecords, + colorRecordIndices: colorRecordIndices, + }; +} + +function makeCpalTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0; + var numPaletteEntries = ref.numPaletteEntries; if ( numPaletteEntries === void 0 ) numPaletteEntries = 0; + var colorRecords = ref.colorRecords; if ( colorRecords === void 0 ) colorRecords = []; + var colorRecordIndices = ref.colorRecordIndices; if ( colorRecordIndices === void 0 ) colorRecordIndices = [0]; + + check.argument(version === 0, 'Only CPALv0 are supported.'); + check.argument(colorRecords.length, 'No colorRecords given.'); + check.argument(colorRecordIndices.length, 'No colorRecordIndices given.'); + check.argument(!numPaletteEntries && colorRecordIndices.length == 1, 'Can\'t infer numPaletteEntries on multiple colorRecordIndices'); + return new table.Table('CPAL', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numPaletteEntries', type: 'USHORT', value: numPaletteEntries || colorRecords.length }, + { name: 'numPalettes', type: 'USHORT', value: colorRecordIndices.length }, + { name: 'numColorRecords', type: 'USHORT', value: colorRecords.length }, + { name: 'colorRecordsArrayOffset', type: 'ULONG', value: 12 + 2 * colorRecordIndices.length } ].concat( colorRecordIndices.map(function (palette, i) { return ({ name: 'colorRecordIndices_' + i, type: 'USHORT', value: palette }); }), + colorRecords.map(function (color, i) { return ({ name: 'colorRecords_' + i, type: 'ULONG', value: color }); }) )); +} + +var cpal = { parse: parseCpalTable, make: makeCpalTable }; + +// The `sfnt` wrapper provides organization for the tables in the font. + +function log2(v) { + return Math.log(v) / Math.log(2) | 0; +} + +function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; +} + +function makeTableRecord(tag, checkSum, offset, length) { + return new table.Record('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); +} + +function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + } + + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); + + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; +} + +// Get the metrics for a character. If the string has more than one character +// this function returns metrics for the first available character. +// You can provide optional fallback metrics if no characters are available. +function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); + } + } + + return notFoundMetrics; +} + +function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; + } + + return sum / vs.length; +} + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + + if (isNaN(glyph.advanceWidth)) { + throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); + } + + if (firstCharIndex > unicode || firstCharIndex === undefined) { + // ignore .notdef char + if (unicode > 0) { + firstCharIndex = unicode; + } + } + + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } + + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') { continue; } + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender; + globals.descender = font.descender; + + var headTable = head.make({ + flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax, + lowestRecPPEM: 3, + createdTimestamp: font.createdTimestamp + }); + + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); + + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make(Object.assign({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.yMax, + usWinDescent: Math.abs(globals.yMin), + ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now + sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. + usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. + }, font.tables.os2)); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var englishFamilyName = font.getEnglishName('fontFamily'); + var englishStyleName = font.getEnglishName('fontSubfamily'); + var englishFullName = englishFamilyName + ' ' + englishStyleName; + var postScriptName = font.getEnglishName('postScriptName'); + if (!postScriptName) { + postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; + } + + var names = {}; + for (var n in font.names) { + names[n] = font.names[n]; + } + + if (!names.uniqueID) { + names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; + } + + if (!names.postScriptName) { + names.postScriptName = {en: postScriptName}; + } + + if (!names.preferredFamily) { + names.preferredFamily = font.names.fontFamily; + } + + if (!names.preferredSubfamily) { + names.preferredSubfamily = font.names.fontSubfamily; + } + + var languageTags = []; + var nameTable = _name.make(names, languageTags); + var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); + + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.getEnglishName('version'), + fullName: englishFullName, + familyName: englishFamilyName, + weightName: englishStyleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm, + fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] + }); + + var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; + + // The order does not matter because makeSfntTable() will sort them. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + if (ltagTable) { + tables.push(ltagTable); + } + // Optional tables + if (font.tables.gsub) { + tables.push(gsub.make(font.tables.gsub)); + } + if (font.tables.cpal) { + tables.push(cpal.make(font.tables.cpal)); + } + if (font.tables.colr) { + tables.push(colr.make(font.tables.colr)); + } + if (metaTable) { + tables.push(metaTable); + } + + var sfntTable = makeSfntTable(tables); + + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { + if (tableFields[i$1].name === 'head table') { + tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } + + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; +} + +var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; + +// The Layout object is the prototype of Substitution objects, and provides + +function searchTag(arr, tag) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +function binSearch(arr, value) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +// binary search in a list of ranges (coverage, class definition) +function searchRange(ranges, value) { + // jshint bitwise: false + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { return 0; } + return range; + } +} + +/** + * @exports opentype.Layout + * @class + */ +function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; +} + +Layout.prototype = { + + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag: searchTag, + + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch: binSearch, + + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, + + /** + * Returns all scripts in the substitution table. + * @instance + * @return {Array} + */ + getScriptNames: function() { + var layout = this.getTable(); + if (!layout) { return []; } + return layout.scripts.map(function(script) { + return script.tag; + }); + }, + + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { return; } + var hasLatn = false; + for (var i = 0; i < layout.scripts.length; i++) { + var name = layout.scripts[i].tag; + if (name === 'DFLT') { return name; } + if (name === 'latn') { hasLatn = true; } + } + if (hasLatn) { return 'latn'; } + }, + + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || 'DFLT'; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; + } + } + }, + + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === 'dflt' || language === 'DFLT') { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; + } + } + }, + + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable = this.getLangSysTable(script, language, create); + if (langSysTable) { + var featureRecord; + var featIndexes = langSysTable.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + // The FeatureIndex array of indices is in arbitrary order, + // even if allFeatures is sorted alphabetically by feature tag. + for (var i = 0; i < featIndexes.length; i++) { + featureRecord = allFeatures[featIndexes[i]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; + } + } + if (create) { + var index = allFeatures.length; + // Automatic ordering of features would require to shift feature indexes in the script list. + check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } + } + }, + + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable(script, language, feature, create); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + // lookupListIndexes are in no particular order, so use naive search. + for (var i = 0; i < lookupListIndexes.length; i++) { + lookupTable = allLookups[lookupListIndexes[i]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); + } + } + if (tables.length === 0 && create) { + lookupTable = { + lookupType: lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: undefined + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; + } + } + return tables; + }, + + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; + } + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, + + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, + + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); + } + } + return glyphs; + } + } + +}; + +// The Position object provides utility methods to manipulate + +/** + * @exports opentype.Position + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Position(font) { + Layout.call(this, font, 'gpos'); +} + +Position.prototype = Layout.prototype; + +/** + * Init some data for faster and easier access later. + */ +Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); +}; + +/** + * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. + * + * @param {integer} leftIndex - left glyph index + * @param {integer} rightIndex - right glyph index + * @returns {integer} + */ +Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i = 0; i < kerningLookups.length; i++) { + var subtables = kerningLookups[i].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { continue; } + switch (subtable.posFormat) { + case 1: + // Search Pair Adjustment Positioning Format 1 + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; // left glyph found, not right glyph - try next subtable + case 2: + // Search Pair Adjustment Positioning Format 2 + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; + } + } + } + return 0; +}; + +/** + * List all kerning lookup tables. + * + * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value + * @param {string} [language='dflt'] + * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) + */ +Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, 'kern', 2); + } +}; + +// The Substitution object provides utility methods to manipulate + +/** + * @exports opentype.Substitution + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Substitution(font) { + Layout.call(this, font, 'gsub'); +} + +// Check if 2 arrays of primitives are equal. +function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { return false; } + for (var i = 0; i < n; i++) { + if (ar1[i] !== ar2[i]) { return false; } + } + return true; +} + +// Find the first subtable of a lookup table in a particular format. +function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + if (subtable.substFormat === format) { + return subtable; + } + } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return undefined; +} + +Substitution.prototype = Layout.prototype; + +/** + * Create a default GSUB table. + * @return {Object} gsub - The GSUB table. + */ +Substitution.prototype.createDefaultTable = function() { + // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. + return { + version: 1, + scripts: [{ + tag: 'DFLT', + script: { + defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, + langSysRecords: [] + } + }], + features: [], + lookups: [] + }; +}; + +/** + * List all single substitutions (lookup type 1) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } + } + } + return substitutions; +}; + +/** + * List all multiple substitutions (lookup type 2) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('ccmp', 'stch') + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getMultiple = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 2); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + var replacements = subtable.sequences[j]; + substitutions.push({ sub: glyph, by: replacements }); + } + } + } + return substitutions; +}; + +/** + * List all alternates (lookup type 3) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt'...) + * @return {Array} alternates - The list of alternates + */ +Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } + } + return alternates; +}; + +/** + * List all ligatures (lookup type 4) for a given script, language, and feature. + * The result is an array of ligature objects like { sub: [ids], by: id } + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} ligatures - The list of ligatures. + */ +Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); + } + } + } + } + return ligatures; +}; + +/** + * Add or modify a single substitution (lookup type 1) + * Format 2, more flexible, is always used. + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; + var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: {format: 1, glyphs: []}, + substitute: [] + }); + check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; +}; + +/** + * Add or modify a multiple substitution (lookup type 2) + * @param {string} feature - 4-letter feature name ('ccmp', 'stch') + * @param {Object} substitution - { sub: id, by: [id] } for format 2. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addMultiple = function(feature, substitution, script, language) { + check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids'); + var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + sequences: [] + }); + check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.sequences.splice(pos, 0, 0); + } + subtable.sequences[pos] = substitution.by; +}; + +/** + * Add or modify an alternate substitution (lookup type 3) + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: [ids] } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + alternateSets: [] + }); + check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; +}; + +/** + * Add a ligature (lookup type 4) + * Ligatures with more components must be stored ahead of those with fewer components in order to be found + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} ligature - { sub: [ids], by: id } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + // ligatureSet already exists + var ligatureSet = subtable.ligatureSets[pos]; + for (var i = 0; i < ligatureSet.length; i++) { + // If ligature already exists, return. + if (arraysEqual(ligatureSet[i].components, ligComponents)) { + return; + } + } + // ligature does not exist: add it. + ligatureSet.push(ligatureTable); + } else { + // Create a new ligatureSet and add coverage for the first glyph. + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); + } +}; + +/** + * List all feature data for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.getSingle(feature, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + return this.getSingle(feature, script, language) + .concat(this.getAlternates(feature, script, language)); + case 'dlig': + case 'liga': + case 'rlig': + return this.getLigatures(feature, script, language); + case 'ccmp': + return this.getMultiple(feature, script, language) + .concat(this.getLigatures(feature, script, language)); + case 'stch': + return this.getMultiple(feature, script, language); + } + return undefined; +}; + +/** + * Add a substitution to a feature for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + if (typeof sub.by === 'number') { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case 'dlig': + case 'liga': + case 'rlig': + return this.addLigature(feature, sub, script, language); + case 'ccmp': + if (sub.by instanceof Array) { + return this.addMultiple(feature, sub, script, language); + } + return this.addLigature(feature, sub, script, language); + } + return undefined; +}; + +function isBrowser() { + return typeof window !== 'undefined'; +} + +function nodeBufferToArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + + return ab; +} + +function arrayBufferToNodeBuffer(ab) { + var buffer = new Buffer(ab.byteLength); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } + + return buffer; +} + +function checkArgument(expression, message) { + if (!expression) { + throw message; + } +} + +// The `glyf` table describes the glyphs in TrueType outline format. + +// Parse the coordinate data for a glyph. +function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } + + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; +} + +// Parse a TrueType glyph. +function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + + if (glyph.numberOfContours > 0) { + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (var i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } + + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } + + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // values are matched points + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } + + } else { + // The arguments are bytes + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + // values are matched points + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } + + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 0x100) { + // We have instructions + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } + } + } +} + +// Transform an array of points and return a new array. +function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + + return newPoints; +} + +function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +} + +// Convert the TrueType glyph outline to a Path. +function getPath(points) { + var p = new Path(); + if (!points) { + return p; + } + + var contours = getContours(points); + + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; + + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; + + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + // If both first and last points are off-curve, start at their middle. + var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; + p.moveTo(start.x, start.y); + } + } + + for (var i = 0; i < contour.length; ++i) { + prev = curr; + curr = next; + next = contour[(i + 1) % contour.length]; + + if (curr.onCurve) { + // This is a straight line. + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; + + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } + + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } + + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } + } + + p.closePath(); + } + return p; +} + +function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + // Force the ttfGlyphLoader to parse the glyph. + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = (void 0); + if (component.matchedPoints === undefined) { + // component positioned by offset + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + // component positioned by matched points + if ((component.matchedPoints[0] > glyph.points.length - 1) || + (component.matchedPoints[1] > componentGlyph.points.length - 1)) { + throw Error('Matched points out of range in ' + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, scale01: component.scale01, + scale10: component.scale10, yScale: component.yScale, + dx: 0, dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + + return getPath(glyph.points); +} + +function parseGlyfTableAll(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + // The last element of the loca table is invalid. + for (var i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } + + return glyphs; +} + +function parseGlyfTableOnLowMemory(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + font._push = function(i) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + }; + + return glyphs; +} + +// Parse all the glyphs according to the offsets from the `loca` table. +function parseGlyfTable(data, start, loca, font, opt) { + if (opt.lowMemory) + { return parseGlyfTableOnLowMemory(data, start, loca, font); } + else + { return parseGlyfTableAll(data, start, loca, font); } +} + +var glyf = { getPath: getPath, parse: parseGlyfTable}; + +/* A TrueType font hinting interpreter. +* +* (c) 2017 Axel Kittenberger +* +* This interpreter has been implemented according to this documentation: +* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html +* +* According to the documentation F24DOT6 values are used for pixels. +* That means calculation is 1/64 pixel accurate and uses integer operations. +* However, Javascript has floating point operations by default and only +* those are available. One could make a case to simulate the 1/64 accuracy +* exactly by truncating after every division operation +* (for example with << 0) to get pixel exactly results as other TrueType +* implementations. It may make sense since some fonts are pixel optimized +* by hand using DELTAP instructions. The current implementation doesn't +* and rather uses full floating point precision. +* +* xScale, yScale and rotation is currently ignored. +* +* A few non-trivial instructions are missing as I didn't encounter yet +* a font that used them to test a possible implementation. +* +* Some fonts seem to use undocumented features regarding the twilight zone. +* Only some of them are implemented as they were encountered. +* +* The exports.DEBUG statements are removed on the minified distribution file. +*/ + +var instructionTable; +var exec; +var execGlyph; +var execComponent; + +/* +* Creates a hinting object. +* +* There ought to be exactly one +* for each truetype font that is used for hinting. +*/ +function Hinting(font) { + // the font this hinting object is for + this.font = font; + + this.getCommands = function (hPoints) { + return glyf.getPath(hPoints).commands; + }; + + // cached states + this._fpgmState = + this._prepState = + undefined; + + // errorState + // 0 ... all okay + // 1 ... had an error in a glyf, + // continue working but stop spamming + // the console + // 2 ... error at prep, stop hinting at this ppem + // 3 ... error at fpeg, stop hinting for this font at all + this._errorState = 0; +} + +/* +* Not rounding. +*/ +function roundOff(v) { + return v; +} + +/* +* Rounding to grid. +*/ +function roundToGrid(v) { + //Rounding in TT is supposed to "symmetrical around zero" + return Math.sign(v) * Math.round(Math.abs(v)); +} + +/* +* Rounding to double grid. +*/ +function roundToDoubleGrid(v) { + return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; +} + +/* +* Rounding to half grid. +*/ +function roundToHalfGrid(v) { + return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); +} + +/* +* Rounding to up to grid. +*/ +function roundUpToGrid(v) { + return Math.sign(v) * Math.ceil(Math.abs(v)); +} + +/* +* Rounding to down to grid. +*/ +function roundDownToGrid(v) { + return Math.sign(v) * Math.floor(Math.abs(v)); +} + +/* +* Super rounding. +*/ +var roundSuper = function (v) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; + + if (v < 0) { + v = -v; + sign = -1; + } + + v += threshold - phase; + + v = Math.trunc(v / period) * period; + + v += phase; + + // according to http://xgridfit.sourceforge.net/round.html + if (v < 0) { return phase * sign; } + + return v * sign; +}; + +/* +* Unit vector of x-axis. +*/ +var xUnitVector = { + x: 1, + + y: 0, + + axis: 'x', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } + + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, + + // Slope of vector line. + slope: 0, + + // Touches the point p. + touch: function (p) { + p.xTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.xTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.xTouched = false; + } +}; + +/* +* Unit vector of y-axis. +*/ +var yUnitVector = { + x: 0, + + y: 1, + + axis: 'y', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; + } + + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this. + normalSlope: 0, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, + + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, + + // Touches the point p. + touch: function (p) { + p.yTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.yTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.yTouched = false; + } +}; + +Object.freeze(xUnitVector); +Object.freeze(yUnitVector); + +/* +* Creates a unit vector that is not x- or y-axis. +*/ +function UnitVector(x, y) { + this.x = x; + this.y = y; + this.axis = undefined; + this.slope = y / x; + this.normalSlope = -x / y; + Object.freeze(this); +} + +/* +* Gets the projected distance between two points. +* o1/o2 ... if true, respective original position is used. +*/ +UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return ( + this.x * xUnitVector.distance(p1, p2, o1, o2) + + this.y * yUnitVector.distance(p1, p2, o1, o2) + ); +}; + +/* +* Moves point p so the moved position has the same relative +* position to the moved positions of rp1 and rp2 than the +* original positions had. +* +* See APPENDIX on INTERPOLATE at the bottom of this file. +*/ +UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); +}; + +/* +* Sets the point 'p' relative to point 'rp' +* by the distance 'd' +* +* See APPENDIX on SETRELATIVE at the bottom of this file. +* +* p ... point to set +* rp ... reference point +* d ... distance on projection vector +* pv ... projection vector (undefined = this) +* org ... if true, uses the original position of rp as reference. +*/ +UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { + pv = pv || this; + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + var pvns = pv.normalSlope; + var fvs = this.slope; + + var px = p.x; + var py = p.y; + + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; +}; + +/* +* Touches the point p. +*/ +UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; +}; + +/* +* Returns a unit vector with x/y coordinates. +*/ +function getUnitVector(x, y) { + var d = Math.sqrt(x * x + y * y); + + x /= d; + y /= d; + + if (x === 1 && y === 0) { return xUnitVector; } + else if (x === 0 && y === 1) { return yUnitVector; } + else { return new UnitVector(x, y); } +} + +/* +* Creates a point in the hinting engine. +*/ +function HPoint( + x, + y, + lastPointOfContour, + onCurve +) { + this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value + this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value + + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = undefined; + this.nextPointOnContour = undefined; + this.xTouched = false; + this.yTouched = false; + + Object.preventExtensions(this); +} + +/* +* Returns the next touched point on the contour. +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.nextTouched = function(v) { + var p = this.nextPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } + + return p; +}; + +/* +* Returns the previous touched point on the contour +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.prevTouched = function(v) { + var p = this.prevPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } + + return p; +}; + +/* +* The zero point. +*/ +var HPZero = Object.freeze(new HPoint(0, 0)); + +/* +* The default state of the interpreter. +* +* Note: Freezing the defaultState and then deriving from it +* makes the V8 Javascript engine going awkward, +* so this is avoided, albeit the defaultState shouldn't +* ever change. +*/ +var defaultState = { + cvCutIn: 17 / 16, // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, // loops some instructions + minDis: 1, // minimum distance + autoFlip: true +}; + +/* +* The current state of the interpreter. +* +* env ... 'fpgm' or 'prep' or 'glyf' +* prog ... the program +*/ +function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + + switch (env) { + case 'glyf' : + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case 'prep' : + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } +} + +/* +* Executes a glyph program. +* +* This does the hinting for each glyph. +* +* Returns an array of moved points. +* +* glyph: the glyph to hint +* ppem: the size the glyph is rendered for +*/ +Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== 'number') { + throw new Error('Point size is not a number!'); + } + + // Received a fatal error, don't do any hinting anymore. + if (this._errorState > 2) { return; } + + var font = this.font; + var prepState = this._prepState; + + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; + + if (!fpgmState) { + // Executes the fpgm state. + // This is used by fonts to define functions. + State.prototype = defaultState; + + fpgmState = + this._fpgmState = + new State('fpgm', font.tables.fpgm); + + fpgmState.funcs = [ ]; + fpgmState.font = font; + + if (exports.DEBUG) { + console.log('---EXEC FPGM---'); + fpgmState.step = -1; + } + + try { + exec(fpgmState); + } catch (e) { + console.log('Hinting error in FPGM:' + e); + this._errorState = 3; + return; + } + } + + // Executes the prep program for this ppem setting. + // This is used by fonts to set cvt values + // depending on to be rendered font size. + + State.prototype = fpgmState; + prepState = + this._prepState = + new State('prep', font.tables.prep); + + prepState.ppem = ppem; + + // Creates a copy of the cvt table + // and scales it to the current ppem setting. + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c = 0; c < oCvt.length; c++) { + cvt[c] = oCvt[c] * scale; + } + } else { + prepState.cvt = []; + } + + if (exports.DEBUG) { + console.log('---EXEC PREP---'); + prepState.step = -1; + } + + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log('Hinting error in PREP:' + e); + } + this._errorState = 2; + } + } + + if (this._errorState > 1) { return; } + + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log('Hinting error:' + e); + console.log('Note: further hinting errors are silenced'); + } + this._errorState = 1; + return undefined; + } +}; + +/* +* Executes the hinting program for a glyph. +*/ +execGlyph = function(glyph, prepState) { + // original point positions + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + + State.prototype = prepState; + if (!components) { + state = new State('glyf', glyph.instructions); + if (exports.DEBUG) { + console.log('---EXEC GLYPH---'); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i = 0; i < components.length; i++) { + var c = components[i]; + var cg = font.glyphs.get(c.glyphIndex); + + state = new State('glyf', cg.instructions); + + if (exports.DEBUG) { + console.log('---EXEC COMP ' + i + '---'); + state.step = -1; + } + + execComponent(cg, state, xScale, yScale); + // appends the computed points to the result array + // post processes the component points + var dx = Math.round(c.dx * xScale); + var dy = Math.round(c.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi = 0; pi < gz.length; pi++) { + var p = gz[pi]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } + + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } + } + + if (glyph.instructions && !state.inhibitGridFit) { + // the composite has instructions on its own + state = new State('glyf', glyph.instructions); + + state.gZone = state.z0 = state.z1 = state.z2 = gZone; + + state.contours = contours; + + // note: HPZero cannot be used here, since + // the point might be modified + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + if (exports.DEBUG) { + console.log('---EXEC COMPOSITE---'); + state.step = -1; + } + + exec(state); + + gZone.length -= 2; + } + } + + return gZone; +}; + +/* +* Executes the hinting program for a component of a multi-component glyph +* or of the glyph itself for a non-component glyph. +*/ +execComponent = function(glyph, state, xScale, yScale) +{ + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + + // Scales the original points and + // makes copies for the hinted points. + var cp; // current point + for (var i = 0; i < pLen; i++) { + cp = points[i]; + + gZone[i] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } + + // Chain links the contours. + var sp; // start point + var np; // next point + + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; + + if (!sp) { + sp = cp; + contours.push(i$1); + } + + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = undefined; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } + } + + if (state.inhibitGridFit) { return; } + + if (exports.DEBUG) { + console.log('PROCESSING GLYPH', state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); + } + } + + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + exec(state); + + // Removes the extra points. + gZone.length -= 2; + + if (exports.DEBUG) { + console.log('FINISHED GLYPH', state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } + } +}; + +/* +* Executes the program loaded in state. +*/ +exec = function(state) { + var prog = state.prog; + + if (!prog) { return; } + + var pLen = prog.length; + var ins; + + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports.DEBUG) { state.step++; } + ins = instructionTable[prog[state.ip]]; + + if (!ins) { + throw new Error( + 'unknown instruction: 0x' + + Number(prog[state.ip]).toString(16) + ); + } + + ins(state); + + // very extensive debugging for each step + /* + if (exports.DEBUG) { + var da; + if (state.gZone) { + da = []; + for (let i = 0; i < state.gZone.length; i++) + { + da.push(i + ' ' + + state.gZone[i].x * 64 + ' ' + + state.gZone[i].y * 64 + ' ' + + (state.gZone[i].xTouched ? 'x' : '') + + (state.gZone[i].yTouched ? 'y' : '') + ); + } + console.log('GZ', da); + } + + if (state.tZone) { + da = []; + for (let i = 0; i < state.tZone.length; i++) { + da.push(i + ' ' + + state.tZone[i].x * 64 + ' ' + + state.tZone[i].y * 64 + ' ' + + (state.tZone[i].xTouched ? 'x' : '') + + (state.tZone[i].yTouched ? 'y' : '') + ); + } + console.log('TZ', da); + } + + if (state.stack.length > 10) { + console.log( + state.stack.length, + '...', state.stack.slice(state.stack.length - 10) + ); + } else { + console.log(state.stack.length, state.stack); + } + } + */ + } +}; + +/* +* Initializes the twilight zone. +* +* This is only done if a SZPx instruction +* refers to the twilight zone. +*/ +function initTZone(state) +{ + var tZone = state.tZone = new Array(state.gZone.length); + + // no idea if this is actually correct... + for (var i = 0; i < tZone.length; i++) + { + tZone[i] = new HPoint(0, 0); + } +} + +/* +* Skips the instruction pointer ahead over an IF/ELSE block. +* handleElse .. if true breaks on matching ELSE +*/ +function skip(state, handleElse) +{ + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; + + do { + ins = prog[++ip]; + if (ins === 0x58) // IF + { nesting++; } + else if (ins === 0x59) // EIF + { nesting--; } + else if (ins === 0x40) // NPUSHB + { ip += prog[ip + 1] + 1; } + else if (ins === 0x41) // NPUSHW + { ip += 2 * prog[ip + 1] + 1; } + else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB + { ip += ins - 0xB0 + 1; } + else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW + { ip += (ins - 0xB8 + 1) * 2; } + else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE + { break; } + } while (nesting > 0); + + state.ip = ip; +} + +/*----------------------------------------------------------* +* And then a lot of instructions... * +*----------------------------------------------------------*/ + +// SVTCA[a] Set freedom and projection Vectors To Coordinate Axis +// 0x00-0x01 +function SVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } + + state.fv = state.pv = state.dpv = v; +} + +// SPVTCA[a] Set Projection Vector to Coordinate Axis +// 0x02-0x03 +function SPVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } + + state.pv = state.dpv = v; +} + +// SFVTCA[a] Set Freedom Vector to Coordinate Axis +// 0x04-0x05 +function SFVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } + + state.fv = v; +} + +// SPVTL[a] Set Projection Vector To Line +// 0x06-0x07 +function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.pv = state.dpv = getUnitVector(dx, dy); +} + +// SFVTL[a] Set Freedom Vector To Line +// 0x08-0x09 +function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.fv = getUnitVector(dx, dy); +} + +// SPVFS[] Set Projection Vector From Stack +// 0x0A +function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.pv = state.dpv = getUnitVector(x, y); +} + +// SFVFS[] Set Freedom Vector From Stack +// 0x0B +function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.fv = getUnitVector(x, y); +} + +// GPV[] Get Projection Vector +// 0x0C +function GPV(state) { + var stack = state.stack; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } + + stack.push(pv.x * 0x4000); + stack.push(pv.y * 0x4000); +} + +// GFV[] Get Freedom Vector +// 0x0C +function GFV(state) { + var stack = state.stack; + var fv = state.fv; + + if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } + + stack.push(fv.x * 0x4000); + stack.push(fv.y * 0x4000); +} + +// SFVTPV[] Set Freedom Vector To Projection Vector +// 0x0E +function SFVTPV(state) { + state.fv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } +} + +// ISECT[] moves point p to the InterSECTion of two lines +// 0x0F +function ISECT(state) +{ + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } + + // math from + // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + + var x1 = pa0.x; + var y1 = pa0.y; + var x2 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; + + var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x2; + var f2 = x3 * y4 - y3 * x4; + + p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; +} + +// SRP0[] Set Reference Point 0 +// 0x10 +function SRP0(state) { + state.rp0 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } +} + +// SRP1[] Set Reference Point 1 +// 0x11 +function SRP1(state) { + state.rp1 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } +} + +// SRP1[] Set Reference Point 2 +// 0x12 +function SRP2(state) { + state.rp2 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } +} + +// SZP0[] Set Zone Pointer 0 +// 0x13 +function SZP0(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } + + state.zp0 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.tZone; + break; + case 1 : + state.z0 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZP1[] Set Zone Pointer 1 +// 0x14 +function SZP1(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } + + state.zp1 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z1 = state.tZone; + break; + case 1 : + state.z1 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZP2[] Set Zone Pointer 2 +// 0x15 +function SZP2(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } + + state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z2 = state.tZone; + break; + case 1 : + state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZPS[] Set Zone PointerS +// 0x16 +function SZPS(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } + + state.zp0 = state.zp1 = state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1 : + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SLOOP[] Set LOOP variable +// 0x17 +function SLOOP(state) { + state.loop = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } +} + +// RTG[] Round To Grid +// 0x18 +function RTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } + + state.round = roundToGrid; +} + +// RTHG[] Round To Half Grid +// 0x19 +function RTHG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } + + state.round = roundToHalfGrid; +} + +// SMD[] Set Minimum Distance +// 0x1A +function SMD(state) { + var d = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } + + state.minDis = d / 0x40; +} + +// ELSE[] ELSE clause +// 0x1B +function ELSE(state) { + // This instruction has been reached by executing a then branch + // so it just skips ahead until matching EIF. + // + // In case the IF was negative the IF[] instruction already + // skipped forward over the ELSE[] + + if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } + + skip(state, false); +} + +// JMPR[] JuMP Relative +// 0x1C +function JMPR(state) { + var o = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } + + // A jump by 1 would do nothing. + state.ip += o - 1; +} + +// SCVTCI[] Set Control Value Table Cut-In +// 0x1D +function SCVTCI(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } + + state.cvCutIn = n / 0x40; +} + +// DUP[] DUPlicate top stack element +// 0x20 +function DUP(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } + + stack.push(stack[stack.length - 1]); +} + +// POP[] POP top stack element +// 0x21 +function POP(state) { + if (exports.DEBUG) { console.log(state.step, 'POP[]'); } + + state.stack.pop(); +} + +// CLEAR[] CLEAR the stack +// 0x22 +function CLEAR(state) { + if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } + + state.stack.length = 0; +} + +// SWAP[] SWAP the top two elements on the stack +// 0x23 +function SWAP(state) { + var stack = state.stack; + + var a = stack.pop(); + var b = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } + + stack.push(a); + stack.push(b); +} + +// DEPTH[] DEPTH of the stack +// 0x24 +function DEPTH(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } + + stack.push(stack.length); +} + +// LOOPCALL[] LOOPCALL function +// 0x2A +function LOOPCALL(state) { + var stack = state.stack; + var fn = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + for (var i = 0; i < c; i++) { + exec(state); + + if (exports.DEBUG) { console.log( + ++state.step, + i + 1 < c ? 'next loopcall' : 'done loopcall', + i + ); } + } + + // restores the callers program + state.ip = cip; + state.prog = cprog; +} + +// CALL[] CALL function +// 0x2B +function CALL(state) { + var fn = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + exec(state); + + // restores the callers program + state.ip = cip; + state.prog = cprog; + + if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } +} + +// CINDEX[] Copy the INDEXed element to the top of the stack +// 0x25 +function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } + + // In case of k == 1, it copies the last element after popping + // thus stack.length - k. + stack.push(stack[stack.length - k]); +} + +// MINDEX[] Move the INDEXed element to the top of the stack +// 0x26 +function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } + + stack.push(stack.splice(stack.length - k, 1)[0]); +} + +// FDEF[] Function DEFinition +// 0x2C +function FDEF(state) { + if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + var fn = stack.pop(); + var ipBegin = ip; + + if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } + + while (prog[++ip] !== 0x2D){ } + + state.ip = ip; + state.funcs[fn] = prog.slice(ipBegin + 1, ip); +} + +// MDAP[a] Move Direct Absolute Point +// 0x2E-0x2F +function MDAP(round, state) { + var pi = state.stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } + + var d = pv.distance(p, HPZero); + + if (round) { d = state.round(d); } + + fv.setRelative(p, HPZero, d, pv); + fv.touch(p); + + state.rp0 = state.rp1 = pi; +} + +// IUP[a] Interpolate Untouched Points through the outline +// 0x30 +function IUP(v, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; + + if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + + for (var i = 0; i < pLen; i++) { + cp = z2[i]; // current point + + // if this point has been touched go on + if (v.touched(cp)) { continue; } + + pp = cp.prevTouched(v); + + // no point on the contour has been touched? + if (pp === cp) { continue; } + + np = cp.nextTouched(v); + + if (pp === np) { + // only one point on the contour has been touched + // so simply moves the point like that + + v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); + } + + v.interpolate(cp, pp, np, v); + } +} + +// SHP[] SHift Point using reference point +// 0x32-0x33 +function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; + + while (loop--) + { + var pi = stack.pop(); + var p = z2[pi]; + + var d = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? + 'loop ' + (state.loop - loop) + ': ' : + '' + ) + + 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi + ); + } + } + + state.loop = 1; +} + +// SHC[] SHift Contour using reference point +// 0x36-0x37 +function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci = stack.pop(); + var sp = state.z2[state.contours[ci]]; + var p = sp; + + if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } + + var d = pv.distance(rp, rp, false, true); + + do { + if (p !== rp) { fv.setRelative(p, p, d, pv); } + p = p.nextPointOnContour; + } while (p !== sp); +} + +// SHZ[] SHift Zone using reference point +// 0x36-0x37 +function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } + + var z; + switch (e) { + case 0 : z = state.tZone; break; + case 1 : z = state.gZone; break; + default : throw new Error('Invalid zone'); + } + + var p; + var d = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i = 0; i < pLen; i++) + { + p = z[i]; + fv.setRelative(p, p, d, pv); + //if (p !== rp) fv.setRelative(p, p, d, pv); + } +} + +// SHPIX[] SHift point by a PIXel amount +// 0x38 +function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d = stack.pop() / 0x40; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'SHPIX[]', pi, d + ); + } + + fv.setRelative(p, p, d); + fv.touch(p); + } + + state.loop = 1; +} + +// IP[] Interpolate Point +// 0x39 +function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'IP[]', pi, rp1i, '<->', rp2i + ); + } + + fv.interpolate(p, rp1, rp2, pv); + + fv.touch(p); + } + + state.loop = 1; +} + +// MSIRP[a] Move Stack Indirect Relative Point +// 0x3A-0x3B +function MSIRP(a, state) { + var stack = state.stack; + var d = stack.pop() / 64; + var pi = stack.pop(); + var p = state.z1[pi]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; + + fv.setRelative(p, rp0, d, pv); + fv.touch(p); + + if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (a) { state.rp0 = pi; } +} + +// ALIGNRP[] Align to reference point. +// 0x3C +function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; + + while (loop--) { + var pi = stack.pop(); + var p = z1[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'ALIGNRP[]', pi + ); + } + + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); + } + + state.loop = 1; +} + +// RTG[] Round To Double Grid +// 0x3D +function RTDG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } + + state.round = roundToDoubleGrid; +} + +// MIAP[a] Move Indirect Absolute Point +// 0x3E-0x3F +function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi = stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; + + if (exports.DEBUG) { + console.log( + state.step, + 'MIAP[' + round + ']', + n, '(', cv, ')', pi + ); + } + + var d = pv.distance(p, HPZero); + + if (round) { + if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } + + d = state.round(d); + } + + fv.setRelative(p, HPZero, d, pv); + + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; + } + + fv.touch(p); + + state.rp0 = state.rp1 = pi; +} + +// NPUSB[] PUSH N Bytes +// 0x40 +function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; + + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; +} + +// NPUSHW[] PUSH N Words +// 0x41 +function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; +} + +// WS[] Write Store +// 0x42 +function WS(state) { + var stack = state.stack; + var store = state.store; + + if (!store) { store = state.store = []; } + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } + + store[l] = v; +} + +// RS[] Read Store +// 0x43 +function RS(state) { + var stack = state.stack; + var store = state.store; + + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RS', l); } + + var v = (store && store[l]) || 0; + + stack.push(v); +} + +// WCVTP[] Write Control Value Table in Pixel units +// 0x44 +function WCVTP(state) { + var stack = state.stack; + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } + + state.cvt[l] = v / 0x40; +} + +// RCVT[] Read Control Value Table entry +// 0x45 +function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } + + stack.push(state.cvt[cvte] * 0x40); +} + +// GC[] Get Coordinate projected onto the projection vector +// 0x46-0x47 +function GC(a, state) { + var stack = state.stack; + var pi = stack.pop(); + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } + + stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); +} + +// MD[a] Measure Distance +// 0x49-0x4A +function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d = state.dpv.distance(p1, p2, a, a); + + if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } + + state.stack.push(Math.round(d * 64)); +} + +// MPPEM[] Measure Pixels Per EM +// 0x4B +function MPPEM(state) { + if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } + state.stack.push(state.ppem); +} + +// FLIPON[] set the auto FLIP Boolean to ON +// 0x4D +function FLIPON(state) { + if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } + state.autoFlip = true; +} + +// LT[] Less Than +// 0x50 +function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } + + stack.push(e1 < e2 ? 1 : 0); +} + +// LTEQ[] Less Than or EQual +// 0x53 +function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } + + stack.push(e1 <= e2 ? 1 : 0); +} + +// GTEQ[] Greater Than +// 0x52 +function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } + + stack.push(e1 > e2 ? 1 : 0); +} + +// GTEQ[] Greater Than or EQual +// 0x53 +function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } + + stack.push(e1 >= e2 ? 1 : 0); +} + +// EQ[] EQual +// 0x54 +function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } + + stack.push(e2 === e1 ? 1 : 0); +} + +// NEQ[] Not EQual +// 0x55 +function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } + + stack.push(e2 !== e1 ? 1 : 0); +} + +// ODD[] ODD +// 0x56 +function ODD(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } + + stack.push(Math.trunc(n) % 2 ? 1 : 0); +} + +// EVEN[] EVEN +// 0x57 +function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } + + stack.push(Math.trunc(n) % 2 ? 0 : 1); +} + +// IF[] IF test +// 0x58 +function IF(state) { + var test = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } + + // if test is true it just continues + // if not the ip is skipped until matching ELSE or EIF + if (!test) { + skip(state, true); + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } +} + +// EIF[] End IF +// 0x59 +function EIF(state) { + // this can be reached normally when + // executing an else branch. + // -> just ignore it + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } +} + +// AND[] logical AND +// 0x5A +function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } + + stack.push(e2 && e1 ? 1 : 0); +} + +// OR[] logical OR +// 0x5B +function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } + + stack.push(e2 || e1 ? 1 : 0); +} + +// NOT[] logical NOT +// 0x5C +function NOT(state) { + var stack = state.stack; + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } + + stack.push(e ? 0 : 1); +} + +// DELTAP1[] DELTA exception P1 +// DELTAP2[] DELTA exception P2 +// DELTAP3[] DELTA exception P3 +// 0x5D, 0x71, 0x72 +function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + var z0 = state.z0; + + if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var pi = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } + + var p = z0[pi]; + fv.setRelative(p, p, mag * ds, pv); + } +} + +// SDB[] Set Delta Base in the graphics state +// 0x5E +function SDB(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } + + state.deltaBase = n; +} + +// SDS[] Set Delta Shift in the graphics state +// 0x5F +function SDS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } + + state.deltaShift = Math.pow(0.5, n); +} + +// ADD[] ADD +// 0x60 +function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } + + stack.push(n1 + n2); +} + +// SUB[] SUB +// 0x61 +function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } + + stack.push(n1 - n2); +} + +// DIV[] DIV +// 0x62 +function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } + + stack.push(n1 * 64 / n2); +} + +// MUL[] MUL +// 0x63 +function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } + + stack.push(n1 * n2 / 64); +} + +// ABS[] ABSolute value +// 0x64 +function ABS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } + + stack.push(Math.abs(n)); +} + +// NEG[] NEGate +// 0x65 +function NEG(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } + + stack.push(-n); +} + +// FLOOR[] FLOOR +// 0x66 +function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } + + stack.push(Math.floor(n / 0x40) * 0x40); +} + +// CEILING[] CEILING +// 0x67 +function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } + + stack.push(Math.ceil(n / 0x40) * 0x40); +} + +// ROUND[ab] ROUND value +// 0x68-0x6B +function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + + stack.push(state.round(n / 0x40) * 0x40); +} + +// WCVTF[] Write Control Value Table in Funits +// 0x70 +function WCVTF(state) { + var stack = state.stack; + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } + + state.cvt[l] = v * state.ppem / state.font.unitsPerEm; +} + +// DELTAC1[] DELTA exception C1 +// DELTAC2[] DELTA exception C2 +// DELTAC3[] DELTA exception C3 +// 0x73, 0x74, 0x75 +function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + + if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var c = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + + var delta = mag * ds; + + if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + + state.cvt[c] += delta; + } +} + +// SROUND[] Super ROUND +// 0x76 +function SROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = 0.5; + break; + case 0x40: + period = 1; + break; + case 0x80: + period = 2; + break; + default: + throw new Error('invalid SROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: throw new Error('invalid SROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} + +// S45ROUND[] Super ROUND 45 degrees +// 0x77 +function S45ROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = Math.sqrt(2) / 2; + break; + case 0x40: + period = Math.sqrt(2); + break; + case 0x80: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error('invalid S45ROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: + throw new Error('invalid S45ROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} + +// ROFF[] Round Off +// 0x7A +function ROFF(state) { + if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } + + state.round = roundOff; +} + +// RUTG[] Round Up To Grid +// 0x7C +function RUTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } + + state.round = roundUpToGrid; +} + +// RDTG[] Round Down To Grid +// 0x7D +function RDTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } + + state.round = roundDownToGrid; +} + +// SCANCTRL[] SCAN conversion ConTRoL +// 0x85 +function SCANCTRL(state) { + var n = state.stack.pop(); + + // ignored by opentype.js + + if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } +} + +// SDPVTL[a] Set Dual Projection Vector To Line +// 0x86-0x87 +function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.dpv = getUnitVector(dx, dy); +} + +// GETINFO[] GET INFOrmation +// 0x88 +function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; + + if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } + + // v35 as in no subpixel hinting + if (sel & 0x01) { r = 35; } + + // TODO rotation and stretch currently not supported + // and thus those GETINFO are always 0. + + // opentype.js is always gray scaling + if (sel & 0x20) { r |= 0x1000; } + + stack.push(r); +} + +// ROLL[] ROLL the top three stack elements +// 0x8A +function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } + + stack.push(b); + stack.push(a); + stack.push(c); +} + +// MAX[] MAXimum of top two stack elements +// 0x8B +function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } + + stack.push(Math.max(e1, e2)); +} + +// MIN[] MINimum of top two stack elements +// 0x8C +function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } + + stack.push(Math.min(e1, e2)); +} + +// SCANTYPE[] SCANTYPE +// 0x8D +function SCANTYPE(state) { + var n = state.stack.pop(); + // ignored by opentype.js + if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } +} + +// INSTCTRL[] INSTCTRL +// 0x8D +function INSTCTRL(state) { + var s = state.stack.pop(); + var v = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } + + switch (s) { + case 1 : state.inhibitGridFit = !!v; return; + case 2 : state.ignoreCvt = !!v; return; + default: throw new Error('invalid INSTCTRL[] selector'); + } +} + +// PUSHB[abc] PUSH Bytes +// 0xB0-0xB7 +function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; +} + +// PUSHW[abc] PUSH Words +// 0xB8-0xBF +function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; +} + +// MDRP[abcde] Move Direct Relative Point +// 0xD0-0xEF +// (if indirect is 0) +// +// and +// +// MIRP[abcde] Move Indirect Relative Point +// 0xE0-0xFF +// (if indirect is 1) + +function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi]; + + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; // original distance + var d; // moving distance + var sign; // sign of distance + var cv; + + d = od = pv.distance(p, rp, true, true); + sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 + + // TODO consider autoFlip + d = Math.abs(d); + + if (indirect) { + cv = state.cvt[cvte]; + + if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } + } + + if (keepD && d < md) { d = md; } + + if (ro) { d = state.round(d); } + + fv.setRelative(p, rp, sign * d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (indirect ? 'MIRP[' : 'MDRP[') + + (setRp0 ? 'M' : 'm') + + (keepD ? '>' : '_') + + (ro ? 'R' : '_') + + (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + + ']', + indirect ? + cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : + '', + pi, + '(d =', od, '->', sign * d, ')' + ); + } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (setRp0) { state.rp0 = pi; } +} + +/* +* The instruction table. +*/ +instructionTable = [ + /* 0x00 */ SVTCA.bind(undefined, yUnitVector), + /* 0x01 */ SVTCA.bind(undefined, xUnitVector), + /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), + /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), + /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), + /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), + /* 0x06 */ SPVTL.bind(undefined, 0), + /* 0x07 */ SPVTL.bind(undefined, 1), + /* 0x08 */ SFVTL.bind(undefined, 0), + /* 0x09 */ SFVTL.bind(undefined, 1), + /* 0x0A */ SPVFS, + /* 0x0B */ SFVFS, + /* 0x0C */ GPV, + /* 0x0D */ GFV, + /* 0x0E */ SFVTPV, + /* 0x0F */ ISECT, + /* 0x10 */ SRP0, + /* 0x11 */ SRP1, + /* 0x12 */ SRP2, + /* 0x13 */ SZP0, + /* 0x14 */ SZP1, + /* 0x15 */ SZP2, + /* 0x16 */ SZPS, + /* 0x17 */ SLOOP, + /* 0x18 */ RTG, + /* 0x19 */ RTHG, + /* 0x1A */ SMD, + /* 0x1B */ ELSE, + /* 0x1C */ JMPR, + /* 0x1D */ SCVTCI, + /* 0x1E */ undefined, // TODO SSWCI + /* 0x1F */ undefined, // TODO SSW + /* 0x20 */ DUP, + /* 0x21 */ POP, + /* 0x22 */ CLEAR, + /* 0x23 */ SWAP, + /* 0x24 */ DEPTH, + /* 0x25 */ CINDEX, + /* 0x26 */ MINDEX, + /* 0x27 */ undefined, // TODO ALIGNPTS + /* 0x28 */ undefined, + /* 0x29 */ undefined, // TODO UTP + /* 0x2A */ LOOPCALL, + /* 0x2B */ CALL, + /* 0x2C */ FDEF, + /* 0x2D */ undefined, // ENDF (eaten by FDEF) + /* 0x2E */ MDAP.bind(undefined, 0), + /* 0x2F */ MDAP.bind(undefined, 1), + /* 0x30 */ IUP.bind(undefined, yUnitVector), + /* 0x31 */ IUP.bind(undefined, xUnitVector), + /* 0x32 */ SHP.bind(undefined, 0), + /* 0x33 */ SHP.bind(undefined, 1), + /* 0x34 */ SHC.bind(undefined, 0), + /* 0x35 */ SHC.bind(undefined, 1), + /* 0x36 */ SHZ.bind(undefined, 0), + /* 0x37 */ SHZ.bind(undefined, 1), + /* 0x38 */ SHPIX, + /* 0x39 */ IP, + /* 0x3A */ MSIRP.bind(undefined, 0), + /* 0x3B */ MSIRP.bind(undefined, 1), + /* 0x3C */ ALIGNRP, + /* 0x3D */ RTDG, + /* 0x3E */ MIAP.bind(undefined, 0), + /* 0x3F */ MIAP.bind(undefined, 1), + /* 0x40 */ NPUSHB, + /* 0x41 */ NPUSHW, + /* 0x42 */ WS, + /* 0x43 */ RS, + /* 0x44 */ WCVTP, + /* 0x45 */ RCVT, + /* 0x46 */ GC.bind(undefined, 0), + /* 0x47 */ GC.bind(undefined, 1), + /* 0x48 */ undefined, // TODO SCFS + /* 0x49 */ MD.bind(undefined, 0), + /* 0x4A */ MD.bind(undefined, 1), + /* 0x4B */ MPPEM, + /* 0x4C */ undefined, // TODO MPS + /* 0x4D */ FLIPON, + /* 0x4E */ undefined, // TODO FLIPOFF + /* 0x4F */ undefined, // TODO DEBUG + /* 0x50 */ LT, + /* 0x51 */ LTEQ, + /* 0x52 */ GT, + /* 0x53 */ GTEQ, + /* 0x54 */ EQ, + /* 0x55 */ NEQ, + /* 0x56 */ ODD, + /* 0x57 */ EVEN, + /* 0x58 */ IF, + /* 0x59 */ EIF, + /* 0x5A */ AND, + /* 0x5B */ OR, + /* 0x5C */ NOT, + /* 0x5D */ DELTAP123.bind(undefined, 1), + /* 0x5E */ SDB, + /* 0x5F */ SDS, + /* 0x60 */ ADD, + /* 0x61 */ SUB, + /* 0x62 */ DIV, + /* 0x63 */ MUL, + /* 0x64 */ ABS, + /* 0x65 */ NEG, + /* 0x66 */ FLOOR, + /* 0x67 */ CEILING, + /* 0x68 */ ROUND.bind(undefined, 0), + /* 0x69 */ ROUND.bind(undefined, 1), + /* 0x6A */ ROUND.bind(undefined, 2), + /* 0x6B */ ROUND.bind(undefined, 3), + /* 0x6C */ undefined, // TODO NROUND[ab] + /* 0x6D */ undefined, // TODO NROUND[ab] + /* 0x6E */ undefined, // TODO NROUND[ab] + /* 0x6F */ undefined, // TODO NROUND[ab] + /* 0x70 */ WCVTF, + /* 0x71 */ DELTAP123.bind(undefined, 2), + /* 0x72 */ DELTAP123.bind(undefined, 3), + /* 0x73 */ DELTAC123.bind(undefined, 1), + /* 0x74 */ DELTAC123.bind(undefined, 2), + /* 0x75 */ DELTAC123.bind(undefined, 3), + /* 0x76 */ SROUND, + /* 0x77 */ S45ROUND, + /* 0x78 */ undefined, // TODO JROT[] + /* 0x79 */ undefined, // TODO JROF[] + /* 0x7A */ ROFF, + /* 0x7B */ undefined, + /* 0x7C */ RUTG, + /* 0x7D */ RDTG, + /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though + /* 0x7F */ POP, // actually AA, supposed to do only a pop though + /* 0x80 */ undefined, // TODO FLIPPT + /* 0x81 */ undefined, // TODO FLIPRGON + /* 0x82 */ undefined, // TODO FLIPRGOFF + /* 0x83 */ undefined, + /* 0x84 */ undefined, + /* 0x85 */ SCANCTRL, + /* 0x86 */ SDPVTL.bind(undefined, 0), + /* 0x87 */ SDPVTL.bind(undefined, 1), + /* 0x88 */ GETINFO, + /* 0x89 */ undefined, // TODO IDEF + /* 0x8A */ ROLL, + /* 0x8B */ MAX, + /* 0x8C */ MIN, + /* 0x8D */ SCANTYPE, + /* 0x8E */ INSTCTRL, + /* 0x8F */ undefined, + /* 0x90 */ undefined, + /* 0x91 */ undefined, + /* 0x92 */ undefined, + /* 0x93 */ undefined, + /* 0x94 */ undefined, + /* 0x95 */ undefined, + /* 0x96 */ undefined, + /* 0x97 */ undefined, + /* 0x98 */ undefined, + /* 0x99 */ undefined, + /* 0x9A */ undefined, + /* 0x9B */ undefined, + /* 0x9C */ undefined, + /* 0x9D */ undefined, + /* 0x9E */ undefined, + /* 0x9F */ undefined, + /* 0xA0 */ undefined, + /* 0xA1 */ undefined, + /* 0xA2 */ undefined, + /* 0xA3 */ undefined, + /* 0xA4 */ undefined, + /* 0xA5 */ undefined, + /* 0xA6 */ undefined, + /* 0xA7 */ undefined, + /* 0xA8 */ undefined, + /* 0xA9 */ undefined, + /* 0xAA */ undefined, + /* 0xAB */ undefined, + /* 0xAC */ undefined, + /* 0xAD */ undefined, + /* 0xAE */ undefined, + /* 0xAF */ undefined, + /* 0xB0 */ PUSHB.bind(undefined, 1), + /* 0xB1 */ PUSHB.bind(undefined, 2), + /* 0xB2 */ PUSHB.bind(undefined, 3), + /* 0xB3 */ PUSHB.bind(undefined, 4), + /* 0xB4 */ PUSHB.bind(undefined, 5), + /* 0xB5 */ PUSHB.bind(undefined, 6), + /* 0xB6 */ PUSHB.bind(undefined, 7), + /* 0xB7 */ PUSHB.bind(undefined, 8), + /* 0xB8 */ PUSHW.bind(undefined, 1), + /* 0xB9 */ PUSHW.bind(undefined, 2), + /* 0xBA */ PUSHW.bind(undefined, 3), + /* 0xBB */ PUSHW.bind(undefined, 4), + /* 0xBC */ PUSHW.bind(undefined, 5), + /* 0xBD */ PUSHW.bind(undefined, 6), + /* 0xBE */ PUSHW.bind(undefined, 7), + /* 0xBF */ PUSHW.bind(undefined, 8), + /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), + /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), + /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), + /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), + /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), + /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), + /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), + /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), + /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), + /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), + /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), + /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), + /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), + /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), + /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), + /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), + /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), + /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), + /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), + /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), + /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), + /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), + /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), + /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), + /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), + /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), + /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), + /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), + /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), + /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), + /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), + /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), + /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), + /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), + /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), + /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), + /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), + /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), + /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), + /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), + /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), + /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), + /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), + /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), + /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), + /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), + /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), + /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), + /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), + /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), + /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), + /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), + /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), + /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), + /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), + /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), + /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), + /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), + /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), + /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), + /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), + /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), + /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), + /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) +]; + +/***************************** + Mathematical Considerations +****************************** + +fv ... refers to freedom vector +pv ... refers to projection vector +rp ... refers to reference point +p ... refers to to point being operated on +d ... refers to distance + +SETRELATIVE: +============ + +case freedom vector == x-axis: +------------------------------ + + (pv) + .-' + rpd .-' + .-* + d .-'90°' + .-' ' + .-' ' + *-' ' b + rp ' + ' + ' + p *----------*-------------- (fv) + pm + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b + + y - rpdy = pvns * (x- rpdx) + + y = p.y + + x = rpdx + ( p.y - rpdy ) / pvns + + +case freedom vector == y-axis: +------------------------------ + + * pm + |\ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ b + | \ + | \ + | \ .-' (pv) + | 90° \.-' + | .-'* rpd + | .-' + * *-' d + p rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns ... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + x = p.x + + y = rpdy + pvns * (p.x - rpdx) + + + +generic case: +------------- + + + .'(fv) + .' + .* pm + .' ! + .' . + .' ! + .' . b + .' ! + * . + p ! + 90° . ... (pv) + ...-*-''' + ...---''' rpd + ...---''' d + *--''' + rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + equation of freedom vector line: + fvs ... slope of freedom vector (=fy/fx) + + y - py = fvs * (x - px) + + + on pm both equations are true for same x/y + + y - rpdy = pvns * (x - rpdx) + + y - py = fvs * (x - px) + + form to y and set equal: + + pvns * (x - rpdx) + rpdy = fvs * (x - px) + py + + expand: + + pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py + + switch: + + fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy + + solve for x: + + fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy + + + + fvs * px - pvns * rpdx + rpdy - py + x = ----------------------------------- + fvs - pvns + + and: + + y = fvs * (x - px) + py + + + +INTERPOLATE: +============ + +Examples of point interpolation. + +The weight of the movement of the reference point gets bigger +the further the other reference point is away, thus the safest +option (that is avoiding 0/0 divisions) is to weight the +original distance of the other point by the sum of both distances. + +If the sum of both distances is 0, then move the point by the +arithmetic average of the movement of both reference points. + + + + + (+6) + rp1o *---->*rp1 + . . (+12) + . . rp2o *---------->* rp2 + . . . . + . . . . + . 10 20 . . + |.........|...................| . + . . . + . . (+8) . + po *------>*p . + . . . + . 12 . 24 . + |...........|.......................| + 36 + + +------- + + + + (+10) + rp1o *-------->*rp1 + . . (-10) + . . rp2 *<---------* rpo2 + . . . . + . . . . + . 10 . 30 . . + |.........|.............................| + . . + . (+5) . + po *--->* p . + . . . + . . 20 . + |....|..............| + 5 15 + + +------- + + + (+10) + rp1o *-------->*rp1 + . . + . . + rp2o *-------->*rp2 + + + (+10) + po *-------->* p + +------- + + + (+10) + rp1o *-------->*rp1 + . . + . .(+30) + rp2o *---------------------------->*rp2 + + + (+25) + po *----------------------->* p + + + +vim: set ts=4 sw=4 expandtab: +*****/ + +/** + * Converts a string into a list of tokens. + */ + +/** + * Create a new token + * @param {string} char a single char + */ +function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; +} + +/** + * Create a new context range + * @param {number} startIndex range start index + * @param {number} endOffset range end index offset + * @param {string} contextName owner context name + */ +function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; +} + +/** + * Check context start and end + * @param {string} contextName a unique context name + * @param {function} checkStart a predicate function the indicates a context's start + * @param {function} checkEnd a predicate function the indicates a context's end + */ +function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; +} + +/** + * @typedef ContextParams + * @type Object + * @property {array} context context items + * @property {number} currentIndex current item index + */ + +/** + * Create a context params + * @param {array} context a list of items + * @param {number} currentIndex current item index + */ +function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); +} + +/** + * Create an event instance + * @param {string} eventId event unique id + */ +function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; +} + +/** + * Initialize a core events and auto subscribe required event handlers + * @param {any} events an object that enlists core events handlers + */ +function initializeCoreEvents(events) { + var this$1 = this; + + var coreEvents = [ + 'start', 'end', 'next', 'newToken', 'contextStart', + 'contextEnd', 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' + ]; + + coreEvents.forEach(function (eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); + + if (!!events) { + coreEvents.forEach(function (eventId) { + var event = events[eventId]; + if (typeof event === 'function') { + this$1.events[eventId].subscribe(event); + } + }); + } + var requiresContextUpdate = [ + 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD' + ]; + requiresContextUpdate.forEach(function (eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); +} + +/** + * Converts a string into a list of tokens + * @param {any} events tokenizer core events + */ +function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; + + initializeCoreEvents.call(this, events); +} + +/** + * Sets the state of a token, usually called by a state modifier. + * @param {string} key state item key + * @param {any} value state item value + */ +Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key: key, value: this.state[key] }; + return this.activeState; +}; + +Token.prototype.getState = function (stateId) { + return this.state[stateId] || null; +}; + +/** + * Checks if an index exists in the tokens list. + * @param {number} index token index + */ +Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; +}; + +/** + * Compose and apply a list of operations (replace, update, delete) + * @param {array} RUDs replace, update and delete operations + * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) + */ +Tokenizer.prototype.composeRUD = function (RUDs) { + var this$1 = this; + + var silent = true; + var state = RUDs.map(function (RUD) { return ( + this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) + ); }); + var hasFAILObject = function (obj) { return ( + typeof obj === 'object' && + obj.hasOwnProperty('FAIL') + ); }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; + } + this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); +}; + +/** + * Replace a range of tokens with a list of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {token} tokens a list of tokens to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function (token) { return token instanceof Token; }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, [startIndex, offset].concat(tokens) + ); + if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } + return [replaced, tokens]; + } else { + return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; + } +}; + +/** + * Replace a token with another token + * @param {number} index token index + * @param {token} token a token to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceToken = function (index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { this.dispatch('replaceToken', [index, token]); } + return [replaced[0], token]; + } else { + return { FAIL: 'replaceToken: invalid token or index.' }; + } +}; + +/** + * Removes a range of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } + return tokens; +}; + +/** + * Remove a token at a certain index + * @param {number} index token index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { this.dispatch('removeToken', [token, index]); } + return token; + } else { + return { FAIL: 'removeToken: invalid token index.' }; + } +}; + +/** + * Insert a list of tokens at a certain index + * @param {array} tokens a list of tokens to insert + * @param {number} index insert the list of tokens at index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.insertToken = function (tokens, index, silent) { + var tokenType = tokens.every( + function (token) { return token instanceof Token; } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, [index, 0].concat(tokens) + ); + if (!silent) { this.dispatch('insertToken', [tokens, index]); } + return tokens; + } else { + return { FAIL: 'insertToken: invalid token(s).' }; + } +}; + +/** + * A state modifier that is called on 'newToken' event + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a function to update token state + */ +Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = ( + condition === null || + condition.apply(this, conditionParams) === true + ); + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); + } + }); + this.registeredModifiers.push(modifierId); +}; + +/** + * Subscribe a handler to an event + * @param {function} eventHandler an event handler function + */ +Event.prototype.subscribe = function (eventHandler) { + if (typeof eventHandler === 'function') { + return ((this.subscribers.push(eventHandler)) - 1); + } else { + return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; + } +}; + +/** + * Unsubscribe an event handler + * @param {string} subsId subscription id + */ +Event.prototype.unsubscribe = function (subsId) { + this.subscribers.splice(subsId, 1); +}; + +/** + * Sets context params current value index + * @param {number} index context params current value index + */ +ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); +}; + +/** + * Get an item at an offset from the current value + * example (current value is 3): + * 1 2 [3] 4 5 | items values + * -2 -1 0 1 2 | offset values + * @param {number} offset an offset from current value index + */ +ContextParams.prototype.get = function (offset) { + switch (true) { + case (offset === 0): + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } +}; + +/** + * Converts a context range into a string value + * @param {contextRange} range a context range + */ +Tokenizer.prototype.rangeToText = function (range) { + if (range instanceof ContextRange) { + return ( + this.getRangeTokens(range) + .map(function (token) { return token.char; }).join('') + ); + } +}; + +/** + * Converts all tokens into a string + */ +Tokenizer.prototype.getText = function () { + return this.tokens.map(function (token) { return token.char; }).join(''); +}; + +/** + * Get a context by name + * @param {string} contextName context name to get + */ +Tokenizer.prototype.getContext = function (contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; +}; + +/** + * Subscribes a new event handler to an event + * @param {string} eventName event name to subscribe to + * @param {function} eventHandler a function to be invoked on event + */ +Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; + } +}; + +/** + * Dispatches an event + * @param {string} eventName event name + * @param {any} args event handler arguments + */ +Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; + + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function (subscriber) { + subscriber.apply(this$1, args || []); + }); + } +}; + +/** + * Register a new context checker + * @param {string} contextName a unique context name + * @param {function} contextStartCheck a predicate function that returns true on context start + * @param {function} contextEndCheck a predicate function that returns true on context end + * TODO: call tokenize on registration to update context ranges with the new context. + */ +Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { return { + FAIL: + ("context name '" + contextName + "' is already registered.") + }; } + if (typeof contextStartCheck !== 'function') { return { + FAIL: + "missing context start check." + }; } + if (typeof contextEndCheck !== 'function') { return { + FAIL: + "missing context end check." + }; } + var contextCheckers = new ContextChecker( + contextName, contextStartCheck, contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; +}; + +/** + * Gets a context range tokens + * @param {contextRange} range a context range + */ +Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens + .slice(range.startIndex, endIndex) + ); +}; + +/** + * Gets the ranges of a context + * @param {string} contextName context name + */ +Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + } +}; + +/** + * Resets context ranges to run context update + */ +Tokenizer.prototype.resetContextsRanges = function () { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } + } +}; + +/** + * Updates context ranges + */ +Tokenizer.prototype.updateContextsRanges = function () { + this.resetContextsRanges(); + var chars = this.tokens.map(function (token) { return token.char; }); + for (var i = 0; i < chars.length; i++) { + var contextParams = new ContextParams(chars, i); + this.runContextCheck(contextParams); + } + this.dispatch('updateContextsRanges', [this.registeredContexts]); +}; + +/** + * Sets the end offset of an open range + * @param {number} offset range end offset + * @param {string} contextName context name + */ +Tokenizer.prototype.setEndOffset = function (offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + (ranges.length); + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; +}; + +/** + * Runs a context check on the current context + * @param {contextParams} contextParams current context params + */ +Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + + var index = contextParams.index; + this.contextCheckers.forEach(function (contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch('contextStart', [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = (index - openRange.startIndex) + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch('contextEnd', [contextName, range]); + } + }); +}; + +/** + * Converts a text into a list of tokens + * @param {string} text a text to tokenize + */ +Tokenizer.prototype.tokenize = function (text) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text); + this.dispatch('start'); + for (var i = 0; i < chars.length; i++) { + var char = chars[i]; + var contextParams = new ContextParams(chars, i); + this.dispatch('next', [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch('newToken', [token, contextParams]); + } + this.dispatch('end', [this.tokens]); + return this.tokens; +}; + +// ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ +// ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ +// ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ +// jscs:disable maximumLineLength +/** + * Check if a char is Arabic + * @param {string} c a single char + */ +function isArabicChar(c) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); +} + +/** + * Check if a char is an isolated arabic char + * @param {string} c a single char + */ +function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); +} + +/** + * Check if a char is an Arabic Tashkeel char + * @param {string} c a single char + */ +function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); +} + +/** + * Check if a char is Latin + * @param {string} c a single char + */ +function isLatinChar(c) { + return /[A-z]/.test(c); +} + +/** + * Check if a char is whitespace char + * @param {string} c a single char + */ +function isWhiteSpace(c) { + return /\s/.test(c); +} + +/** + * Query a feature by some of it's properties to lookup a glyph substitution. + */ + +/** + * Create feature query instance + * @param {Font} font opentype font instance + */ +function FeatureQuery(font) { + this.font = font; + this.features = {}; +} + +/** + * @typedef SubstitutionAction + * @type Object + * @property {number} id substitution type + * @property {string} tag feature tag + * @property {any} substitution substitution value(s) + */ + +/** + * Create a substitution action instance + * @param {SubstitutionAction} action + */ +function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; +} + +/** + * Lookup a coverage table + * @param {number} glyphIndex glyph index + * @param {CoverageTable} coverage coverage table + */ +function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { return -1; } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); + + case 2: + var ranges = coverage.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } + } + break; + default: + return -1; // not found + } + return -1; +} + +/** + * Handle a single substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return glyphIndex + subtable.deltaGlyphId; +} + +/** + * Handle a single substitution - format 2 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.substitute[substituteIndex]; +} + +/** + * Lookup a list of coverage tables + * @param {any} coverageList a list of coverage tables + * @param {ContextParams} contextParams context params to lookup + */ +function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i = 0; i < coverageList.length; i++) { + var coverage = coverageList[i]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { return -1; } + return lookupList; +} + +/** + * Handle chaining context substitution - format 3 + * @param {ContextParams} contextParams context params to lookup + */ +function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = ( + subtable.inputCoverage.length + + subtable.lookaheadCoverage.length + + subtable.backtrackCoverage.length + ); + if (contextParams.context.length < lookupsCount) { return []; } + // INPUT LOOKUP // + var inputLookups = lookupCoverageList( + subtable.inputCoverage, contextParams + ); + if (inputLookups === -1) { return []; } + // LOOKAHEAD LOOKUP // + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, lookaheadParams + ); + // BACKTRACK LOOKUP // + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, backtrackParams + ); + var contextRulesMatch = ( + inputLookups.length === subtable.inputCoverage.length && + lookaheadLookups.length === subtable.lookaheadCoverage.length && + backtrackLookups.length === subtable.backtrackCoverage.length + ); + var substitutions = []; + if (contextRulesMatch) { + for (var i = 0; i < subtable.lookupRecords.length; i++) { + var lookupRecord = subtable.lookupRecords[i]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === '12') { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { substitutions.push(substitution); } + } + } + } + } + } + return substitutions; +} + +/** + * Handle ligature substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function ligatureSubstitutionFormat1(contextParams, subtable) { + // COVERAGE LOOKUP // + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { return null; } + // COMPONENTS LOOKUP + // (!) note, components are ordered in the written direction. + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l = 0; l < ligature.components.length; l++) { + var lookaheadItem = contextParams.lookahead[l]; + var component = ligature.components[l]; + if (lookaheadItem !== component) { break; } + if (l === ligature.components.length - 1) { return ligature; } + } + } + return null; +} + +/** + * Handle decomposition substitution - format 1 + * @param {number} glyphIndex glyph index + * @param {any} subtable subtable + */ +function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.sequences[substituteIndex]; +} + +/** + * Get default script features indexes + */ +FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === 'DFLT') { return ( + script.script.defaultLangSys.featureIndexes + ); } + } + return []; +}; + +/** + * Get feature indexes of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { return []; } + if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } + var scripts = this.font.tables.gsub.scripts; + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); +}; + +/** + * Map a feature tag to a gsub feature + * @param {any} features gsub features + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { + var tags = {}; + for (var i = 0; i < features.length; i++) { + var tag = features[i].tag; + var feature = features[i].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; +}; + +/** + * Get features of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { return features; } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { return null; } + var gsub = this.font.tables.gsub; + features = featuresIndexes.map(function (index) { return gsub.features[index]; }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; +}; + +/** + * Get substitution type + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; +}; + +/** + * Get lookup method + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case '11': + return function (glyphIndex) { return singleSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + case '12': + return function (glyphIndex) { return singleSubstitutionFormat2.apply( + this$1, [glyphIndex, subtable] + ); }; + case '63': + return function (contextParams) { return chainingSubstitutionFormat3.apply( + this$1, [contextParams, subtable] + ); }; + case '41': + return function (contextParams) { return ligatureSubstitutionFormat1.apply( + this$1, [contextParams, subtable] + ); }; + case '21': + return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + default: + throw new Error( + "lookupType: " + (lookupTable.lookupType) + " - " + + "substFormat: " + (subtable.substFormat) + " " + + "is not yet supported" + ); + } +}; + +/** + * [ LOOKUP TYPES ] + * ------------------------------- + * Single 1; + * Multiple 2; + * Alternate 3; + * Ligature 4; + * Context 5; + * ChainingContext 6; + * ExtensionSubstitution 7; + * ReverseChainingContext 8; + * ------------------------------- + * + */ + +/** + * @typedef FQuery + * @type Object + * @param {string} tag feature tag + * @param {string} script feature script + * @param {ContextParams} contextParams context params + */ + +/** + * Lookup a feature using a query parameters + * @param {FQuery} query feature query + */ +FeatureQuery.prototype.lookupFeature = function (query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, script: query.script + }); + if (!feature) { return new Error( + "font '" + (this.font.names.fullName.en) + "' " + + "doesn't support feature '" + (query.tag) + "' " + + "for script '" + (query.script) + "'." + ); } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l = 0; l < lookups.length; l++) { + var lookupTable = lookups[l]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = (void 0); + switch (substType) { + case '11': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, tag: query.tag, substitution: substitution + })); + } + break; + case '12': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, tag: query.tag, substitution: substitution + })); + } + break; + case '63': + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, tag: query.tag, substitution: substitution + })); + } + break; + case '41': + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, tag: query.tag, substitution: substitution + })); + } + break; + case '21': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, tag: query.tag, substitution: substitution + })); + } + break; + } + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { continue; } + substitution = null; + } + } + return substitutions.length ? substitutions : null; +}; + +/** + * Checks if a font supports a specific features + * @param {FQuery} query feature query object + */ +FeatureQuery.prototype.supports = function (query) { + if (!query.script) { return false; } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { return supportedScript; } + var supportedFeature = ( + this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) + ); + return supportedScript && supportedFeature; +}; + +/** + * Get lookup table subtables + * @param {any} lookupTable lookup table + */ +FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { + return lookupTable.subtables || null; +}; + +/** + * Get lookup table by index + * @param {number} index lookup table index + */ +FeatureQuery.prototype.getLookupByIndex = function (index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; +}; + +/** + * Get lookup tables for a feature + * @param {string} feature + */ +FeatureQuery.prototype.getFeatureLookups = function (feature) { + // TODO: memoize + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); +}; + +/** + * Query a feature by it's properties + * @param {any} query an object that describes the properties of a query + */ +FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { return { FAIL: "No font was found"}; } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); + } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { return ( + { FAIL: ("No feature for script " + (query.script))} + ); } + if (!scriptFeatures.tags[query.tag]) { return null; } + return this.features[query.script].tags[query.tag]; +}; + +/** + * Arabic word context checkers + */ + +function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + (prevChar === null && isArabicChar(char)) || + // ? arabic char preceded with a non arabic char + (!isArabicChar(prevChar) && isArabicChar(char)) + ); +} + +function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + (nextChar === null) || + // ? next char is not arabic + (!isArabicChar(nextChar)) + ); +} + +var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck +}; + +/** + * Arabic sentence context checkers + */ + +function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && + !isArabicChar(prevChar) + ); +} + +function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { return true; } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = ( + contextParams.lookahead.some( + function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } + ) + ); + if (!arabicCharAhead) { return true; } + } + break; + default: + return false; + } +} + +var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck +}; + +/** + * Apply single substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} + +/** + * Apply single substitution format 2 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} + +/** + * Apply chaining context substitution format 3 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function (subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); +} + +/** + * Apply ligature substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i = 0; i < compsCount; i++) { + token = tokens[index + i + 1]; + token.setState('deleted', true); + } +} + +/** + * Supported substitutions + */ +var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 +}; + +/** + * Apply substitutions to a list of tokens + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); + } +} + +/** + * Apply Arabic presentation forms to a range of tokens + */ + +/** + * Check if a char can be connected to it's preceding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i = backtrack.length - 1; i >= 0; i--) { + var prevChar = backtrack[i]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { return true; } + if (isolated) { return false; } + } + return false; +} + +/** + * Check if a char can be connected to it's proceeding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { return false; } + for (var i = 0; i < charContextParams.lookahead.length; i++) { + var nextChar = charContextParams.lookahead[i]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { return true; } + } + return false; +} + +/** + * Apply arabic presentation forms to a list of tokens + * @param {ContextRange} range a range of tokens + */ +function arabicPresentationForms(range) { + var this$1 = this; + + var script = 'arab'; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { return; } + var contextParams = new ContextParams( + tokens.map(function (token) { return token.getState('glyphIndex'); } + ), 0); + var charContextParams = new ContextParams( + tokens.map(function (token) { return token.char; } + ), 0); + tokens.forEach(function (token, index) { + if (isTashkeelArabicChar(token.char)) { return; } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) + if (willConnectPrev(charContextParams)) { CONNECT |= 1; } + if (willConnectNext(charContextParams)) { CONNECT |= 2; } + var tag; + switch (CONNECT) { + case 1: (tag = 'fina'); break; + case 2: (tag = 'init'); break; + case 3: (tag = 'medi'); break; + } + if (tags.indexOf(tag) === -1) { return; } + var substitutions = this$1.query.lookupFeature({ + tag: tag, script: script, contextParams: contextParams + }); + if (substitutions instanceof Error) { return console.info(substitutions.message); } + substitutions.forEach(function (action, index) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index); + contextParams.context[index] = action.substitution; + } + }); + }); +} + +/** + * Apply Arabic required ligatures feature to a range of tokens + */ + +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} + +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function arabicRequiredLigatures(range) { + var this$1 = this; + + var script = 'arab'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'rlig', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams(tokens); + } + }); +} + +/** + * Latin word context checkers + */ + +function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + (prevChar === null && isLatinChar(char)) || + // ? latin char preceded with a non latin char + (!isLatinChar(prevChar) && isLatinChar(char)) + ); +} + +function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + (nextChar === null) || + // ? next char is not latin + (!isLatinChar(nextChar)) + ); +} + +var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck +}; + +/** + * Apply Latin ligature feature to a range of tokens + */ + +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams$1(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} + +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function latinLigature(range) { + var this$1 = this; + + var script = 'latn'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'liga', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams$1(tokens); + } + }); +} + +/** + * Infer bidirectional properties for a given text and apply + * the corresponding layout rules. + */ + +/** + * Create Bidi. features + * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' + */ +function Bidi(baseDir) { + this.baseDir = baseDir || 'ltr'; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; +} + +/** + * Sets Bidi text + * @param {string} text a text input + */ +Bidi.prototype.setText = function (text) { + this.text = text; +}; + +/** + * Store essential context checks: + * arabic word check for applying gsub features + * arabic sentence check for adjusting arabic layout + */ +Bidi.prototype.contextChecks = ({ + latinWordCheck: latinWordCheck, + arabicWordCheck: arabicWordCheck, + arabicSentenceCheck: arabicSentenceCheck +}); + +/** + * Register arabic word check + */ +function registerContextChecker(checkId) { + var check = this.contextChecks[(checkId + "Check")]; + return this.tokenizer.registerContextChecker( + checkId, check.startCheck, check.endCheck + ); +} + +/** + * Perform pre tokenization procedure then + * tokenize text input + */ +function tokenizeText() { + registerContextChecker.call(this, 'latinWord'); + registerContextChecker.call(this, 'arabicWord'); + registerContextChecker.call(this, 'arabicSentence'); + return this.tokenizer.tokenize(this.text); +} + +/** + * Reverse arabic sentence layout + * TODO: check base dir before applying adjustments - priority low + */ +function reverseArabicSentences() { + var this$1 = this; + + var ranges = this.tokenizer.getContextRanges('arabicSentence'); + ranges.forEach(function (range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); +} + +/** + * Register supported features tags + * @param {script} script script tag + * @param {Array} tags features tags list + */ +Bidi.prototype.registerFeatures = function (script, tags) { + var this$1 = this; + + var supportedTags = tags.filter( + function (tag) { return this$1.query.supports({script: script, tag: tag}); } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = + this.featuresTags[script].concat(supportedTags); + } +}; + +/** + * Apply GSUB features + * @param {Array} tagsList a list of features tags + * @param {string} script a script tag + * @param {Font} font opentype font instance + */ +Bidi.prototype.applyFeatures = function (font, features) { + if (!font) { throw new Error( + 'No valid font was provided to apply features' + ); } + if (!this.query) { this.query = new FeatureQuery(font); } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({script: feature.script})) { continue; } + this.registerFeatures(feature.script, feature.tags); + } +}; + +/** + * Register a state modifier + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a modifier function to set token state + */ +Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); +}; + +/** + * Check if 'glyphIndex' is registered + */ +function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { + throw new Error( + 'glyphIndex modifier is required to apply ' + + 'arabic presentation features.' + ); + } +} + +/** + * Apply arabic presentation forms features + */ +function applyArabicPresentationForms() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicPresentationForms.call(this$1, range); + }); +} + +/** + * Apply required arabic ligatures + */ +function applyArabicRequireLigatures() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('rlig') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicRequiredLigatures.call(this$1, range); + }); +} + +/** + * Apply required arabic ligatures + */ +function applyLatinLigatures() { + var this$1 = this; + + var script = 'latn'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('liga') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('latinWord'); + ranges.forEach(function (range) { + latinLigature.call(this$1, range); + }); +} + +/** + * Check if a context is registered + * @param {string} contextId context id + */ +Bidi.prototype.checkContextReady = function (contextId) { + return !!this.tokenizer.getContext(contextId); +}; + +/** + * Apply features to registered contexts + */ +Bidi.prototype.applyFeaturesToContexts = function () { + if (this.checkContextReady('arabicWord')) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); + } + if (this.checkContextReady('latinWord')) { + applyLatinLigatures.call(this); + } + if (this.checkContextReady('arabicSentence')) { + reverseArabicSentences.call(this); + } +}; + +/** + * process text input + * @param {string} text an input text + */ +Bidi.prototype.processText = function(text) { + if (!this.text || this.text !== text) { + this.setText(text); + tokenizeText.call(this); + this.applyFeaturesToContexts(); + } +}; + +/** + * Process a string of text to identify and adjust + * bidirectional text entities. + * @param {string} text input text + */ +Bidi.prototype.getBidiText = function (text) { + this.processText(text); + return this.tokenizer.getText(); +}; + +/** + * Get the current state index of each token + * @param {text} text an input text + */ +Bidi.prototype.getTextGlyphs = function (text) { + this.processText(text); + var indexes = []; + for (var i = 0; i < this.tokenizer.tokens.length; i++) { + var token = this.tokenizer.tokens[i]; + if (token.state.deleted) { continue; } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; +}; + +// The Font object + +/** + * @typedef FontOptions + * @type Object + * @property {Boolean} empty - whether to create a new empty font + * @property {string} familyName + * @property {string} styleName + * @property {string=} fullName + * @property {string=} postScriptName + * @property {string=} designer + * @property {string=} designerURL + * @property {string=} manufacturer + * @property {string=} manufacturerURL + * @property {string=} license + * @property {string=} licenseURL + * @property {string=} version + * @property {string=} description + * @property {string=} copyright + * @property {string=} trademark + * @property {Number} unitsPerEm + * @property {Number} ascender + * @property {Number} descender + * @property {Number} createdTimestamp + * @property {string=} weightClass + * @property {string=} widthClass + * @property {string=} fsSelection + */ + +/** + * A Font represents a loaded OpenType font file. + * It contains a set of glyphs and methods to draw text on a drawing context, + * or to get a path representing the text. + * @exports opentype.Font + * @class + * @param {FontOptions} + * @constructor + */ +function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + + if (!options.empty) { + // Check that we've provided the minimum set of names. + checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); + checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); + checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); + checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); + checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.names = { + fontFamily: {en: options.familyName || ' '}, + fontSubfamily: {en: options.styleName || ' '}, + fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, + // postScriptName may not contain any whitespace + postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, + designer: {en: options.designer || ' '}, + designerURL: {en: options.designerURL || ' '}, + manufacturer: {en: options.manufacturer || ' '}, + manufacturerURL: {en: options.manufacturerURL || ' '}, + license: {en: options.license || ' '}, + licenseURL: {en: options.licenseURL || ' '}, + version: {en: options.version || 'Version 0.1'}, + description: {en: options.description || ' '}, + copyright: {en: options.copyright || ' '}, + trademark: {en: options.trademark || ' '} + }; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign({ + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + }, options.tables.os2) + }); + } + + this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; + + // needed for low memory mode only. + this._push = null; + this._hmtxTableData = {}; + + Object.defineProperty(this, 'hinting', { + get: function() { + if (this._hinting) { return this._hinting; } + if (this.outlinesFormat === 'truetype') { + return (this._hinting = new Hinting(this)); + } + } + }); +} + +/** + * Check if the font has a glyph for the given character. + * @param {string} + * @return {Boolean} + */ +Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; +}; + +/** + * Convert the given character to a single glyph index. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {Number} + */ +Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); +}; + +/** + * Convert the given character to a single Glyph object. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +/** + * Update features + * @param {any} options features options + */ +Font.prototype.updateFeatures = function (options) { + // TODO: update all features options not only 'latn'. + return this.defaultRenderOptions.features.map(function (feature) { + if (feature.script === 'latn') { + return { + script: 'latn', + tags: feature.tags.filter(function (tag) { return options[tag]; }) + }; + } else { + return feature; + } + }); +}; + +/** + * Convert the given text to a list of Glyph objects. + * Note that there is no strict one-to-one mapping between characters and + * glyphs, so the list of returned glyphs can be larger or smaller than the + * length of the given string. + * @param {string} + * @param {GlyphRenderOptions} [options] + * @return {opentype.Glyph[]} + */ +Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; + + + var bidi = new Bidi(); + + // Create and register 'glyphIndex' state modifier + var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; + bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); + + // roll-back to default features + var features = options ? + this.updateFeatures(options.features) : + this.defaultRenderOptions.features; + + bidi.applyFeatures(this, features); + + var indexes = bidi.getTextGlyphs(s); + + var length = indexes.length; + + // convert glyph indexes to glyph objects + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i = 0; i < length; i += 1) { + glyphs[i] = this.glyphs.get(indexes[i]) || notdef; + } + return glyphs; +}; + +/** + * @param {string} + * @return {Number} + */ +Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); +}; + +/** + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nameToGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +/** + * @param {Number} + * @return {String} + */ +Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); +}; + +/** + * Retrieve the value of the kerning pair between the left glyph (or its index) + * and the right glyph (or its index). If no kerning pair is found, return 0. + * The kerning value gets added to the advance width when calculating the spacing + * between glyphs. + * For GPOS kerning, this method uses the default script and language, which covers + * most use cases. To have greater control, use font.position.getKerningValue . + * @param {opentype.Glyph} leftGlyph + * @param {opentype.Glyph} rightGlyph + * @return {Number} + */ +Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); + } + // "kern" table + return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; +}; + +/** + * @typedef GlyphRenderOptions + * @type Object + * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. + * See https://www.microsoft.com/typography/otspec/scripttags.htm + * @property {string} [language='dflt'] - language system used to determine which features to apply. + * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx + * @property {boolean} [kerning=true] - whether to include kerning values + * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. + * See https://www.microsoft.com/typography/otspec/featuretags.htm + */ +Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] +}; + +/** + * Helper function that invokes the given callback for each glyph in the given text. + * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text + * @param {string} text - The text to apply. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @param {Function} callback + */ +Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables(script, options.language); + } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback.call(this, glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (options.kerning && i < glyphs.length - 1) { + // We should apply position adjustment lookups in a more generic way. + // Here we only use the xAdvance value. + var kerningValue = kerningLookups ? + this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : + this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + + if (options.letterSpacing) { + x += options.letterSpacing * fontSize; + } else if (options.tracking) { + x += (options.tracking / 1000) * fontSize; + } + } + return x; +}; + +/** + * Create a Path object that represents the given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path} + */ +Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + }); + return fullPath; +}; + +/** + * Create an array of Path objects that represent the glyphs of a given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path[]} + */ +Font.prototype.getPaths = function(text, x, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + }); + + return glyphPaths; +}; + +/** + * Returns the advance width of a text. + * + * This is something different than Path.getBoundingBox() as for example a + * suffixed whitespace increases the advanceWidth but not the bounding box + * or an overhanging letter like a calligraphic 'f' might have a quite larger + * bounding box than its advance width. + * + * This corresponds to canvas2dContext.measureText(text).width + * + * @param {string} text - The text to create. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return advance width + */ +Font.prototype.getAdvanceWidth = function(text, fontSize, options) { + return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); +}; + +/** + * Draw the text on the given drawing context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); +}; + +/** + * Draw the points of all glyphs in the text. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); +}; + +/** + * Draw lines indicating important font measurements for all glyphs in the text. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); +}; + +/** + * @param {string} + * @return {string} + */ +Font.prototype.getEnglishName = function(name) { + var translations = this.names[name]; + if (translations) { + return translations.en; + } +}; + +/** + * Validate + */ +Font.prototype.validate = function() { + var _this = this; + + function assert(predicate, message) { + } + + function assertNamePresent(name) { + var englishName = _this.getEnglishName(name); + assert(englishName && englishName.trim().length > 0); + } + + // Identification information + assertNamePresent('fontFamily'); + assertNamePresent('weightName'); + assertNamePresent('manufacturer'); + assertNamePresent('copyright'); + assertNamePresent('version'); + + // Dimension information + assert(this.unitsPerEm > 0); +}; + +/** + * Convert the font object to a SFNT data structure. + * This structure contains all the necessary tables and metadata to create a binary OTF file. + * @return {opentype.Table} + */ +Font.prototype.toTables = function() { + return sfnt.fontToTable(this); +}; +/** + * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. + */ +Font.prototype.toBuffer = function() { + console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); + return this.toArrayBuffer(); +}; +/** + * Converts a `opentype.Font` into an `ArrayBuffer` + * @return {ArrayBuffer} + */ +Font.prototype.toArrayBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; +}; + +/** + * Initiate a download of the OpenType font. + */ +Font.prototype.download = function(fileName) { + var familyName = this.getEnglishName('fontFamily'); + var styleName = this.getEnglishName('fontSubfamily'); + fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; + var arrayBuffer = this.toArrayBuffer(); + + if (isBrowser()) { + window.URL = window.URL || window.webkitURL; + + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); + } else { + console.warn('Font file could not be downloaded. Try using a different browser.'); + } + } else { + var fs = require('fs'); + var buffer = arrayBufferToNodeBuffer(arrayBuffer); + fs.writeFileSync(fileName, buffer); + } +}; +/** + * @private + */ +Font.prototype.fsSelectionValues = { + ITALIC: 0x001, //1 + UNDERSCORE: 0x002, //2 + NEGATIVE: 0x004, //4 + OUTLINED: 0x008, //8 + STRIKEOUT: 0x010, //16 + BOLD: 0x020, //32 + REGULAR: 0x040, //64 + USER_TYPO_METRICS: 0x080, //128 + WWS: 0x100, //256 + OBLIQUE: 0x200 //512 +}; + +/** + * @private + */ +Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 +}; + +/** + * @private + */ +Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 +}; + +// The `fvar` table stores font variation axes and instances. + +function addName(name, names) { + var nameString = JSON.stringify(name); + var nameID = 256; + for (var nameKey in names) { + var n = parseInt(nameKey); + if (!n || n < 256) { + continue; + } + + if (JSON.stringify(names[nameKey]) === nameString) { + return n; + } + + if (nameID <= n) { + nameID = n + 1; + } + } + + names[nameID] = name; + return nameID; +} + +function makeFvarAxis(n, axis, names) { + var nameID = addName(axis.name, names); + return [ + {name: 'tag_' + n, type: 'TAG', value: axis.tag}, + {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, + {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, + {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, + {name: 'flags_' + n, type: 'USHORT', value: 0}, + {name: 'nameID_' + n, type: 'USHORT', value: nameID} + ]; +} + +function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip('uShort', 1); // reserved for flags; no values defined + axis.name = names[p.parseUShort()] || {}; + return axis; +} + +function makeFvarInstance(n, inst, axes, names) { + var nameID = addName(inst.name, names); + var fields = [ + {name: 'nameID_' + n, type: 'USHORT', value: nameID}, + {name: 'flags_' + n, type: 'USHORT', value: 0} + ]; + + for (var i = 0; i < axes.length; ++i) { + var axisTag = axes[i].tag; + fields.push({ + name: 'axis_' + n + ' ' + axisTag, + type: 'FIXED', + value: inst.coordinates[axisTag] << 16 + }); + } + + return fields; +} + +function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip('uShort', 1); // reserved for flags; no values defined + + inst.coordinates = {}; + for (var i = 0; i < axes.length; ++i) { + inst.coordinates[axes[i].tag] = p.parseFixed(); + } + + return inst; +} + +function makeFvarTable(fvar, names) { + var result = new table.Table('fvar', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'offsetToData', type: 'USHORT', value: 0}, + {name: 'countSizePairs', type: 'USHORT', value: 2}, + {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, + {name: 'axisSize', type: 'USHORT', value: 20}, + {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, + {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} + ]); + result.offsetToData = result.sizeOf(); + + for (var i = 0; i < fvar.axes.length; i++) { + result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + } + + for (var j = 0; j < fvar.instances.length; j++) { + result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + } + + return result; +} + +function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); + var offsetToData = p.parseOffset16(); + // Skip countSizePairs. + p.skip('uShort', 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); + + var axes = []; + for (var i = 0; i < axisCount; i++) { + axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); + } + + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); + } + + return {axes: axes, instances: instances}; +} + +var fvar = { make: makeFvarTable, parse: parseFvarTable }; + +// The `GDEF` table contains various glyph properties + +var attachList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) + }; +}; + +var caretValue = function() { + var format = this.parseUShort(); + check.argument(format === 1 || format === 2 || format === 3, + 'Unsupported CaretValue table version.'); + if (format === 1) { + return { coordinate: this.parseShort() }; + } else if (format === 2) { + return { pointindex: this.parseShort() }; + } else if (format === 3) { + // Device / Variation Index tables unsupported + return { coordinate: this.parseShort() }; + } +}; + +var ligGlyph = function() { + return this.parseList(Parser.pointer(caretValue)); +}; + +var ligCaretList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) + }; +}; + +var markGlyphSets = function() { + this.parseUShort(); // Version + return this.parseList(Parser.pointer(Parser.coverage)); +}; + +function parseGDEFTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, + 'Unsupported GDEF table version.'); + var gdef = { + version: tableVersion, + classDef: p.parsePointer(Parser.classDef), + attachList: p.parsePointer(attachList), + ligCaretList: p.parsePointer(ligCaretList), + markAttachClassDef: p.parsePointer(Parser.classDef) + }; + if (tableVersion >= 1.2) { + gdef.markGlyphSets = p.parsePointer(markGlyphSets); + } + return gdef; +} +var gdef = { parse: parseGDEFTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable +// this = Parser instance +subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { + return { + posFormat: 1, + coverage: this.parsePointer(Parser.coverage), + value: this.parseValueRecord() + }; + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() + }; + } + check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); +}; + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable +subtableParsers$1[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + // Adjustments for Glyph Pairs + return { + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + pairSets: this.parseList(Parser.pointer(Parser.list(function() { + return { // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }))) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + classDef1: classDef1, + classDef2: classDef2, + class1Count: class1Count, + class2Count: class2Count, + classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + })) + }; + } +}; + +subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; +subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; +subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; +subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; +subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; +subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; +subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos +function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); + + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; + } + +} + +// GPOS Writing ////////////////////////////////////////////// +// NOT SUPPORTED +var subtableMakers$1 = new Array(10); + +function makeGposTable(gpos) { + return new table.Table('GPOS', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} + ]); +} + +var gpos = { parse: parseGposTable, make: makeGposTable }; + +// The `kern` table contains kerning pairs. + +function parseWindowsKernTable(p) { + var pairs = {}; + // Skip nTables. + p.skip('uShort'); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subtableLength, subtableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + return pairs; +} + +function parseMacKernTable(p) { + var pairs = {}; + // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. + // Skip the rest. + p.skip('uShort'); + var nTables = p.parseULong(); + //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); + if (nTables > 1) { + console.warn('Only the first kern subtable is supported.'); + } + p.skip('uLong'); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 0xFF; + p.skip('uShort'); + if (subtableVersion === 0) { + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + } + return pairs; +} + +// Parse the `kern` table which contains kerning pairs. +function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error('Unsupported kern table version (' + tableVersion + ').'); + } +} + +var kern = { parse: parseKernTable }; + +// The `loca` table stores the offsets to the locations of the glyphs in the font. + +// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, +// relative to the beginning of the glyphData table. +// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) +// The loca table has two versions: a short version where offsets are stored as uShorts, and a long +// version where offsets are stored as uLongs. The `head` table specifies which version to use +// (under indexToLocFormat). +function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } + + glyphOffsets.push(glyphOffset); + } + + return glyphOffsets; +} + +var loca = { parse: parseLocaTable }; + +// opentype.js + +/** + * The opentype library. + * @namespace opentype + */ + +// File loaders ///////////////////////////////////////////////////////// +/** + * Loads a font from a file. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} path - The path of the file + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromFile(path, callback) { + var fs = require('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } + + callback(null, nodeBufferToArrayBuffer(buffer)); + }); +} +/** + * Loads a font from a URL. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} url - The URL of the font file. + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.response) { + return callback(null, request.response); + } else { + return callback('Font could not be loaded: ' + request.statusText); + } + }; + + request.onerror = function () { + callback('Font could not be loaded'); + }; + + request.send(); +} + +// Table Directory Entries ////////////////////////////////////////////// +/** + * Parses OpenType table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); + p += 16; + } + + return tableEntries; +} + +/** + * Parses WOFF table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; // offset to the first table directory entry. + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = (void 0); + if (compLength < origLength) { + compression = 'WOFF'; + } else { + compression = false; + } + + tableEntries.push({tag: tag, offset: offset, compression: compression, + compressedLength: compLength, length: origLength}); + p += 20; + } + + return tableEntries; +} + +/** + * @typedef TableData + * @type Object + * @property {DataView} data - The DataView + * @property {number} offset - The data offset. + */ + +/** + * @param {DataView} + * @param {Object} + * @return {TableData} + */ +function uncompressTable(data, tableEntry) { + if (tableEntry.compression === 'WOFF') { + var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); + var outBuffer = new Uint8Array(tableEntry.length); + tinyInflate(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); + } + + var view = new DataView(outBuffer.buffer, 0); + return {data: view, offset: 0}; + } else { + return {data: data, offset: tableEntry.offset}; + } +} + +// Public API /////////////////////////////////////////////////////////// + +/** + * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. + * Throws an error if the font could not be parsed. + * @param {ArrayBuffer} + * @param {Object} opt - options for parsing + * @return {opentype.Font} + */ +function parseBuffer(buffer, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + + var indexToLocFormat; + var ltagTable; + + // Since the constructor can also be called to create new fonts from scratch, we indicate this + // should be an empty font that we'll fill with our own data. + var font = new Font({empty: true}); + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { + font.outlinesFormat = 'truetype'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'OTTO') { + font.outlinesFormat = 'cff'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'wOFF') { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { + font.outlinesFormat = 'truetype'; + } else if (flavor === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType flavor ' + signature); + } + + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); + } else { + throw new Error('Unsupported OpenType signature ' + signature); + } + + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gdefTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var nameTableEntry; + var metaTableEntry; + var p; + + for (var i = 0; i < numTables; i += 1) { + var tableEntry = tableEntries[i]; + var table = (void 0); + switch (tableEntry.tag) { + case 'cmap': + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case 'cvt ' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case 'fvar': + fvarTableEntry = tableEntry; + break; + case 'fpgm' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case 'head': + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxTableEntry = tableEntry; + break; + case 'ltag': + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case 'COLR': + table = uncompressTable(data, tableEntry); + font.tables.colr = colr.parse(table.data, table.offset); + break; + case 'CPAL': + table = uncompressTable(data, tableEntry); + font.tables.cpal = cpal.parse(table.data, table.offset); + break; + case 'maxp': + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + nameTableEntry = tableEntry; + break; + case 'OS/2': + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case 'post': + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + font.glyphNames = new GlyphNames(font.tables.post); + break; + case 'prep' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case 'glyf': + glyfTableEntry = tableEntry; + break; + case 'loca': + locaTableEntry = tableEntry; + break; + case 'CFF ': + cffTableEntry = tableEntry; + break; + case 'kern': + kernTableEntry = tableEntry; + break; + case 'GDEF': + gdefTableEntry = tableEntry; + break; + case 'GPOS': + gposTableEntry = tableEntry; + break; + case 'GSUB': + gsubTableEntry = tableEntry; + break; + case 'meta': + metaTableEntry = tableEntry; + break; + } + } + + var nameTable = uncompressTable(data, nameTableEntry); + font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); + font.names = font.tables.name; + + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); + } + + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); + addGlyphNames(font, opt); + + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } + + if (gdefTableEntry) { + var gdefTable = uncompressTable(data, gdefTableEntry); + font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); + } + + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } + + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } + + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); + } + + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; + } + + return font; +} + +/** + * Asynchronously load the font from a URL or a filesystem. When done, call the callback + * with two arguments `(err, font)`. The `err` will be null on success, + * the `font` is a Font object. + * We use the node.js callback convention so that + * opentype.js can integrate with frameworks like async.js. + * @alias opentype.load + * @param {string} url - The URL of the font to load. + * @param {Function} callback - The callback. + */ +function load(url, callback, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + var isNode = typeof window === 'undefined'; + var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; + + return new Promise(function (resolve, reject) { + loadFn(url, function(err, arrayBuffer) { + if (err) { + if (callback) { + return callback(err); + } else { + reject(err); + } + } + var font; + try { + font = parseBuffer(arrayBuffer, opt); + } catch (e) { + if (callback) { + return callback(e, null); + } else { + reject(e); + } + } + if (callback) { + return callback(null, font); + } else { + resolve(font); + } + }); + }); +} + +/** + * Synchronously load the font from a URL or file. + * When done, returns the font object or throws an error. + * @alias opentype.loadSync + * @param {string} url - The URL of the font to load. + * @param {Object} opt - opt.lowMemory + * @return {opentype.Font} + */ +function loadSync(url, opt) { + var fs = require('fs'); + var buffer = fs.readFileSync(url); + return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); +} + +var opentype = /*#__PURE__*/Object.freeze({ + __proto__: null, + Font: Font, + Glyph: Glyph, + Path: Path, + BoundingBox: BoundingBox, + _parse: parse, + parse: parseBuffer, + load: load, + loadSync: loadSync +}); + +export default opentype; +export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse }; diff --git a/jsm/libs/potpack.module.js b/jsm/libs/potpack.module.js new file mode 100644 index 0000000..efba9a4 --- /dev/null +++ b/jsm/libs/potpack.module.js @@ -0,0 +1,125 @@ +/** + * potpack - by [@mourner](https://github.com/mourner) + * + * A tiny JavaScript function for packing 2D rectangles into a near-square container, + * which is useful for generating CSS sprites and WebGL textures. Similar to + * [shelf-pack](https://github.com/mapbox/shelf-pack), but static (you can't add items + * once a layout is generated), and aims for maximal space utilization. + * + * A variation of algorithms used in [rectpack2D](https://github.com/TeamHypersomnia/rectpack2D) + * and [bin-pack](https://github.com/bryanburgers/bin-pack), which are in turn based + * on [this article by Blackpawn](http://blackpawn.com/texts/lightmaps/default.html). + * + * @license + * ISC License + * + * Copyright (c) 2018, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +function potpack(boxes) { + + // calculate total box area and maximum box width + let area = 0; + let maxWidth = 0; + + for (const box of boxes) { + area += box.w * box.h; + maxWidth = Math.max(maxWidth, box.w); + } + + // sort the boxes for insertion by height, descending + boxes.sort((a, b) => b.h - a.h); + + // aim for a squarish resulting container, + // slightly adjusted for sub-100% space utilization + const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth); + + // start with a single empty space, unbounded at the bottom + const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}]; + + let width = 0; + let height = 0; + + for (const box of boxes) { + // look through spaces backwards so that we check smaller spaces first + for (let i = spaces.length - 1; i >= 0; i--) { + const space = spaces[i]; + + // look for empty spaces that can accommodate the current box + if (box.w > space.w || box.h > space.h) continue; + + // found the space; add the box to its top-left corner + // |-------|-------| + // | box | | + // |_______| | + // | space | + // |_______________| + box.x = space.x; + box.y = space.y; + + height = Math.max(height, box.y + box.h); + width = Math.max(width, box.x + box.w); + + if (box.w === space.w && box.h === space.h) { + // space matches the box exactly; remove it + const last = spaces.pop(); + if (i < spaces.length) spaces[i] = last; + + } else if (box.h === space.h) { + // space matches the box height; update it accordingly + // |-------|---------------| + // | box | updated space | + // |_______|_______________| + space.x += box.w; + space.w -= box.w; + + } else if (box.w === space.w) { + // space matches the box width; update it accordingly + // |---------------| + // | box | + // |_______________| + // | updated space | + // |_______________| + space.y += box.h; + space.h -= box.h; + + } else { + // otherwise the box splits the space into two spaces + // |-------|-----------| + // | box | new space | + // |_______|___________| + // | updated space | + // |___________________| + spaces.push({ + x: space.x + box.w, + y: space.y, + w: space.w - box.w, + h: box.h + }); + space.y += box.h; + space.h -= box.h; + } + break; + } + } + + return { + w: width, // container width + h: height, // container height + fill: (area / (width * height)) || 0 // space utilization + }; +} + +export { potpack }; \ No newline at end of file diff --git a/jsm/libs/rhino3dm/rhino3dm.js b/jsm/libs/rhino3dm/rhino3dm.js new file mode 100644 index 0000000..8cf7ae3 --- /dev/null +++ b/jsm/libs/rhino3dm/rhino3dm.js @@ -0,0 +1,21 @@ + +var rhino3dm = (function() { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; + return ( +function(rhino3dm) { + rhino3dm = rhino3dm || {}; + +var Module=typeof rhino3dm!=="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function convertJsFunctionToWasm(func,sig){if(typeof WebAssembly.Function==="function"){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["memory"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["__indirect_function_table"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}var ExceptionInfoAttrs={DESTRUCTOR_OFFSET:0,REFCOUNT_OFFSET:4,TYPE_OFFSET:8,CAUGHT_OFFSET:12,RETHROWN_OFFSET:13,SIZE:16};function ___cxa_allocate_exception(size){return _malloc(size+ExceptionInfoAttrs.SIZE)+ExceptionInfoAttrs.SIZE}function _atexit(func,arg){}function ___cxa_atexit(a0,a1){return _atexit(a0,a1)}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-ExceptionInfoAttrs.SIZE;this.set_type=function(type){HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]=type};this.get_type=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]};this.set_destructor=function(destructor){HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]=destructor};this.get_destructor=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]!=0};this.init=function(type,destructor){this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=prev-1;return prev===1}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;if(!_gmtime_r.GMTString)_gmtime_r.GMTString=allocateUTF8("GMT");HEAP32[tmPtr+40>>2]=_gmtime_r.GMTString;return tmPtr}function ___gmtime_r(a0,a1){return _gmtime_r(a0,a1)}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){var alignedSize=alignMemory(size,16384);var ptr=_malloc(alignedSize);while(size=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){FS.forceLoadFile(node);var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___sys_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2)+i])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_as(handle,returnType,destructorsRef){handle=requireHandle(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=__emval_register(destructors);HEAP32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_allocateDestructors(destructorsRef){var destructors=[];HEAP32[destructorsRef>>2]=__emval_register(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,__emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=requireHandle(first);second=requireHandle(second);return first==second}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function __emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2)+i],"parameter "+i)}return a}function __emval_get_method_caller(argCount,argTypes){var types=__emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=requireHandle(object);constructor=requireHandle(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=requireHandle(handle);return typeof handle==="number"}function __emval_is_string(handle){handle=requireHandle(handle);return typeof handle==="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;i>> 2) + "+i+'], "parameter '+i+'");\n'+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return __emval_register(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","__emval_register",functionBody)(requireRegisteredType,Module,__emval_register)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return __emval_register([])}function __emval_new_cstring(v){return __emval_register(getStringOrSymbol(v))}function __emval_new_object(){return __emval_register({})}function __emval_run_destructors(handle){var destructors=emval_handle_array[handle].value;runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=requireHandle(handle);key=requireHandle(key);value=requireHandle(value);handle[key]=value}function __emval_take_value(type,argv){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](argv);return __emval_register(v)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance.now()};function _emscripten_thread_sleep(msecs){var start=_emscripten_get_now();while(_emscripten_get_now()-start>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _setTempRet0($i){setTempRet0($i|0)}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _uuid_generate(out){var uuid=null;if(ENVIRONMENT_IS_NODE){try{var rb=require("crypto")["randomBytes"];uuid=rb(16)}catch(e){}}else if(ENVIRONMENT_IS_WEB&&typeof window.crypto!=="undefined"&&typeof window.crypto.getRandomValues!=="undefined"){uuid=new Uint8Array(16);window.crypto.getRandomValues(uuid)}if(!uuid){uuid=new Array(16);var d=(new Date).getTime();for(var i=0;i<16;i++){var r=(d+Math.random()*256)%256|0;d=d/256|0;uuid[i]=r}}uuid[6]=uuid[6]&15|64;uuid[8]=uuid[8]&127|128;writeArrayToMemory(uuid,out)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"__cxa_allocate_exception":___cxa_allocate_exception,"__cxa_atexit":___cxa_atexit,"__cxa_throw":___cxa_throw,"__gmtime_r":___gmtime_r,"__sys_fcntl64":___sys_fcntl64,"__sys_fstat64":___sys_fstat64,"__sys_ioctl":___sys_ioctl,"__sys_open":___sys_open,"__sys_stat64":___sys_stat64,"_embind_finalize_value_array":__embind_finalize_value_array,"_embind_finalize_value_object":__embind_finalize_value_object,"_embind_register_bool":__embind_register_bool,"_embind_register_class":__embind_register_class,"_embind_register_class_class_function":__embind_register_class_class_function,"_embind_register_class_constructor":__embind_register_class_constructor,"_embind_register_class_function":__embind_register_class_function,"_embind_register_class_property":__embind_register_class_property,"_embind_register_emval":__embind_register_emval,"_embind_register_enum":__embind_register_enum,"_embind_register_enum_value":__embind_register_enum_value,"_embind_register_float":__embind_register_float,"_embind_register_integer":__embind_register_integer,"_embind_register_memory_view":__embind_register_memory_view,"_embind_register_std_string":__embind_register_std_string,"_embind_register_std_wstring":__embind_register_std_wstring,"_embind_register_value_array":__embind_register_value_array,"_embind_register_value_array_element":__embind_register_value_array_element,"_embind_register_value_object":__embind_register_value_object,"_embind_register_value_object_field":__embind_register_value_object_field,"_embind_register_void":__embind_register_void,"_emval_as":__emval_as,"_emval_call_method":__emval_call_method,"_emval_call_void_method":__emval_call_void_method,"_emval_decref":__emval_decref,"_emval_equals":__emval_equals,"_emval_get_global":__emval_get_global,"_emval_get_method_caller":__emval_get_method_caller,"_emval_get_module_property":__emval_get_module_property,"_emval_get_property":__emval_get_property,"_emval_incref":__emval_incref,"_emval_instanceof":__emval_instanceof,"_emval_is_number":__emval_is_number,"_emval_is_string":__emval_is_string,"_emval_new":__emval_new,"_emval_new_array":__emval_new_array,"_emval_new_cstring":__emval_new_cstring,"_emval_new_object":__emval_new_object,"_emval_run_destructors":__emval_run_destructors,"_emval_set_property":__emval_set_property,"_emval_take_value":__emval_take_value,"abort":_abort,"emscripten_memcpy_big":_emscripten_memcpy_big,"emscripten_resize_heap":_emscripten_resize_heap,"emscripten_thread_sleep":_emscripten_thread_sleep,"environ_get":_environ_get,"environ_sizes_get":_environ_sizes_get,"fd_close":_fd_close,"fd_fdstat_get":_fd_fdstat_get,"fd_read":_fd_read,"fd_seek":_fd_seek,"fd_write":_fd_write,"setTempRet0":_setTempRet0,"time":_time,"uuid_generate":_uuid_generate};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["__wasm_call_ctors"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["malloc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["free"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["__getTypeName"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["__embind_register_native_and_builtin_types"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["__errno_location"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["stackSave"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["stackRestore"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["stackAlloc"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["setThrew"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["dynCall_ji"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["dynCall_jiji"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run(); + + + return rhino3dm.ready +} +); +})(); +if (typeof exports === 'object' && typeof module === 'object') + module.exports = rhino3dm; +else if (typeof define === 'function' && define['amd']) + define([], function() { return rhino3dm; }); +else if (typeof exports === 'object') + exports["rhino3dm"] = rhino3dm; diff --git a/jsm/libs/rhino3dm/rhino3dm.module.js b/jsm/libs/rhino3dm/rhino3dm.module.js new file mode 100644 index 0000000..57a6f56 --- /dev/null +++ b/jsm/libs/rhino3dm/rhino3dm.module.js @@ -0,0 +1,16 @@ + +var rhino3dm = (function() { + var _scriptDir = import.meta.url; + + return ( +function(rhino3dm) { + rhino3dm = rhino3dm || {}; + +var Module=typeof rhino3dm!=="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function convertJsFunctionToWasm(func,sig){if(typeof WebAssembly.Function==="function"){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["memory"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["__indirect_function_table"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}var ExceptionInfoAttrs={DESTRUCTOR_OFFSET:0,REFCOUNT_OFFSET:4,TYPE_OFFSET:8,CAUGHT_OFFSET:12,RETHROWN_OFFSET:13,SIZE:16};function ___cxa_allocate_exception(size){return _malloc(size+ExceptionInfoAttrs.SIZE)+ExceptionInfoAttrs.SIZE}function _atexit(func,arg){}function ___cxa_atexit(a0,a1){return _atexit(a0,a1)}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-ExceptionInfoAttrs.SIZE;this.set_type=function(type){HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]=type};this.get_type=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]};this.set_destructor=function(destructor){HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]=destructor};this.get_destructor=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]!=0};this.init=function(type,destructor){this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=prev-1;return prev===1}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;if(!_gmtime_r.GMTString)_gmtime_r.GMTString=allocateUTF8("GMT");HEAP32[tmPtr+40>>2]=_gmtime_r.GMTString;return tmPtr}function ___gmtime_r(a0,a1){return _gmtime_r(a0,a1)}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){var alignedSize=alignMemory(size,16384);var ptr=_malloc(alignedSize);while(size=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){FS.forceLoadFile(node);var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___sys_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2)+i])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_as(handle,returnType,destructorsRef){handle=requireHandle(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=__emval_register(destructors);HEAP32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_allocateDestructors(destructorsRef){var destructors=[];HEAP32[destructorsRef>>2]=__emval_register(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,__emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=requireHandle(first);second=requireHandle(second);return first==second}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function __emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2)+i],"parameter "+i)}return a}function __emval_get_method_caller(argCount,argTypes){var types=__emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=requireHandle(object);constructor=requireHandle(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=requireHandle(handle);return typeof handle==="number"}function __emval_is_string(handle){handle=requireHandle(handle);return typeof handle==="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;i>> 2) + "+i+'], "parameter '+i+'");\n'+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return __emval_register(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","__emval_register",functionBody)(requireRegisteredType,Module,__emval_register)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return __emval_register([])}function __emval_new_cstring(v){return __emval_register(getStringOrSymbol(v))}function __emval_new_object(){return __emval_register({})}function __emval_run_destructors(handle){var destructors=emval_handle_array[handle].value;runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=requireHandle(handle);key=requireHandle(key);value=requireHandle(value);handle[key]=value}function __emval_take_value(type,argv){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](argv);return __emval_register(v)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance.now()};function _emscripten_thread_sleep(msecs){var start=_emscripten_get_now();while(_emscripten_get_now()-start>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _setTempRet0($i){setTempRet0($i|0)}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _uuid_generate(out){var uuid=null;if(ENVIRONMENT_IS_NODE){try{var rb=require("crypto")["randomBytes"];uuid=rb(16)}catch(e){}}else if(ENVIRONMENT_IS_WEB&&typeof window.crypto!=="undefined"&&typeof window.crypto.getRandomValues!=="undefined"){uuid=new Uint8Array(16);window.crypto.getRandomValues(uuid)}if(!uuid){uuid=new Array(16);var d=(new Date).getTime();for(var i=0;i<16;i++){var r=(d+Math.random()*256)%256|0;d=d/256|0;uuid[i]=r}}uuid[6]=uuid[6]&15|64;uuid[8]=uuid[8]&127|128;writeArrayToMemory(uuid,out)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"__cxa_allocate_exception":___cxa_allocate_exception,"__cxa_atexit":___cxa_atexit,"__cxa_throw":___cxa_throw,"__gmtime_r":___gmtime_r,"__sys_fcntl64":___sys_fcntl64,"__sys_fstat64":___sys_fstat64,"__sys_ioctl":___sys_ioctl,"__sys_open":___sys_open,"__sys_stat64":___sys_stat64,"_embind_finalize_value_array":__embind_finalize_value_array,"_embind_finalize_value_object":__embind_finalize_value_object,"_embind_register_bool":__embind_register_bool,"_embind_register_class":__embind_register_class,"_embind_register_class_class_function":__embind_register_class_class_function,"_embind_register_class_constructor":__embind_register_class_constructor,"_embind_register_class_function":__embind_register_class_function,"_embind_register_class_property":__embind_register_class_property,"_embind_register_emval":__embind_register_emval,"_embind_register_enum":__embind_register_enum,"_embind_register_enum_value":__embind_register_enum_value,"_embind_register_float":__embind_register_float,"_embind_register_integer":__embind_register_integer,"_embind_register_memory_view":__embind_register_memory_view,"_embind_register_std_string":__embind_register_std_string,"_embind_register_std_wstring":__embind_register_std_wstring,"_embind_register_value_array":__embind_register_value_array,"_embind_register_value_array_element":__embind_register_value_array_element,"_embind_register_value_object":__embind_register_value_object,"_embind_register_value_object_field":__embind_register_value_object_field,"_embind_register_void":__embind_register_void,"_emval_as":__emval_as,"_emval_call_method":__emval_call_method,"_emval_call_void_method":__emval_call_void_method,"_emval_decref":__emval_decref,"_emval_equals":__emval_equals,"_emval_get_global":__emval_get_global,"_emval_get_method_caller":__emval_get_method_caller,"_emval_get_module_property":__emval_get_module_property,"_emval_get_property":__emval_get_property,"_emval_incref":__emval_incref,"_emval_instanceof":__emval_instanceof,"_emval_is_number":__emval_is_number,"_emval_is_string":__emval_is_string,"_emval_new":__emval_new,"_emval_new_array":__emval_new_array,"_emval_new_cstring":__emval_new_cstring,"_emval_new_object":__emval_new_object,"_emval_run_destructors":__emval_run_destructors,"_emval_set_property":__emval_set_property,"_emval_take_value":__emval_take_value,"abort":_abort,"emscripten_memcpy_big":_emscripten_memcpy_big,"emscripten_resize_heap":_emscripten_resize_heap,"emscripten_thread_sleep":_emscripten_thread_sleep,"environ_get":_environ_get,"environ_sizes_get":_environ_sizes_get,"fd_close":_fd_close,"fd_fdstat_get":_fd_fdstat_get,"fd_read":_fd_read,"fd_seek":_fd_seek,"fd_write":_fd_write,"setTempRet0":_setTempRet0,"time":_time,"uuid_generate":_uuid_generate};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["__wasm_call_ctors"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["malloc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["free"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["__getTypeName"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["__embind_register_native_and_builtin_types"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["__errno_location"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["stackSave"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["stackRestore"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["stackAlloc"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["setThrew"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["dynCall_ji"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["dynCall_jiji"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run(); + + + return rhino3dm.ready +} +); +})(); +export default rhino3dm; \ No newline at end of file diff --git a/jsm/libs/rhino3dm/rhino3dm.wasm b/jsm/libs/rhino3dm/rhino3dm.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fbbf481911e526f2e2689146493eecc86db319fb GIT binary patch literal 2803968 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�z1v@aooVN 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

yN<&NW5Be@F)&F~Y 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%0Dr|Xko0mF@H zj}K<4OgnbxS)c!E2kwBJsUfu|_WbTj5AxZ7H)=1hXPK_kp=Uw7S=13p(Xz2h@Z$bx zCWTulxa#Ppo~4eJo~3$eAwjNCfZDI9WSM6D8!K6Sa$rX4=&DAPS_RL%xT!{|V^yj& z-Ymw5+GQ^whJ$&Cf9RhS-g*AUwH)O~zFJ*4)JU$$A)_|!=zL6hWao& z_*Ex8(h(cM^Z9UL+N)UbrN^5fbG$Zapa~@2bjAeo<)VMW{+b5D?82s)4kqHag!WOa zPUbVcWf@IccRufGb!1B3I{567nH!lbs|`ha2NjT{GBVIkptOMlSgNl}KHD}*Z>#BC z!{;7Q!*rk=$4sWywi0(9I|)csIck<~12ngjw#HEDlt#M?S>me!l)32Gk&MsPps0>h zOg4#Oi!~syW@G=?+Tr}as7)m)Ux2AC54&yQ*@-I>^2$5Q`rAtDhm&`fe|Y458azFg z_0EDxnyVCB2=yY??A++8YazJNRWqQcs|Rh?jA{k0tz1t}s$+Jdmocukd(I4gNs>d#N*|8cHswCU0BSaB33TDjQ8G8T&W}haC_->Tw=X9TgPS zk)IEW;aHk_Hjb|`(%u5FH^4uI+|95GQLIbVj=>u*!5*@P0WKAwJ`#oiCklv`B*3}{ z7IN(h?`>9C5_!8j_;-_bt#U%vk&8hVe9YdmWZ08q*yDvHjueV~(n6hk9l{gXETXak zZT0_VO}0vFDc1b4mZEaB?ZGM*RP;-$Bt6k8imFj}0Ha)Yyi|xAMLq3`PlTW>xWG|* zL1$PM21UUv=_}e>`As83tkHrsGx+0c!EA(1gFnR^Odx`(|y$l|%#YhI|

LQ*&X$hjf1X86;T|@z3-x9#7FO^W` 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

4%;v|!1Y!jp19HQ?y))xmiyy%)FlZ*I-_*l#! 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�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ˢP8rhRe&^nsnguYnzuFE@wXlZAEJhT+-O#NYq9O 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

rJ&W;fcK1)^KMtPrY3L^wk*uYTYL zH6W!xzJX}S9VBBR+hfai&1i2yXtg?}A)?85mv6y5gh5)?iHhw)wJg~*dn197SS|1i z8=&*BDCTb(Vh{BsRs7@Yycj^|XjTkH*n%NKFh*d4L>c)Zf`S25)xT^XWWDl^sGONj z-PHWVmPIuICgaYujkTjVXgW({gOCdWkU~jvu#IV0@futK2*yAeyVoFvq!cQaAI!rO z?Q*(5%JuYBk6cH@*?Aoa@t|iK^g5?8$j1gfFbGC7T{mGxb;0&qy*C zIpuiS)&~1TNV<@Mnd&_SNkU3WagkJYMrxtYvhKBbHp6j_E&9pf=sm;X*aY~{(++KF zChLN7vjEB=p-ceBo7M8dE4HnTeEj>@i9GWIiu@0`4UyWDH`vm=?J{Y`;cOIWHR4Af zxJ=4r=HLUDCDpgyj_?*5alIYpEfnQ?JH%V)&-J#W%qW1w~jbB zCwXgmEL8Wrb)>yH&09zLo1<@Qo7e`9q(J%OIXK%qbm+338RLz3uW5^K9=ME? zaWsnlBVc9~_947s*T(@-;t9;BF@R$?lrN4jrI!$D$6!a1F{MvJw z;jZAT&&7pMT+9qQ%G$5|!G6%*SVJ{<``W$qeWq!7^{X;P0M(HglUVac-*)TIm_WWR z&R&2Jcg7UyX^c=O=~2hyyo)3}jG8~3o*fQsw*d>CC}%wo{StB0^nrr0#)(H`Nvl4- z$67iqR@KM$1Vybru?Ll-RECJmGeemd&a^kuP{)*XPt2y^WV7n`ILPy&GxV~H`Iq? z4bFvnU4xAuS{v11tRF@VZceUlNlk`umk;JpYy;r@5{=F3z*wF-PTRMDS*IRau}+p; zlG`Dih(UN1JWNpX8Nnk2;XcaQm|b|DzBSs^4aPcsV^MV9iVO6OF8#ub%gfCwUyJ;9 z->bDDC6A;w|6C38ELjHE&GL!f-8b?S>h&5nG{1V)cMMt8Z@y49#p;T>XZb|VMXdL*4P4xFV?NX*?=^3&!-kkQA$mw4oX^-R zMp+mr;jU@@puT$)eE%r;crQ5#lL#0#-efz%=rZA0f)3KO-o3+Oza|l1R!9VdyZ57- zm`b7qnjXUVQG;nK@6y>nPEQ-OT<=d$iIaMl_$hT80w_;<)jJ4m`rD#bX@C(q1w>S1 zysWe@>>JvzuZ^iR1~l2Y1vtYD8J6c}>Hw>bDU;xg7V!J-9^%hqD2SnE70?52=x~8s z0Q?!ltpzu56MuoX#e>rK-aE|Kz^&K58*WMx5nAqs(MV8dxe)5m_QT?x)B|2bG*M^h zQN;u9*OU|zqE+|{d9o}MVraC4ScOZwS!k#Z;y=iCzOW_gI-ir|uFhMf`OJ}Ktn*Ma zk06J`==@aM*_EEDRrgNkksng$n;d+?{aKC$Y4v&A7!kt>#e;Dy{tuA=J#>5T#xNyZ z0yRjY?qID>mD(m|JZbvj@jhUSqpVJ2WBjp%N7{s%7Uw6tjJX#pp7a?q=1|$?6;#yp zSRl-0d+tBnW1f`d_48BH=Nx+dws@m(GpJP?~r44u$(u}?O!bi>@#XnclMue-D!Raf$dTMtUN7a zo4=Yhb(m;mXs5!QP81QcD?gsNH-2vj>#`#}n!>Cc%}NTZ<~Gfau{8Bq8krlSIB1h8 zOIy?pW65M=*~UvF-lmssx2^70W@o#b$g0)tAZpgOJBvOOB0T}!wTcaQLrgXfgvRw? zbcI*+3L%(l9Yk0U**!cLRh+O|}(c&=%jS6%hQ}; zPpLx1SBj@Pg=Yv;e0B0VY&+VLn_ohf`t)_*%nCTsi7Mk??9d&IIm<17&ES>8-ZmI0 zv{^L)-NMaR_7Y|B*f25fUCb3&)I7Impk%gtpxKKBC(Q#7t<9mZ{N7>4#vtMx8k;&l zs7^@<$o!E11k@_`a^o6ol0~@uO+(s&+{kyCF2c3VV)LiTR8XtfpQ5?)pm3Bd#0{}@ zYOcI%m>uAnKNzuWn^=!4|4%lzuhdE$EM=i>mF7wBnic?n&v?)mH#fid=UedH#$C1_`|~Xb#fuw-4~nUP319+1)AtTH37W`h zGjwZec!{=41u)vr#cAecSS6nkyy#Y+vl8qV2grt(@}!mE7yzX=HhtN|q}8}h)t6RZ zK?7cIzFx}JdP>IU5Uen7+n_CKwr#a94#m1Lmm~=AFUwGM*A@DVdH@#&BXa1V{s$xn!r7X{kgz zz|-P7SoF=CMIhWaIK?GTvpPY?|6e(pE|zFwc#ds{k71oM%KPxIga}X`3uU9cQiuCi z5q~9R_^tw<>KrX`R_AYmFpzr+wYsv^^72WYD7)W;#xAdCz~<-p_&o09l+FzcHFwCvgLlB;lo{tsQv6gYC##D@r5xDd;mKr zSM6aE$f}oFqA5VUG#aeVsK%pZ8hECkF5c65eNF;knWP$0-OS|yZK641D$z8{eK~U= z{GC#os$%lV8wtiT(wR@G56AH{|aS*bU6|M6b1*re#p%JDha-Wns%0(&?5qhpXase+gW#!v* zs_HE<&=fGtV)OxMW+aFSCNG~@E4QvOY=9s~;( zYMVr$`^msZM%SY#G#;I&O#M_Z0TAc~P9_U+J_%h=0PD0YyX-hNUqXlVQ`dFEE|;HY z5(}9XmK8ysf^G>&2Dc0f1QS*Z;UAK!E^R*89FkXAG>M=fuxKm+#0g|Dn(9YxNB^2& z2%y?@l$N7sBE&Fv56;W-Plq6I zMqdu4m$P~~oL*M-awNTYzkMaWEO+f;j?1MEzc=i!{>w{PG}sfnRf4*_0xTWqSZ1QR zyHub^8LBTM-in7@L1DE5AQe7J_-G0rBa9ucK1cXm3NQD#bATZgoDUH`)GL;XCE=I#1fkAmm+YhHEHpzf`j$ef z*w#5S3LOKWK)F`v(3CCDPNB=%t>D$gNxc8;h~No=bp($S#1Y8OZ0m4A1;Z4m;Dv3& zIT0^(Q>b7z%6AyvqN+c#`>Zw|(_(fJH;Q`AbxjRg#x^HKfmfHq=+=a|mq>RvJ8p*@ z)jCvP&tl{@FGBI8mRMvzSbfm)>GuLK^y@jP`TKP#vS7z#Y*b29qkT?_nzqcMiU=XH zXoVw$#G;CHy@FO4t~pl-E=9T(pRx3gtu>3%7j3OK(s$}b{Zf3#HdYGKX&9WsiUoqL zZl)a^+dL~8W{LaK(}u*md)!~%Kd6QORy6fk%)1pcZf~z^FQL?4LaDvhLD61TbGMhg zYOlnR1rbZDwn#)Iy<;ojAe;0ChSXQ$%rSPR($<>yQfsy1)M0&Tz=Vf@#6x8aG+G9V zDsgUzP}l-r>K3n;O;Uc!rX}Ueob+Suf?R)zkidY>o(%x}!AU&}EEqeT$RWnMw-`al zmEK`*cuEHhBSe)jp9x3~8AuKpNPx!tY3S={g+{8>wlNTbRzwXcsOkkRmY0B*HJntK zpv5i{&FcCa(CS~X+tT^A(4s=;9>My8*~XwHAbT<|;)f#ccNJ{zWhv>^3kPc)eOo5F zub$tAwQ{ePyNY4|AeN)^+lG?kGX(|*7dO@Oz`3k99bCKwvx)Mcz$dP78fPh&=nXJ6 znd_jW6;DLlRdlV*i1bas&j#e zSnma{nPY-mg7bQlWgKg$#8~qT=NQoREE9BNmKk84Wi8ae^+Hg?$)INyj!I*88H%vl zhN6jU%rzm)g3u)ZS|nt7`soKv6+ulnGl41VLbIEXrmnP+85od-w2MXt^&(b~4U##U zkD9I*4_W7ahLBCTN;8iFKQp9Uq10C1Sy7CmER ztruj2v7V!=5G5%6)xFa&7^;ZiGUxnuMDUzB5XY{X199*yK`q#%Jws5sWeY$YE|k$t z`2%_A`$N;(@<#1=k<$~S-Q8dUXrnR3#a={^AoE9=L=GGgCKXacBQ0&v^QvNdhh|hy z!3Kwcm^OngfRuka(TMdbn`js{o?kUpL}NW4MBNe81z5yM3ENC>aZ7$|)+69pM^LcY z5kWz0cLW8;8yF<%i?8kpz!a{az<#TTNUBP@We){b^eBNVLYIxiiN&4`>{6fO>W{FaA8ZJAO4#)71UVX-8lwAs^(+W5KyM{Y#TRDX%D zN`H*KP7Nt2=?+xOd#bmZjo2I;;3eB&04hW7M$44kSk2#DzQXMc*_H48Mfd={d9r%_ zQ4M_*ZE-FFnYH#xL*kM1pz;@UXD-0dn+iDd!>J+7X@{hTme($I(9XgT z!(y)XuFlCy!{QW_wv}Uh$@&EEMfq5fPeJ9{9%ZivlK0VGF_?rz0W7LNeEu-MDNFyp zCytS|5vzOea72>_1D4XQe#;#UTLA-;-}J6W^zUEny`30w-uX@66DqqwsNC#sewWq_2 z04V}t&4qpW2&9}MXS4y|`5+BIN=0My-*J#a0o~636z%*Ax06D@>B!WL-COgdS7gY* z>zsMvdmA-L0HG$5k6Q^33E%7(mLtwyYc4boJ9^t_UPzPZ83IG332}F`eBV9I{o0bB ze&x1%fOmEay9oD~SJCOe@*yDqU!jY1hz-tro6D1#3iv>4;B%PI}7hoJeLyvIQ#8`qAhZUdbqR+AB zfWfxSQmpi0A7)RN;DF$n2r^Sa%;I74==qTHzMCrmyk0dYyU>$eh)Si!+umLO(e_Q9Z=&PPEOxoZ1Jf_+*m7^*Gv!5=D~TZjTmXv)VverILKDt#MUNuX?AAQE$xmF#|!CH|lX? z{#uw1uF@Hmf%X1m8LwNXj5kCXcSaenqm1T0Z6RPG_Mf-BFr!?!xwdP!(FZuVv%RwX zH+j~MGSB;>-Kq}A!kmdqck3O%J`=nso9F($4TFA`_X;UXWN`{0`Uk4Nc&>Okk+z9< z5N+(;qEc{<&5Vj$%mEcp2K=6oR#&zn2kkU%l_CuoGHz;!;cOfgUN~Tmlpxc#TyI3b zkVaIO*vE*TJAXvKFgBt$YiV#r8pR?Sq`Q6!#_hu3RXqv3UjV%S>-o5~np(otY@71c z^P2*BcIBN6{E$YXMYo?Ap&MdxuI3qtGgxy0(qX}rz$(@E0>3ix+0EzPw$S|MeDhNe zEHs~ZP&|=9*%QD7tfKN>cfZgnhOzpd~G zuY#@vxF=a2H0G|_1b91im{S5Z4SD3y0!u>8-2`q+*NisvIke`JnKzT!-n@(E*PXda znH%jirq+=iE^9~e)!s0JU&V}0i-t3@P{<-dbLD{tshpZar&8xWCx-_hS|qWj?w5bh z-pPPt+o=bxTFecx7G6XMb4No~5kn13M#-Ci03)JT;^X|uh%6Mzq^sH5*<;3xlq`x7 z*;C|Kd*vxjsaS#KwnRJ55@tN#`?Bl^zEXN48n-9{+01CUYi8oBR{V*tat{e9iLYV< z#=r7qA2!a?zbMsJce6O7c5CMEksCDnC;H+=Hts5sUO<$|a0HS8&UqR_q?EFn&JYP; z5Ra^Umu^#u6}JaysV0<7GE8sR93dzqkOfl*eE7AT)V zfrQzU)}woT!vz_q)!}|z2BCt1S7!Os-XDYzIqtmeyjM4XXk5{1D9 z*=~kCwR+}&*96>G(ntH|f~3|sY~RNN1bw5%1g||o>(V;UH!t_}SmsS4M)lM)gU##K zfz2DvgN?2Yr{m8AHn00{02??K!RF2h*d+eN+{}&n>n36IQ3+dtHv!YN7E2ZcSD)%?E*3z4FWbZq7{uJ0KOX zb5`*X)dzgOD*=%UuMNe3;XCu;3@y_pJ}m-JA^@m|M=<1i?QtO5bqk5bq_-0={4-#D zCm_oDiXsA*eeJU8`rLper$~JgNE5i<0+Z!qRNM%dc$ty^xTgc=Yzs_nFT$ItBhJ9 zkJ^0U%vX)|j#`foeVR7~D)PVND#l#Co-RK)v~@K2f&H7UUPz7^Znz$D2#a(Y>fInzQYlAc=K(!MbKZ^ zvo5n}?z%&^hx1z6;&6M%?(u_p^EG+ucrf?0<*b(Ys~08ZHOpFp84FeiFt5cdDzR!X zHH69KUxRCwc&=I33JHn__M90?T&<6`2<@xK_Tco%Y|aR&mDG&;&6=Kn8w2B*!%R7$ zAK5&CgoSUUXpxQBm}wx}uYqyL8Vc0~CqrLC+nV1XoTu<lN>b)|Yly*5_ zCEPH`xE5Xd0hZjCW%HmM`bgAW%U7WzUb_uQJP7wzP!+EkK8mFw2AtBZ#C0%OWi99ZOuABNgYXuQn&B$|oY!77rb)1k_ zzqSYDR|#4XQ%{hW%8Shr{d^`l0t(9UsS(kThAQLugcZlou8wG!gg^$mtL)jwgkFf# zPhIe8r&&HDs8hbA-(6ZL40vtJb^{eBbG5s$Ar z9pI|!ZDwe$N_a2Ji$=ULF4JgfCnF%Wcu}xVmIKB`G{$fY z=)A<2=LE16!O$UCV+><*sjpf0h==Jy*$bX>W0?`ZdGp=FGEkj}`6&0wD3DV_EQ59L zE_l*Zw#4E6x=B0n&Xmn;3CoolEmC!$q;TIW1*xEHGdb=}P1(9yiSN4nEdO2!BUQ14 zZ3>GkW*2uyyYECTpaMtlg;ixRqXnn7G3KF0-+WHlaLs8D68sbB6VwyHCt;nBVC|*b zFq{{~Rj2$W_|mBDdx{+qVqMpaCvLL{LcWN*aa|w<63mys-RB0*Vo6`|Mi;<@$$FJf zhwMiV7|yHH2^u7{k6ayHkXx+pZn*0=YjVlgl8Ii6yzypE^525v01HX`9D$dMCgYe^ z8BlsHR;Co_i{hq@7;~z4T4OUr`suMEAp!YnN0@!a2}L?W2C>XMHKtBilleBenR@nn zfw=j^1^_WRF=^OgaA2Kep$=RQ){3v$5C$EG9w@9?Z7c~o;vib48Yj{gZlpAj7)FZ* z^_<*v=wKQ`0NYak;YE_7?t}8qai+y+=2`&-!YWznxx0n~-)p8yF;;FYDn(x;PZ?sW z3LIc44lG(Fp#wSSmc;>j{k5m10DWX(jMZXQmJJQxJ3+4~OkFxiJIh*&aWs-aSK4dY z_|?{K_Nr}|2VJWuA2-&lWq}Yi0YUVr*%>nP&bXu10MXqf1!cjddc`neEa9myI^%;B z-TS=Oexm%qI|WJcr?;p4NbI*J8lnftsYe|TH6vL^sC<2yY3WPiv#-=j=h~Cr33Tvk zhY-NdN+#y_PS06BNInl1yg%;V2n4fssK@>}{X|6sa>Z11j)C!#FQ}15lUVjUc0>kF zcy?RX*Zx>HgWckFyJW+hu1nb>+p|G7yKX@nM%@zRv<=3{v0_%o_muxS{2sKuHBq9= zNZXbNbIlqH(g?oZgNYeq`l9K=Ox9#`rx?+eQZa=Vq!9^3$BNW8x)<3gnPQwbPTe{{ z!=pnyT7;lKXg$=Q|GOxrIzyCPIt~2=PP=nZnAbA*avg8oAqx$M>g#)TgzWIwad!CY z5Z6{pnsOMq3bfQdTs{4PxFAVnK8KiDr6^+AlUR;crlo8-iG#8UghAOnnP|4;e05eK zP8=}G3f0D4g*cf2`YOc1Tya{2)E*|)^asL+9rKS_AWUPhF=?kcNzRx1l2U`NnJqm^ zsI80gJsS4d4Ct6gAdG%j_C$uSY>Q-1q{eCXv`e&S4^b_TxScwK9flHdGQFri$wiJZ zji4CkIp_&;r$Rns;WHjOV(gyifH812Qy84#ZY>;H`RjS`-0-xL5llQh3xI_h03a^V zj;o6$3jDP_u(?C#YC*oWnWeQDJFvY@wt?6UrVp!=AFk{^ zd};hc8i7FG|F-mFhsI_$#vrH`jB#`-u> zIv{CF2PW&6UawdBcTJq1CvFm%yXtBmdff{eQg6MY3e_FN#whg$I=1ip&&ivNrkKA zMtS8nMV?`IX0L0Iry{RvRe}0aR44HaCo=cg^Z9w-^Vt}!-p{X-6x9!3C8jcV99eu6 zHvNE7paT>xfL~f~$bd@|Xn9fo!1IesVoBCnR!N6_^)g(?Mr*a=(^|*Bxc2&gEpPY# z5N5_Zf}#mpmv=t4Y8^ZPgoEI}Vvl~R@y_Qo+=kukAu{R=X@!nm&uQ)7acGRq0d)PM zXaiP99k!G0(sWXB<1T?Nq&j=mTc1DVlKFXb<{VpMLDW&;Da|;GQ}AuFGZI%WRsN9k z?hrKEH5dCjbGo5^Mi(@3GzUIdUIyaUdYF4Y(Wm)MeV8bjCrv4t>f$PSWtrkU6?%Qj zpR4B&5DaJaE@@t%cN6T{hHQNt@Ii0+N`_IYRiTTTHxd(1gns72v4u1 zqlJAvx5pqL?luqo46VCduohx?Y+#n-V1_$x`v`JjSNA)+)fHrQHQzDZ#snXLoU&b2 zdYlnIdd&`;TC_ms8VJN7pRyhZv^}t)xYlR!5!(`I@tqT(g|*R^8R*MlO~HwsF;0)F z&}$`5&71Rz?>iEFk>nU*Q9brs1v=-Z)>i{EapU;3pt;m>fS};4)>6T>k|UXRY|i0> zgCxYILfO#~q-dub@M71$c&Rhel0_Y749Gei4eTrZ<^Uo7H=54~y=Q>zYzNu-bgI#K z8dyy(-sBNX;I%5kgqr9nwG{BOjkCD}Z)H}d`ni|brDP9CiLL?119ho=D{y0mgNDeF zcu&wC6!Ua(TH|Z%6BQaXzB_H89huSHX@ll>KKPqh^!dTvX@mX1jn^FANz=P^TM7CP zq=E4uw(~>i$ZTlmhX%2o9Z0yk^X=)U1|6mnCDYW{5L07NZ|J7RhP6|}J&b+Bm>~?G zuY=mC6-~+jMO?&8VN%gEZLNt9&ghsPRXox}4p2(OrF|)RJuSyfR3fNOAwMSaB(nui z7DnWa8gia@`YBz9Ss_xtcBoT`YGP2h;WU0Z7KqmcUJF_+*t%|ocTfzltBg6EbCONh z3e9=D8}}FD`Ymf}UTm-|K#Pz|Yu`UKcS7%et~=Ju zm2RwqDCm}W`qYkfOjx~Zx^jz%#u_RI_svuQ{qf988>HCdae11l3^&w8DQTYQsb)Bc z64P_;K+r%J1C5ft+8e|{SOhfB19AKIGOdAjO={Yx8Yay+m7_gs2pT}2hM;i<5j~Np zU{K-KXWG}CS3l3hl2j$?MNc%Hq=n0>`(Hxc0pHEQ*SDm4Di}Pd#b#-P)*mkwli)E7 zD)rS}6}ki4@Cwb^Ik}4EqgU%@*f+OZA)=ErNewXnIYnu4K{PnTfGvYV9dr&I4L^gGoPo z=(vL3Q`{7hVkdUU**Q)m{so))Ndiud({8`Id0TUz^l-EgmXnM`y=G5=ajobvPAIDP z8i9l~=(Q`Zl45H0yYZC;RMm5H3Vos4 zcMJ3_CDxfnPWkKF2fC0t{N2jCPKTK=sly;i+hHQtc9?2jmQB-G&8vp%&wp$d8W;We z;{w7kWsFS#@urvi|J|5s#us3wYkn9W{GbxJD+W)1>bejE7pp&AVEqKH+7q~q#tAHY zih~j=$NB||U_>ECP0Z7%lLqZG9oEq}wRo3C+AXF6v}Vx|aUus*oyS}ggYca=Q;CeA zfhOw)tG7S%_XN^YU?2m3>4MzB?ZN0CJzhZ})T&=Sn+ylU`Ppl$PkSgO7@<}g}I zj$!=8j_O}s#}J!T?H(f7y_!)g?Tkw!ueM%0WNq-0GOh7wHnXnH21^yXa;=W@P=8eN zeXT2+q?0jPE>JV~&r3ELBha=4Ukrw7g7XfSb09wDR??Cg0Ul_)jj1#)Q-8ylso7|cWG1WP!9=w%=kaqTJ&&a4 z;q*L|p36J|oY#z{oR1d>7PNJ>z`DlWlr-jE29vj93Gzz$3AUzCT7~8q zokig_!=%%krpW1vM1)Mn>6OyU=Gt`V0lxX~(CbvaL1(a`+^jLT5K6$;Mhti>H~WKn zK@_uXNN_u0cf`FFs7m)~%G+foL{K5E4lD;^o|SzQ{tjtcCNgCiuJxM87*>dosfilV zA%s`Wbra{^M4^W&gA}AD65}R1#!V!~O>_)RR8hKT0grFWHE)cJp-HRx!J3T#M)-V) zO7_xNLY=4Oh>wLSa^gToEH&WOZde^pe|JTf^qD=$YMg_-QsLZCEVOQx$PDLAGu zIDIG5xC@q3p%T@?JTo?FBhnX}6wikWLUp-G0wyF$bJH5P;2rj`@vKFE~_ z57&B(mV8M7Yls3GKb-ApmEk519gk+7Rv{j%Ta|nVLOI|^8I6Mfb?IdUl_N>8vq6-6 zA*MLu5Ml>t4PD-Fihj(J9!7C7XJDvgUP>CeDdi*ss-2bL-~gMyzKPX)uNisd7Gj8a zCXf&W5>yVHyqP+U_K?zKEQxo*X>JA42*c{^M@cl6#k7wuCjL>b@?i3{BJm%~R6Se$ z!49`naUw&EPMGEzG~R`QH0@yTqnE6E-y>OuK6d3FvJr`5H-Mm>Xhn7BV($PZAA{;}5-J2b?_ z>TQjGx>$YlxN@Lq{`51Cuxaea*SlCfZgUJPYN?>j#4u}fB+6#9OlGA$&Z<2K+k~#p zW^iF0A1z+%SQh!ZqYPp9(b3uf##FhAi;?!v3orE z1HIx?tCwSA0owx&rea`6(q-#jcU8O;M!|om2<=FEg9S55$viF+opJ&)^_skR!7>L# z$WC_I3s6Y=4&WF&c$|Xa2o}(eZ7#8vYF%tMY1mwJYG(3iG2iyQ`2cot|GC%vBOZSg z=5ggOT-5k8$<`a`-o0aLph8LkOd9<7Mn>9|XryP-NUP(SJM`asR6~UJsJ0^XMUASu z^j~-U{)TrCLuHtGdC=e+-NlspK!P#E`(iAIA|;9KRRrJp?fj3zhAVUF~O4tUP-Ipx`xWFk5|T154qn(xA)Hm z*biOG^kuzlOOzJkN?JIO$LD@Ig^QC1FDpNi_8_CFimHC|8g)|SpT>(e^QeCN8h2kq zqMq45t6^yp3Q?!);vRKlme>JpK0=jQ%8O?-?$kBD$k{zLaXYgsqxF2PLs41&AB<7} zFb5!~uaWUc3khNvEH%iXc^^qT|EA#hYKl*~s|#00C{^dYs;=&pKRiNg^)f-+-Hm;E zhL%a3(cRIok6Go5BDAP5X_h~ZCu14LP-i@S^*h%T^&-nxUe58cI(rS9ndR|5whEY^oMH2o4Z*fEp17mS zB@raM%fe7Eb=Qd9N|?JNx;w^rZEqjb9%6eh+3uDnVkqjZSWP?7ao_LXzD6G>A z#8@X};P%887(v8*U$KC!BI6vK{yS77*+vLsp=gW}_)bVCK*FaXienn>mil#2{ekOZ zs3+@Uwddo%<*wLgSBEq%)gQW!bp}Os{MNIrLp;8zo1g!=uL%!?5p{4Ne$=6%MwFWC85 z$o~7!54nKZ47{O6EFBOH_N;$Y9$-FMr0X^`TZr~lfud>nxr^*Y+HoeHIoW*aDY332 zTqOaxxS^lrMNb6pnqJ+Ue5%AETw7HV<)ebw*oDc`VANN=kmhP|7z^p9hN-@Aa_YD#{^((RJ> z+JYJ0UiHMDH`so4vUXASXrgLwa*PU5W(IE@JQPhH!b)-}1j- z-fIHeex6x+U)S0ddekOzDFViaKB#|xvX^1uRq{fY=8lD;`@ERfZILuu6tDZ_F3A44 zi&>KxTxqKv|MJN@>-=9Uiwqt_1G((xLXdr7?v*4#FsW&=+)MIVj_j|mg_kui+ORy z(j6XJj`98Oi-gF{s!w*-@E^?Gf{S$gsm`?K<<%L?FS*=?7k=yFfZ53H-?8m@8ouvh zAln)DSe+ezU(yP!dit){5awhUItO}}Uur1|W0y+mpxee=v9#io{v=rg-7W>tl_A|E zeA}*lg86zYKzBxG6Yfk=|sMsBlmS4#namo?j zvmjzWB0lY8_^zeL-py>t{BO0nokm1B%f9Ds3H2WWybbji&8Ha?5iBNv#Q386y*-Nz z5bWfTH%9LFIL9SZJN@3iEkJ%>pz~8XCV3*@j4AXBn62|yXB%EQKc|1$++034S2*1B zZ{NQI|4#XrpXoNxJ!jg`>|A-%9pAh2hVS~+nY|lZ68*;tM(pY+_A8^5PWPu=k8 zfApokf-RmAeBBLy>*~M1f1_gm?gu{h#~=I4Eu#ci5KMpvAs=(kyE`}o01r$dJ`}oz z;1rBnFRZBnht6w=&s3nF==N!2@}kX=WQ2?oFb2_4Kv9z@zgseq&HiOhKcEmq8bzaS z>!Ni{k(Pq7B2#|bJ=__VhUAng4^7{lQmn4Y08(MjHn^t85hi{fRq)NUK-)Ml7?~rM zb7I)%9r7`IwEE3m%=8}Ok<2XHd<5StCWagY$n2+q}qw`fUy{rc)KYp5m6{aYd8uAo#?7rWY=qyGZB&L zo3@&HX^Aun_8=g`8Au3kfq26i=tn)xkhH~{zI9`uX0(KCcFAZu-e@ThQOm=1&Xm_> z<0OWR!HUFStnR%XqnRcj2${@Zy}m40WM0bffZTWx!j%DgIbKPyi_}VjjHZ=@m9Q1d zC34pkA#e4qD2`a(isB&1(3_StMSco-P&h$~MRB@~K{GFj+ca&?K;q6kRW@figPCsw zd&Ok;#4W0cU6k^JsUN09O-jB(BOy-Sdr)0 z^1P*-n()ySmO^1Ah0!86M^ab%mCip%OhPUk??{iQ+5) zMdhpk3TlIS5^+kSCo*1}fN8Dh=%qMe{0si>9*HJm&Jhh^iKCyf3Xi_Xn*k%KWh)MO zwPP=nYk}P#7~Q)6(%$9caPD0Q$S6c(0KU0!{*!`5ln#%eOdr2-D04=;g|f=IFqAj8cEmxWLT0WpDz=VbE}42Ah?oDJVg1hsI0UBKZ(;hKG7b!9-T(kl zmv>7mg3eaKrNz4wqKD#<+Zb_iGNV?WNglaN0T&0u-V)_5=NkSk-`XZQF-o$Ak!zE{ zDUX#t&dKHMNEM}R8KLm6=U5DFT|<*0v)zE1u@kN6D4%&~t@9n_(*+#RSC=H-d0^g| zs5QiJ{fUm7-4+E^^cobD6PlRlVerK1PI;t+JYtvm#2&~CW>}iE5VIiHz_QMCfTA+S zwi$x=hg!5PR}o<=t~SH9w)}qYfHc0%1^InY7=#_eUaAff5yv>EUnhRWd3SShtYF~eD2O6pSyDiJsLAd=^*i1 zm3o+Ra6nDxMbdE_X~40;WllZY#!2l^wI(JY&^O*qmBjLawNl-~`ggI{WRmK8K(g+^ zt9oiI=2Z95J@r5v%vzD+4^@|xKY5W^`9lu8n^9Qx`+KTC+Ji9!2$ENSu*d2iLVsw_ zQ;Pfip1`qZ)pYaG&!z;r?lXqvdr{z|6NVNXS{2l@DI5d>3AANUXf?SH5uivSz`)|( z&74nqws>IDCEFmIPkl2=W1zpnAvO6E!Upo2(n52EJ8D1JHV8WDM-^|#A&P|Q1BEc+ z-GiKhvhFN)h`kt~7eYh9H3Ty0?&Z1c+unNrLo3TCKE+IYrM-#&hdi3FLhrymf#7A?d-F8G z@(nX^o&{V{W<8Ty$PBAC@XpNbHJ|#k66RMqq!8_iuOx^uy%Deav*)8wVEvd(83O3B zA75zx6d#j3o_tt-X-MRfC`ZFl$3quI-;a6m$xC#I1pRrhd2hb|+w$t4Tvy90Uyshd zi_!I`>f_gG((9%And^A2*x?4=ouSL=4sd2g!D8B+eeY&%Vtrz&(b*0b?z}lMwZG&L zcl9GTZ!$a6o`#QovtW}eyo;V%mPA7ioDoXEndy6*Myxcb^G zwu*cNf}Nn3ghcVC(O|I+>S1-qlnL;7Snb%d>uw*JL`b&GhhCBu>iG!;;ulIHi7RsRCd4%en8J*^yv12_N$p?Zq_#AP$VuPL@1X z+TL=%(kri^fTm}0iF=N1-1Gh(rZi>w*ZJZa`n*Pyi+u3kYd1kAO-UrHjGt$u9m!xqTQ%!{mBG(9t+Ny*k$(h6I`H`Q zt%&OhV0o%*<>7o&mYS#|br4LoR5S#pSC(P)E2W{K5W=)g=@85ptA*lVzJk%}-6od^ zV!3n!XRQz;W(i-nA`*Q=clXimE))D5HV0Yxu>vErgegF``VhFHgQHexK*%`)8@Q6I zo0w52Y+){U*piQ7i|QW3mV69bn0YD7Zi+n*TVyx@v!&@*7YSQLEd@Q+LlOKDWNdM2 z=n)r22jrF6!6=Ola>Es)AWO*5b0|TNxCA{y33|f!4thi<=n+cLBP8fSS-#biwrwP^ zPK^|L0^AV>uKB>YSHHKVe1{f2VE#YU{QUiBR6Fc+A{H#@nMr0$zSO&1W3%=Hy&IyW z-}0;`=m@`bc)F!VHQ|;N;fbI_F3w$d$c14?hg=Ye2@+QwyBtCnW)0tey|#jHiXk|A z%_7)88p%MmnweT-v@R)EgPDnBE69K8n-&;WnD7%3$U4G6Uuh3xv|yq_*Q z(GA9@SSpsH9X`67F%uX2L~$#tYu!hSe3hN}>LpW)A;qMxms6_Eg#^{F8*HqBA=K`Xws47NO;S(#}cFq29i|OpIpPwPrsD(Kl-j{FpzSGKB~xfr^p0xP`S|BA>#C&cfq{M zmf>efs3I6KJ2glY=1Q$UdG#UtH+L>I`*G)Tvo$#6(G{9S14|*GM4<*sS1358HhFB< z6ly*?6)Jz`6tr{zKdi4vDhg0Kd8d+Rs?UyI=6Q*g^)Mzi zf>-Onv}v31Kit|nT691{X^Q;NA#6xKn9dJ;;s-W`r7cUqJolI*ycl7n2XDJCzD3a{9 z)LRyrKbtD1N7s_~MPmGov+0=rfS++t=Lw8<2IK$vfx78fyb|@I;wgjD};6${WrOuBHmf_7#rr~Y6ZyXCkr(qwYW7rMG z%@TTwS8mhB7!6tEoPsZ1hVbi2-PPY)uI0D}EUL6+; zj!H8#I#EiBNx5gG50u4QoZOfz_liWrDkR>CshTI=>0V|jMhy~j%}{uGmXMziL*L8t zA8IWF21UVKbs<5ikQ8`T8bE<2C}kR>*l7iSGf70|U5TY+9ZVQ+gdn1NCkg?OaH=7f zYGooj|EVyuD!-@$2`m|h@xAXgb|N!<$gAJKCK@bprqJZ+SHF0L2$%(yLj)8Ala^Bu zOgiU&j4!WuiErw)YiPLbJHND5+L}r`n}U>f#-$-p(cKWHR9fA`)|FwRQ73VuVM>5~ zC1P*kql86sj%YSunUBo!LM8^?Cvrq2j`b+jAFCgGIRVI}Bgh;PU;xod=a1E6DhsOy z1W#zBp*NW04P_y)@i(^|lBxxe}cr+LX!(kZKUh6HInT z2#_EhGG<|nDLq561UF*KUx>u*v9-_Og<2MFKah=Y32Beyn=X;|Y!X>O`0~M=;>MVW z>aDvLvtmhqb&<4lAp*;i2mUVM=fk-L__}9Oq8$`MP!gFrhCFOqco7+-?X!CEKlNod zKW_NK3^LmYQ{?#?E9Q*RDS3i8Z<178#SM#Gg<05Qq0ZRvp?!B;J)(l6*UT>IzMJ&bg_#W?qi%nuw|QH&jsl z$6d@K703eSWWTp-Wf932>v>NnY(5lXHc@hdWi2`cjqf*~%0!l^bYyPwMvt|aL@vGO z7+=Y$WYmzFt78yi?ubNDC0x>lAvAT75E>bVh){Qc5*v51fU<*!2|%TouLCGHbDjmD zbi3L*C&l93+^fh_Fx-@a#FIA7ItZ03jRzbu4B^dFTkrylp z=Fzx$G>43>DQn^z1e3!E7uBv~7i^cmM7#dh)Avy10UGPJeK_B=<@~m-`7XjXD8Dj@ z>q}O2e+z7{b>r3lv(xa!1|7Ct;Nm~1&OqNm{t@AEq-Gi)cJEK9b!Kl&v3hNUF0|{( z2@1bU2l!d#5-w_}K7PGuoZm6wO2w?oSnD5b{wKHm-rv7}?;G`9=yN2$zIRNti|}E> z@JJ3HBD^9Vi1=l~;z$X;ISv{Sx+@MEoWupofEK$Qn4K}%obWZWhjHjmQD}$udAq?p zC$kua^L=7p)$)YKPQ`q`A<{$Jcas1)RYq<7>LWYD2^Lw#YFdg^QOs>-YgOQ_Dn^sa zCBPM_vQ$%Q4>i?YO|(``ny88NzBMo)`7=AUeL)8SQL};()w1_6g2tIRiW%r>>BFUm zc1MQcbU;IKlt|G!SoC126a49;z^uNx2)I|BtqL{9XYx(+C^@Lo9r2kPVUOzJtbMF0 zkkF?qF&o0?Q(|D9OBAYlx>^ZyQE1@yP4=6mfpUOET6!;PYM#2_)by8_nio~lZ(Oh> ze~FTk%$eAMU;>6En|l*%fR6j(&qFKN)HW6!0Gek0>3q{wNH`pP-^sIh${fFfyr_121tQ#c#QY>IT_Hw_X-C zZhADZZh`!e?rZ> zcv=2$Pfz@U?41#W7jrnYPh8P@gk3}DBZ)Y1r76)ei~`if;1qt%H0%@lj<+ z=m{My@=Nv!FQ8=r50?$B^KuBmf%*WRoB_%K5Wp*e{Q@J@gAX`3l=BD$ z?()Odu}yIaAN@&h=KABiQ+Ppg>rCQkn5%;7Y`p>Smx3*f$2Obf>?ppFZ`vA44Z&a6 zx<$%)pZ;I)pn5dc&jh?Jzhyn?I8kllaX>O!Ry`ppeS-ws6#@$mh-m?{bl}}G7hMv` zuwA)h>L;#qpH=4T>0C3{4pRR7KH{G(B)$|)@k@Xa^EK(xKE@Z*&lUP&vkLYlH zgD(ta@`b(Z&uYsMR=zVEZ)F7Lj?T7vg82ysNV*DfA*=o+4rUkx@p27}52CqUIj|r8 zDyKzVn@P4i9nEOx zSkGK8{fU;gT&|+wR)268*xebUzJUqh5BYi@B>clN?Eo@_SZY})yc2_D4z~tCCMq%K z!v)r`e$@}}(p>Ae6-Fhg_SbtGNgCo*>qHZD5Rf6Y!Z}tts%g~;8p-I^szjLJiM(ku z@U6?~_Vla-@Yd)!pPF1pVWg?tV#Fkx>QAs5RZ6V`}Bq z!|pkHe%$(M>d8XE_6V5rsraeXHHa$A!$l;sGdl0Rg$)MFg?HAcL_5>vE>rCm>y&t2 zPA;dN^{KI)^{H;>^Hv%$v9r#?op#f2;L~Vl9kD#ccXk-F>XW=MvMH^~wTg-!X=M;I zB}SsNIY7Ul?j~|HOnL2Cu1f>4fPN;;gG04U3>-oSg_2rd5jA#12vMU}h#nXxFOTRO zl*vhVcbRr1C6K%TLEYtKfMPKT!F3M=_2V*cr8MGcu(H5@@Q#KyXgXv%mOv!s5Oj#xpOdT>z9r~uh6qwccwBjdV zIuM`72;c*o6-Mxe^U>=qMh(wlf!A&YwP|CYxgbld#B4(t0Ay4K2(l*qMP*RRVQxV~ zG@6N~d_3{!$}nrH zxhX5Jq#%-ovNLU0c($u6GF2(~nDoAh*sJQIt;nR%YfWm-!}+G?NDt3)NyD@9$F(gn z*4!1*VEea}I#Z{){Sh~s_cDg^6x4PMbyq)l00@rcxE_`M*k8qv)vi22I6GFQ^?*Zz z(`dXB??)l#wPDzRE6iN=<>w=x!@kzj)^`Okxk*^0g5t-333?MtZz&qi2333Yc2x49 zMZfys^A>?@x>z?Wf3nZ{f1jMhguwfx*2!5BEgcI5$I=5>#GWXspvdaMUD8R!>)ZW0 zb<-~u8UJqkg_YYwy*_ATzV(edI?~Xnn@Di(si(fkQ8c6UL<-P(YLq}_u)?M~j!4%d zrB5u_QjRRYVC>v(q>Hwi$&~6nxH`FkSOoUkl1{x5H`{z4mjKDLI0+e;AH7OSOr~9e zJ~C#V9?Mux{z8=5L^MF)r$K>gtDus6eNM7wZFPRg`}? z1#--3cLCOoy)T-idp-2C;|(F36RdG?%wq=J*p!QSZxvB)fQ`pArv+MhZqt6m_m%c#L(ceC!Uz>3Fp}NX7FjDayT6 zk09a&YhHpYdgX08zYkOpXe5yt0k{MhZe2i9+zyoyx8bFPGxhwVfhjIG!RPCjn>Ab| zJyx!+)&sGqXE=2`4QB>OJ8f`<&vkH};Q>G^2?S^MK6asAgg{AGqP=7Fxcv=WD9{Sv z)KoJ)1{`UOFAN-x#TYmu64++-qZ5q#2sreFekdWV+Xy&5baFwUPn4TAQxoMvT4*a0 zJ>a>WG3N;+gggwpAf=(#dS(dL&j3IP9a9+wI)#sQ2Qg6VO93@wn)S-h_ItDU^}esC ziKbg#w98yHXHekNtb|IQ6ExWI-8vRD-^8q^wS@FHqmGJbFI1Akfmy^nE|O-;x|tAW zSz56#00nrLIWLMRtVg)KK??`b(^eGqbPq|ofT8ir-G>_vwef>nyyiL{Jb_Oxut=^_6;|3 zR2WUt1|Ha#AwzOvPI|JjDTfyI%8y}{?{z&x;>X)0A$8!Hiy#+x_rB^-=S_~4uak=09$5F0&0DDBjJ zc_!Z=nR}Vr?&pY{6Vv)tY)1%vwTG$5w&6zev0@6KYn{0hB6i#P%&NDDOPl;uw7+ z@*?+uj8_f`nrn^&T5f(rD=3Z6M05T9==gVf3ij3;whb?h$qE5%uHwemH;D(mdvSAf zc}*DJ@$SVfaJst>3VC(^h;&&}kbK+$~%6xN3Tvuk?Cf#fy46TL=r&z)eD=9(vkwiC0DK_TH$CqLFE zr}Irg@-Lj1+?c(Y2G7|<=8}4YY*pfAL-pZZ9mD!q^+gmYMSYo=NHyUs^LtZWjgnXA zC-+G$e2*3NC8Ui!b7+yP$Dg+q!lJ=K&C4%ZRYkQah{S0}?WIL*>T@VT8$SG?f_8GZ z5}Nu`yXsj_MX1Qe28&M9MJW~8oMKVaL;CX32lOKvKn4_A1SPtZdt8!NY%0xeh3}RY6?E#0_RN?6{*oX(CuZ6mu?5CWYfKD_XAf@w6~Ty&M;5on4nz(i ziW{=#nlrdP9%b%UK`P`+JIP9&sjsDkfP94SHFqt?QjXBW*CE#&`n!|HPJbYb)n>%V> z>Q>taWlD)BvewYa(H}JDvs<|ufMdJ!Szh$|1OMw=9iqJ$rU?)ctCG3{$@~a511!yo z297Xfd^SG!0tlb2J&6rtQ_7?q<+LxE^8f(t-VAA~c!U^;J;h(x2!TH$d}A z&Es}8kY$sP7EMq`lL&rOm9_2G*#aMI8!O5sWnAoD`zMu z#gxI4j6MdC}4UhjL9P9FH>8>gd3Q zQd~-k=+}8E*z9Lww8N;v&l^TXM!!UHBFUOzRAh8)lw{2?u9xJHN1EQPZPuuiqsAJ# zci|)_88P>7GKsy@2w%>pF-mSc6zJV0AMlW+61FMlQ=K*GVHbdzC{g4{gG{Op^;LYp zOxJn2idae83MMF1JI6`q2A!gU4UMSPcm+)C=;3(bAme1glay;s%g?&yqGsa-a}me+ z1(&cOUi-b~tIGVMI>^$EBw@I3ZsuRHc$ufGOcX9a?sSV|N2-R;NiAmXJ+tY`j=d;k zOjo2DCSL`!a|wJ!AT6J_R3AbJD#ku-;o}J`*Gl6-8(4P04Ujwxz10|=Fvm*Z44;{> z|MGdY&K#K`dJeV>V%Ep|5-^2ZYA25>(=;G&K(<$3R&Nx^mpdd%NFHSiBvD;+VysYVQ%iKa`ZQdOchs3G%xjH_5S)Vs{JvQ~%MR}2<_1+^Rt=$o z{?3TES|~rCql%VL2X8fvI!Kl|m+r48d{1X2mF<5i&0Skt%Yuw9N-wI3K-MWpUQ)E1o{sQMx@`j%@~ zr>S2pi!4-#po_Xbl)4Z=jUWv-Hh}3PCr1XQ{MPz^-l@Qab z64>D@=Mh&8cHeY!9@sS*g+{FTo|A0Jr5NtpBBleoL|3IaC}Pd%FqfPW3m~Eg?dUM% zem{urL&>U0#-Po&gGQBU(AwdPsu(mGSv51=puw4?v{4Hi1ap%qFj7h>C_V<5;?n?& z#z4=y0aodwcZ|R$-c^N5uTuR$7kO+v%Y3s*I8hoW7CM|*?HBBUNwv6)5!@_PV~PHH zFhFcgXzU8jx&mi}$Hh5559H#J8b`uztK6x}^WtWu?}KTqK8NJloP{IgMoPz(K@YOI{LJZiS(@4^pOPFN`^|S9j_dj zkg|FR3{qzl+;=uDBIxgIhUFNPu&$$(h;K@Zw8VELZ}Jz)UpTL}i7?wq+4-!!G<~LU zvX6h3M=pUa^S@&+f6QQmsspxBoMh?%R0lNys)HH}M|_@bi0`1res}^J7Rm``7`==@ z2A@R_vs$EES`kBDR- z{^n>3(}HFtg);$m*%rT9`HnujuEMt-MwhWlu`Zcjtm4y5)Zkoto=(p*>3LgxeoZ`N z;?Z6c&IamBduK@|$@r9>oWeU>NZ;(>DT2FMu%@K_;unk=R6qLyK)p2zJDjRGlAfHu za#NPmdmXPk;z8$vhGB*F(jF(cSo>buFjYTJ*sbnR|p@@-CZomss3=jKOtC#x~KXX zI?mee^3J4k&!*?e^gNZG%jtP2J@1TccTp2vKTWK`tsiVDF4VAe9tz-`aU$8#*k!f_Iv4D_`&mR>O@ zN7MdfGKi9SLN6tB9Li)&;z0F0=$&$TQ4ggq=YIxdY0FNuKY9-Em~~L+k^S9$ceoV|@wLnW3kK z)^gdeHNoyj-@14bbV+p4ZVaFlweA;3aaxD{kKHaj^ZoA{WG%xW+u8(bu?83Egt^o! zpGaas-P%{GH)GhYru8uU85`yRs|+9Tg_JOnT_C39C~u_cjboX|)VnVuZ1aQ~t6~ye zWb|@=6_aU3Re(?Qzuy%1u*XBrS{>|{yNyI@Tt{Ng58xHsaQ?{kTg(3?sEw?;Wo-lo ziq{EiZPcT})e+NRr(HK$AY#3STkDCvm}A`>q1FL&Dws0RukV8Erv+*$0eZhS21yRDGJyZMZp>9NEUJAz6}tP zQ2P?DkY9Eh{wYU_aP7O(R-Fll2Bl4%E|IJ=Zibwff75o#O(wKGA zRmt;cIAwa?Te41d2f*brTQoKf5KNz|0bh6cglke16Y{W(K<`Tk7;@eA7c< z!JJCIR4{rWgCa0o>EB9N{upE|N@=>0u|qn!_?g$fzfCTEWLQ(b06Hq!j#z*JZvf4; zrE0|Z0F`z!NrZ8CSe7IWQi~oeZfYqXyVBGheNu4{Tw}kpLKm7GXb+NYhz>6UEh^be zegST?0Ouwt0~<;x!6F5wVFWmV@kHf?b)W ze^jjUuX7v*14Z#ss1o~bHCRMRbYF9ORRTm#g z?VPX{KK&yT*227&OB4ju;=Do=38=UcCINBHA0GA*sP({C(GjsTo2Nd(^shOAECrq> z9oIo36;yKYn8O%-=z2>5ZV@g*IMUj4NVkr`tTLD!CpHQ$(!3>p}_S& z$-$d&D}9ZVB5lj5O~b*>2s7mkT|>f6nQfVKj<%%}O~A-Yz-4su%nIJ(gG9wDkNjAdks+n7y7EM!(mx2*b5tm*)~F;$~Mswn)YrP#ByTF zl)~yu8m8GEVc3{!x}r=Ul$g?PP6cjDLuzA(QnGJ!SdQdnGcGo3EN!aQ7DE?VvQ7EQ z>$;MC>_#QSa7~n~CBS{MG3rH@Zl1opkJ3X34@{5?jo{^iS~BIp)rffu0ONZ)hQ`na zKwWc~7qc>m?ut#oP*q295w`EeF>V~hWwk?obNV2WdHDfp0l=d(olPX7p^b_uMMq_6 z0FRCZg=^O!NGg*^K>`TyngrDuOu=I?SU9@j3Kr$K?IZJornR`Ank$>QwaqPPn25MM zVvc;6rBl{IrzrH};)sJ3YSdLwTX^-$JL&aBj>#{napf&h6##IPG>f>3KGQ~coge7> zV|hnf6|^goNjU=xT64_*h3VY-OFGOM;V%NTi@=!Rm6{N0XN$?!Dfl3_>50D~cta04 zINEfD=@4gwS7J1tjJKu~WoUY-K|Dalu1#}p)2V#Z?yUTlt^}WT5+Y;voZ2uxJx3)t zx3{72L^%on30Qz_hLw4zG$v;>CK951<*S`nLoQg_0kqJ9_PT;q%QN8@ny$cf^dy>c zt&EV1gn|C1joZrSGPr99Bk%n8q-bYyyQu`D>FUqtr1=o2m=)4b$T*Q5vrWD!j~Q+7 z!4wg3;X=Op3<~*oJqaZYboJAlxCn%K*-msU)xoE&^o{luaP$~hw0EXDR_tiFyzY|- zCB;GDyR97ds^#FdjRIk1mAjDYGp#Q|@{V3u3#;GI7dS3J&Mg+SWvQ6>Q~qqj)Kq4B zB?vOJ@JO-0`UL(Qb;p3-kMl0ikA2EG@ynOSH^yj`*8V%Yl;mXO^%Vi)qhvImjf#c8 zb5>OE`pOWGR|>W-uPij5E}9cOe&SJi+$KZyn;0gG>kQ%%rosXsJn9I5!j68hv&@8p zO?VjJnQ8NZYI=fn0wa}P-CM;WzaT^d&S}rP`rvi(_K-HOd8^TssJ&4=>~_hj9+LsU zIjtH|oZpCK%v@;nLZJWEUeOC*Qrcf;X6hUb|q{;9&TYnYPCN>ia{d< z9cU{Y?-6+WfLlT2pjsKr;|5@9qej-_FZ9_Zs&6SX5y~tKqAKs8_sX};;1_J5UB459 zZY-;>Sc#Oi14(+!61wH{`~VB;5N-;k(pi%rGgfC(kZl|`2dI*^=y{s?@{|@}e86?3 zuI_!hQrUul&bri-qf$?(v?Hn16Qfcw_E}rlSn;E_9v_u@Y*gw|m3l0d%BD%%)|K;1 z&000%>fTYQE2C16j7mMMQf2m}Cz~*BsfW5!yC!ylEE|=2XjJO*sMI%87=~6Zl--rK z(ADC?02puTT^&^Kj1WV$k0Q^dB4JzFB2RZkjx{eqdAvwYe58T8tt(O|Jr2uqk0+aM zE>cQNDC7C1cEyjE3TCHLbw~t6prO0TFHiqc;g)vTEIokPF7h|46?kM7VGH);Cd zK(qYNrJ^fyy(P*Dv_wIFP`sc{n!9jLpFm_WBxNUyy6Udh%SG$R#ftEkyN0um46$X9 zGpuRMO*pdrdV+IuLWf$!IU)Ai4WkUPDbezbGNP?TDg@q3h3I5lR|xk67R@0##M&8- z5m%YPIzB=qhDsDN%?puOj}K>Vha(`rltEZhL_2iVU|rt9qS^Q9MC(D&4Jp!U5Jq4i z&a_wlxM+G@j0xY3^22S+Whj(2D?imzX^Phe>SeJK9Ew|qgKb*7?&VH~79T$8cHH#A zSHI@$-~YM)xOdTKHM1-`!w1^XJ6PMwA#^%p`9>)-&RtViD)7u+=bMUrH+|;D`@ZKr zE1xHF6x4nYMh;!M^9M)el6`8_7=s80gza68kJZJ`zYll6@BDTunX2CV?Hr|N>}5#* z)Qtek?pgBchqeQI#_v{P9w87oVTz+UtX~t&bpq(qF{e@pa>%Jng*^PQW$&ul`<08R zo|OASu<9SZTm!7?(lX`BwP92KTG6hvXBd-p*4YQQYxT&gw{LHeeeg!(I0M5Dy;L7= zPsTfxYzSwa_6=Ap+$vEh>;1Fpq3r~`zd8J5d3C|iD$}79UfsaU>H>^R zH)j;VZR!&dXNfrd%dH1ZTFCpUvG>!wpB#IKUG;dKny0jrd_4C{qs%9GUmbft&imQ1 z_hY=D8GAp<`{}XwQz^VMuer@7FC6+v8ec97k`BCR-iw~;gW@oYB_-NPJhT2w8MBP9 zx=?670PX}%)Q24(^s`>UK=jal_JbjQzWL!#t%x61+<;G5pZ2XE<-jAn5=@^fJp}yG zyh|10ua3NbO{7OnOj z5*PO)%rC}BBNMiN+6g$YVPFw#TKnLc2kaCh@Xr_*%tWXH%r+*U;DuKZ->(?I=#_a9 zcqEdwTCW{6CSKkAAalyY8I;kI0}&jt&gyG z2U@Rb7v>YGt!y4Z`BmBf$KKn3+jW)ozVliJ~crfO6z@kpQndxpyF10G+@;fD(U&0QI z*buPrc*GxBUOobUWcB$7{E@}!Bd|w2a2UZM(qROWSm?`bEV1IrWycYePG;8O7O>wA zqc}i)fYM`|ot^deV`u05F?M#JKgQ1P_s7`Td4G&^vq-)4crgs+gg?f)!8eP)KC)BR z2cIW=wBjg{7D-EtjAQ-!vSYOqteP_@X7Na2w}|>gv4OU9qAUu+DbtKZVqLLRw!r?> zCMufxA>e%jUkjuEM{sEv;FeawVS5t?s5gh~O@sh%G(@4_JUphFU#S`fjqB}8i`7EoKH>SvB-0SxLkhQ z>*r>XdX9rmB0viwu8~0P(?g^-$Mo>!;nLOT2esn?;8`(uX*vP6f%$|GwR36q1m!hB z`&L5ccvqb?`oJ^|U^-_Mg1L_qB&Cr6Z39Qrx2zMtLgtq6Rg>N9#|t_Ze)fE z4Fm>uIF=zpw(D;w4F*0lN((ETMPP+ZHUJ;ik(xr#xCWiQz(tO3oIQ($=!T9vAq`AUnYmR;Fes+*EiI-9Pp zNOszk{8M~=71)R>j&`4Adq42htyiz9hWFZqU?z zZo-{-CcTKvD|9`3S@SEaKr~rTCcr>s{I7a6zp|gK$PrNz7f!y$`odcGgi9xgB_@yL z7~V=_BS_HO6A6}~rQBN*vL9C*1m3I5#k(Yj;65V&9&dwDSzSaYA7mfp?sC&s-!4m? z!il@7?(N@;KcozGHXtQY(!w6_JH{~9bR@s-!*L%ajRsu$J&qEhB*=R#Bmw5pkknGl zHE3E{;9$O+cRc;Ii;&<~#a<>`er=rK5;i+{irkizvkTDw9bQ#VlFS zGa*b6Uorf!`1CslG0?Bb8NZR~C8zWd*k>{eDT%x@nbSkyjmhnLh}rIFX1j}r5?w1L z1OCulKOVuQ<5|pyRse3}mevwD6w+D)^C7JTvtK>gPiTV%gJVrMv|%;{Dx8^EnvIth zKAl}MyRBSuiRd(rR&1?=bfKlZPp6E!Pvu!>@2H5?Vf_2$UD=|?({cH*y!2c~6WhE$ zv%EYm`F?*^oI*!le`a}lv~oa3dHXb*XA<;hB^Va}h&Lr)JkxRQk9qk;CB5nsO8Uw> zsid!tQ%ZV;$4FM3QsCcX;&;WVOG!FL_nGCR`tCTbC^rkl{-Gni39A&8Q8NVeP{YI; zR#$ouBmJzR4Ju&ywMu9;i?dZC!p!T}`F2a_by&|*ee_EUv-(|S7YqrE*15`L1oquz zvk}*9%dU*AaF$C>G<|L9=IllLY#}z*vvoAimSYO|eIy_UC~GK9XX&0{HA&1XhS(&_ zfZHUeh2F4&VHA-Y>S6ivcaL;D;`!kb{Qa1Ibb71m22WA{wVtBUYdl3yt~Nf^Hv5}X zWltNQB8K6{{08t^(R@f=3+O`f8lV}HR|BXK@hGB(YRAOtg4Ek%;89k54*nCXnk60U zIt7=Dm7OF_Z7hU#&7)MN=20qC^C*?7dDK4auZ9}?i2uNvl)nM?zm2r%&mcGX(WJsN z$j#wHjA-`EVM9IHu5HFj>PKOv?yIXzRoC?Jav+op$Yf2&o*i=6 zGIFPO|32TI2u<-)d4*gi3C7*P>Ei>O7Hw=-a}C-kTF0vs^`#4{2DnFZDVRrcNqQe? zBbTH_QbjJ6e~yvbN>Lutwp2*(Cv9tm^gL->ETj*SJ|4;*CcT(dDXXBX{{=)X&)WWo zI?=8Uk7(uG{oH&UJEh0@V)Z~gZL1bT+l!Hb)8dzG3$Rr!bavP3<O%kzI9ZY0r(ExkcNtfeJ#PkRX8@(u}2#l0FgA3#1p79ybz$ zd|^p&6#2rEU>@=Xbk8t%zuuArb>~Bp4EvOvu(wA-k{S+&BpD8o^sOrosN1~*BS#1%q+$t{^fO4(_UIprPhQktRb$`vVD+lrN)gOe z<*IN@Ln9d@;*k!1Y9TQ}g}@f*8%JR{>q&bsx6s^}HN~-i9h0HBHFDjV>gE8g>ei$V zMy8Xm9BQZs#TnN1?JMg41Xx+O& zKb9G|^)8L$m#S~1<3ziUT!c<*@(~2r-k!9#D82R;(ra%Q?d{R<7D6pd7ZACklI>k) z3L1e(Y{DP(1W7r{f%A~oxO{BEA2I0--ZO1MUgS~B9M8OFwf5m%PUKSKV1S*Pb-4zmREAJNuL4PLs^ zlCJ71=?T-Jd2x>`*ZTlWbtpW*{CFTMG$5+m0o-s#fL-kmIp>sf*1(4ZNLm}!Zxg|U ztu>V4R&|7D&S%)t(V9QTGuY!?RWAruE2Me?$?XGt$;VJ!gVzt{C}qy`6NOdP%u(G9TNh^GuIa+)k@ZVW2w6ZKg4hULbuu1*C$2 zcIIgx?fENi0V)1Tfoo4&Cdd%rH7gQ^LqYn|z)pYxN=ow(TtHMQ839BKrnpxN)c^I1 z?nI^FY=}IqME=zbOASiGvQz>D7+6!-f35{pcBN}Mm8cH2n5ykJ`J1LCqN9TLK{O5f zwR9BON@El8C*qXB?+Cy*4MkueZCVQHlN>fSjfV6o((@VtX^B3ELRu%W_o)e=ESVEw z$P7$p+^S>Nu27m8bhIHa^B53Rcw>attv(IJS)|^GIPg)dZvMiip3)I;u5RNg7XZ}15~v4kGjT!_Vnq!@2VG7a<4O%?%RAz1jXn!MEbTvG1ED2g{A^ z{~=$z^)dxlu|0z9N@gm((PH3_*Mf$;e01<1!aaCtLX|cT*^{X^4wa5ZQcs$J)RHia z%YRCCk3_n3a8QtbTW=pq!L9cWrQy~ahf;CtT|?=(^_Jlx4>Fma2)`%wF>DgLp)=Yx zXJy-rR?ky$a}fKiXFxaeVkm8_4^#QewwnV+E=kN7I(BIy3n`{nGn=xw*+j||JBn1W zO|IMBFmuwPYyko0GMlpE!C7QMOMp4xvzkjg~%48Z@=^5z^qVrDf=tm9R2W9i!qAg8^Z*Sbm7Nqd~mY zuBRIn*>?HwkBpIKpmbUis2mGPKzBSOOEW82hG{btSdtk;Z|w+5WWcCkNitw4up}8~ zNmj??li;JYjX*nVMD!6e33RFxm;{h^-Sb=?qJ*?D%A#_VHWk_qeen`Kzc=Lsjq?scmtD{5P6ncS>7^jfO=!vzcZbUaM zRKo$SMtdC4YScqGm8J##2e-elr_nIOK~$@&d|2_Dpw*OGD3x zq6}>G1hK&g!~6)J=A}#)-_UL>$xx#@+>-*~Le5bo;ZJ}UNE2Dd$&Zrk2hJn#Fv->~ zcrRZoJ3XVbGxSkzo7$$6BxikOg(>fXWIgqy^ygYmZ;W9;^Lz|u^f}WQ-h6>}#5IV} zWa#}I!wkKjRWfL8;7}ltCzbTgpA$-kF)hlnl2?lmKbwGWq>9z0lTDnn!AQb1045CF z=0w(*5!_2{>}~4^9^4uwqdL^~B>ZANBpJ*8kOa8!Bkfg+@kfaF|nK+e=IUj~s=q9~@tweZX95rwpVT!SiTKX{Q`H+^Dg0U63 z5C#Pz!dXn~LV>icYe6!+5DOzGLXN+PP=?rAwflIz&_U4!dP4UA?HPl1Pugb0Nk`rj z1N2-Rpy%;`ld>2ebDTT|PTKxrNzKLAFO@ASLixaDa@({>lWJ5JBz{jjm}WibckCnE z1S)~0pkpV~Jd(^l(=Ln+1{9<%+Q0+}Ehb@J4_ax~uwFJ-Fnz8#)8VTlU_T}iUmeV( zun@lw*J6jTA32HrZ_zei!6Xe~Qiqo2)K~B!U>w%Kypp~Ku*4YbrG)PzWyHX`8<|FQ z5MhtXlC-{WB%AT9%k9TkCk@<;wXQ<1hBoGDfkiryH{)}VIUA3XI|q-FJx|}5ACDXJ z-MBG75jW;1ALT zo_1qC%AjeKO283a(=s5+5YJi)y$eF|WmF=PV$; z5!i7R>12kpL7mp{HH1~n!0aj!h0#yF3e}(C>L&HD32Fnhj?#4>>}2B{psDX6?M>kM zFgxyUT2Kfqb*M4+i{%p>P>eGD5WL&P&B<&Afiuz8D)dWCNvAw9kk|>_#w!UQL>*Ct z)X8!}N+B5iu`92eijTbEw9`}%e}4aqyc9)_9FYUq5kLC2tq2_A?*8~wJmzA5gReq6 z<<01zG-d!5Gt;xB!8not46vIrm~Bz9UO_DKNom!X6U)MZ+{ajUME2cY9JB6YjJ zk`3ZEASuh9g7&3<-FaWI65{4biqoZ5Plmml8iIth)l;l`9m#?_!qNyn75HRHOZYd4 z1mNhWgYJcU$e93m+JR0Zgm=>z!=WPvgxKix<$)2Rpw`Y+=+c_wRA3 zio&KocgW>6XplxP_=jB2FvgbX&vjFG-^m8o`5YKuWJ zbY6r#bWZ$frL#7jRBBgzuQYT}+PPHHZ@)Oa#Ai<1Xmwx{GNsHP{Y?9vmE9RuD?iQ(;Ohpr;a9^>wt*^h)fz?T|F{E_b)?&%h1t0OWU! z?O*FT=ZdeNOtBP=zOs*M%Nb+cB1Pu;keT17$$XP%J`^(Z`^=dShs^vweP#{D$dC`l zlW{OdLM{A0O)Xa<$1s?MkeT17$t)`ynU99d{62H$V<9uY&z$*q$jtB4XRh{wiF9Q# z6ySG^n5@D-rt0(=qu&Bm;Mf^4jl@8-f9pIve=+;F;4Cr5!5Mv%BnTDddWnqC@9TsX zrKg7aMMVmtW5|Is0>~lZ5ZM3~zA9d1?jGH0ua=hX(+h^wd?5}ozF@;>wY*>K$-}!wD+X+AEHxUe%I$-w{Y~Q|M9Kgpo6GtIsl9d zE#1vF^Mu#oh5WR4BpdxD2ezxbK19vWSKjT(PJZJ4#t)K?+Z!(<9d|dbXyo~^MxIwT z^88RE{o{>1KS?^)|DQ<5%hu;?h~eh-eR_yj-tX7L;qYZ%4@bhoAw4XFhr@c9`_k%5 zvLk7k>N@X7_C2Tj!HjzV($|8GAx0lBx$;{YSD>@PxTJbMhg|PjTb$S?CU99llTxBn zF_L~Qu&UsAnJBX6oTluPU-w6Uq85ZBPUZ6!R}W>`fOp> zgi)LW!7k2$U^@RTfh&MsalvZ1!IN$O1=w(fxj92c`?-M`HdZpnyrty3k%XLsmh zxW>U!>$t{K?=~gVhL%cg+%7Z&xD{iO8{dnCJAlk|dPX z$U0385sORG5xqIHR^z>$)C@DM7UN2x#gb+GJ=SAXv97dCGa(3b6{E^SMJfmrNQ(!n z7C+7azRn@U2>qV{(M#ffUIy}ON{(PMrsP6sME4H{q^Qq(ZoQXvA`&6P0TH3p ziICkxUzYa6p{3J^5Sc`T^c5mR$SV<{)QOP2l+`g1A**mF&k^uHNIC-bWuznEuW01? zu|}R(HuC&XBmLuzJU>Y~*8iVKN6^ltJTz8ST2BV3lh(5|uZK8&AREW*21F>W7n8vD zbHt43z9-{C@$|J|M@_?@FKlR1oD_;fO-m>ag{bFWHnqu$&u$YxgRcC4Vr<1HF67_t zk^cp@h?>h1>E*$uIH>Q-L?luct-x9%-Tx9C!ZH3^u6pTOQ={SVH$~oXp0rsa#Ia& zk-8z;%hVR?@jY=B#V4ox`H`$}&y2&|Z*7Or6e57)kMHP@7fxl_a};9 zS`Qk?W30ad3gR3ViG1;EKYEX2BcevG$(gb2||E_x+d2;^YpXGFD}> z@!~*mBQvndEBtt3HB$EU3i-lY`m?*oPq~tzk%zOH-rmPK6pcM@xfUO@772_!$WA|> zOcc0THm8fJSZ-*XP(cORE$^~I8UnjF{$m%7;ijjj%`{dW4V z1cQgb({IDl&5Xq+vVb?08ou z)E?bm+AHw`+(O1p*3>#ohZtrKRIp$fXKae-LQmENhg-JN@C3?u+LH;n#c9oIZ}E+F zS_vi&5hFvkJ(f!Xm?yZXu~q5x^w$1Jv40z$TME!A+n?4(2F8vScd^u!87Xn2{+TY` zycJx+QJjK*xt8_sn#Dx%_Vq!a$_U2$HY-Lv9>xONGOd|$x{>0mTe+`DX?)-86@J2Y zrF7o?&GBq(?`XWjESK+vn}N(Uxbv=`Oy(|o;?mdFiQ@GaX(wZmJCVY-Z`29<2B{fk zpN1{)#1w z=D+Xs%T9`Sp=PNI3g)M~Lt7Z$uY;5tNhjSyO`RxL%=6L4IKdP9W##1cQzcVzMeda_)_$S6NAi zJ2Nt9Z$q@oxkO9N3qAaTq0xLsILp22Eun}U6?pE{vui1vT}#>Q0{^xazRwhU55Y{X zdSr~!KB0C(iU&FI>9iFeLIWJZ3xVFeU7e`;01#$73Ik?^fl*c;2q=lloj3QmpNeH!pTHGzqdrPkn30_rB-c1UqaqsKhJcNRaFFepI z4B!;ZFq5sw(uDjIZ}9^((Kx*Lz5|+Ofz#|}od`c$U_g(l|I~_pTR1Wa6l8tp5Ld~I z(fR&_Up?x8)BeE9a&o>uxlO9BA+^-r(ony|OCS6*oCFtyiI;`zCd8;m(0@njmRMz# zwAWD}Z!LK{zVpG31DLY{pwEU@6U+vw0wb-e2*RO0F#$@5=tlz@N9T?sJKu~RVtLqs05O2`V&^N`k?m3l zGYl;${$PhDO%V){r4C3nwrGYJIqcWSv&O^DIM7Pqgb7srm$r6~Wn4W<1+>v(ZpD*O z;ee%LRD7Xg?H-F^Asyb@g#~n`6TXc@l78gXtW;E>i41h zk9@B#InhknaBQYn$R_DSGhWVR>poGSo{9Qx28T#QKmKuBsHpg@P-RAS^)Pf5k323d zQ}KYZz@R{!x0p67c0TXjmnDPl$jp=%_k6D!&_FN^>8GUN?e%!rs5O(zzSX?8hcF88 z4TF^cD`Q;0Rgh1iMG@e&*ZB)8-YR}qJ(ekcP~u0_16A%{y&Z`-4{H!=6rsl- zY>yox!tjvorn7aIU>~#aK*gnr7OtJsPe46Zl9@oYYVzr%P)2))kGqUo@Lpgu!ec+)sUU~g;e66 z|AplUqWPW`qeN-tPnu{oY)U@TQHf2-pLU2~tHr-5`LE&CF}*TL5L)j!HQgg-@-0VY z17_))HFbvmM04AZk&foPSCWqAydP?$pKiaPVRk!hS72zrpkc&o?lA@dSPanD+@)d}9<2Dg)+PQ8{$#y#Terj~iTaQ&1UzuSY4-16MaJNTl!S_81l;!Ruo zu#61&ghk9A9QQuewS+~?)@pGV@*!R(#jhf`oXd^{U`_v}V|(5`+4B3_X4olYGdMj5lPof-2@6RMlU^g&X0!M&d z5^mH9q!eiEx$KmD;jH&mp4I&l1$p`&`YeVo!$vlx{%_Pj(%= z74R5m)yY)yl^xyR4vugDhQ;XAZDXmh9tLnW*<9EJ*%1}eVxIkJ7sbetTONBWf(I>T zno8!6oo-Mrb`Pv14^v0=c28cITi#DG#imr606Umu>DMyRoEd3FeqZ+$D zr)^5d{k(Z-Z*vdnGz!t69kN3K#$#E$RJp#^HZ9E*!T?9{uGGS1(Zar@6iO^@7f+Bt zmXmgt+>M@w)KiXL$KCBg0me^ zd^j$ewp-^yt2LpZeqqI+4fTr`9-t>Q@FGQt$Ihzz#&CpmMV0QuHUhg7T?(9UZryz4)0c%Ct=HtY7@7SbEnk02FQ}XgzYt5~gApysG=gRChN?O^H_tIsX5(#q4HB z@Pn(1_j+2MHAV60;9=e1K?JD2CrI*jcnC8=5Td#q*k3%w1waVy@!lKGc7146Vf7W5 zPHIIi=66PkSj)Ca)X0h{R9nCmcxr<3zX*>8k+6Y8%<-K$EyE=evE%^QC(+)K#RLg$ z@{+==VuA7v?Nt)8_F=zqRiAOKeAlMpeQ`R)IV?abW#s}N9-NMGd#F`ucZWL8p<}M; zXI1M5T6?R*_e@rguc|Iu>gH=?~}EN=Ce-pBss z)vqzO*C~K}m1^`vzE4>(qO8*T&yF-UN#L2tonY}b1jZ!E8OFI)@>a^MUl}q6cet`B`Y1VGtAX~Crjj{F!|cCq9h&pD4s<%8%24l zP`YZHkj5dSV5qXP8Ea;R~%D8eSD&t@tGsAJQPm!2mHZX9L zOZux|+Ljz80vMaM`c(3+v0r9C9PHY=3hCa-?<30r}5ab4V1DGNjkpdy{yK8+52(!ED?(Lb$|3Z||Q z=~RM&K27_lQl9|6eJOEYMGDd014c`kV8c8 zK-f~(y1PH`u5oXFQdgU^zw+%;J>+BPHgJi5D80AH(ne}y{%Tp;EOo=BZj!_8jn;+r zqypBHDjn-d1*|6(u$~k%E{IgKX0dy3F271yGZ(q0w1wE=8>z*;Y;p@h6BjmpL)64A zvr?OQCo+{3i-fh3Y$qAd#Cq^}@X8etG}G`3chz!L$m2j=9PtHGjF zfhGYMjL)FKqlRLS@{>+%81_g!V&`TCh18%Bipe*xQB02-eJrN&_cZ!NJCl=i_oqz% zvu2cwbjswcZ_NhM{bEj7c)Pm`4G6?CcJwpjCM12~9tlap+u~Cf(*N38!pKIK>{bs4 z{AAygt0M`gQ~c^h__o}-GqFD>Untx`A2~mtVU@Zu#BYKIF?WSrNfH*h-U)d!Ep7xT zE$+IeZf`W3sh`S`yYGw-|0WbicR6s{yDEsGuGe|{?7qE2n-_yNO!2^YAu1o7h{7ge zmV?*uR!rtjYuOmT;mgrh5Rq@prg*NmzhbuJaUxR_qR{Ap+{DKzuW} zDYNzuY-eVHtoBT)Z0MS@R|(H$vPz3jZ|lxCP8nuqUGZDY{Yohzf}EJmw4yVpjOx?d zv=JwOaMJ{8sc|*qU&U{3DpTHzqy|c-M|eElrIU6{Bl7x2>CGy=)k}N9Y3lJZrD}`- zXq1^znO#;!OxlY9(NGNU;82Xp8^z%67v|SAo4Tehb9G&2mmnBP+q*`HSu%Dj?r9^i zCm|J7gr!~Yv<9=K?RA=5g!+SX21{8BDd~?1{|t~Vyw%Lbh%Iq8ld4m9v#BCiRldrL z2VJh!AL(kleCQjv!+!S!#*X68xAi*uNAY|TE8Gsh99=2?ZX0`wxqgs`nK@d?i^sRg zhf}uO)81hHF{7^k@B|0=8`abf|^A|W`eXE`v!3(fhS z?+5zhTB~i`n)vD9-$EVy(_w*WU5$s7wp?Vv(mUi%OUVPqQ>DT~+B->&#=c@XB(3b> zKudA3IM83koBZJe^%a??UU;C77X}ez@4magviRzEQy#)W|GQ;Zt~@L5M$*OGyxz@nUd2MEBs^YwT`dNNx7+e-gCGD++2ta^wU+{YjPbS%kcR@c ziz-Q|;2%iSKVV!HWeGz_k~$%mC3SKf8JH4G82&J(Py&q?ESM%UcZ)WbD`UZ?vr4m|M4q@T5$ps^FkfKfhdCwZyQ@u+C{4 zf@Ng84@^fnq5}wE%q99L_b_TJJ6ztz9ywlfCQJUgY@TMqn&o_Ox@57&b!p5G$NSq? zZ)BBX5IB)S0r*|yw_2!Qp9-%}rmVET)hN?y5xd8KWs%Ff!k_+xFaVYocVG#8ZCmj= zokI|@M^{};AJ)$k>BCOyQ%xK%PWb!J`}-CJe5On||6p+v(})x^kZI6Ekmnv4eRGeP?kZmC zbO+bLV-e#n`tIvfB=_vr>e$!qLh3A=Q4Z%2wzB)#0oJnKX|<1Ro5v^|+}E6PWi$FKY8Kt8a1XoNJZ6CSXw&y<%wUD{ zO{kf@EYU+9|C7mF%MAI-8Z#1%$FWU_)F#((wYWxv`%rf8Pov~ex8MZkCv;Z_E@Uz9 z5n~lkGOOm^zXmoAx$MYHh|AEpxQoU+oqaTAeDH%Q=@mR5^XHROawW#K{A3$y%kRFH zWLI~>A=@1-Ng=%M;0nQ7DE`E@u=sKm2W)+L)8LC(>523}w-HGLmjDY$kipD4!Yg{k zBur<=6##oeCHA)tl%q9m0uK0i<7Opkgt0IK?dl@g4t;gfg@3>=NNP%}dLYF)Na$aD zo_DGITTOOWrQj5m&$r|;ZOth|gKe_X?9#j-o^9uNrdh;VD;?M}q z^VS|A>IeE0%M@;_a6Tw(swpCz_1zu_+;C7%=8WQ4Ok7xPDhE{hN2h#n8G~!)+EiWG zIPD#CwLRBeRu{b8e0|Lc*e2W{0wfF~T)O(S5SctsCiCAMM2H?#t~hY_(!$>qM3{u3 zu`?`tUS^chy}1JgV^ijhM)GAofB-C69X|P^!t;7{V8CE?XfX(h2dhNyPlor4tQ}tj zKbCoZ_U>KgtAHnKEY5xp|UO~)uYs--b%Q!iWx4!<7!7N8Mr(FiRYyb-}njU2Hm z!ERR_KRAiB$xA}NqlxIW2ay6nrLXS5t8lM2;j`kw^~G;&QF%{m#dSE%(Z?LchCO5n zs}#Q7I+mg8T_iM$QsuUd59l%$R;zeOCq6}Ls13>=-o9IJi-#JmkX9>W4$6-*O7d<> zezBuoR8Is{SATv}IlaziW3aYm8q-@frh-RQP*cRN9s~c>4sj(p9tI)Sju7yj5fS#1 zEtFF``gxVSoF24+uoe2cNRXb?2m93_GdUUaSs*I&JymN@@eR)>L#FcaD|+Q^^GY0I z01FL9BQ_j+)<>+VGpEzGFP95Z3p1vTVJ0iGERow4$I-QY^w+-q^FiA_0{y$!k{6dl zNoGkkQrxo?g0ZKv_O9eeElU)I@p-r6$&s7f17fYA4u*mZjol|gc!lFc3Q;&x7;s{M*S zvW(PIKV%Fhvi-l0iO9M$w29{8&3_YBXxo+(_70x38bLrju#wgRfV^24kL0unA(r&^ z`VD3iK*=ErhChU_d!tI)}tN7)brlgdamNZ@~8+y5xEh6yj z>t|ayHevVEmZxS0fAeBtqwq&_WY(Q&6E&bVtTmQIzYV(xoh6_766eJ)ZxB6_+y+wG zuZRc+V4b?-?3d@WVl?;h<#Vf7h7Vn8$!6qGy9RnFNQ9lL&rq32uqsVV*!=7cou!5Jt^yLjh@;{CWIsou+qw`|0{ zDBNI7rl+nfKe1@3gLdXV|#soof%8Fc+9R6oemrr(r~nuYw>0 z8!WyCuEI7yvx7>+BL@hlmo(rps&Lv0M>AM%yseJ>(FhzoE$-jKP_fa{6R}@#(R;Mg zdc+;H=1O3*N&72lKfoWK0!;K^9Fsn7+6z7gIyDSm8lc*4 z52R>J2Fr|-_RCeKp${g_@-17hL?UBqRr(rnXmJ1FMxltww(+!v^8wyY?}qhiJk)r5 zYOH_*i^ZqUW9`{1`c{&kW#SmDVi;+5M-KSbO1HcYmsMaiuqPl&{IKa_efzj-$wvl{RU0=|ge@OdpPyXXZGl{cBa z$wUAQY{J?1+qu4o3M?RmTIQ21pg5Po?kFDHKnYPC4#pV@)`eIHB0?ged!>tT zVQz1y0{jB_3F(SxNDW8|0>zbze+9uH$K4D4K?BKo4ps=Iz)K`T1!a zLw%%x1hXKS9y){~jG>6H@4$HH$h4hyqm*XQu%Q}=Lh7!MjjOyY{zg1I%y?3%WcAZOSqx*;= z5M{5WV{W)K`{=-ki8rBJE)@$vvNuN$U=okQE@psG>(Wf@h#y&xv9U^8=BM--1X zpo+efqLh0DKgYkY+NEUNIfyv>MVU-5%{Z-B&4sjlO=d$H+iZ2}3!X>NIvLVbU!4eP z+Nc&o8q=IwusW50{})$7OsvP%p^(0s_xFXgdUPZJ=r;L1v znE7{Zu&3_(Q`KLOCJscaBMxLU={OLeQx60X4Rx~F`R1USgZ-8Uf}92}mloib0YHmjos8yo98@X2{{ZTF4QGl&qe^ZU4>q^KPtqv_Ddn&tvbF}&?sbx8} zs#v1Aq0}Ts!IWK~o>>nt)UwxY_vP7LEleZ(UYQ1}NQR&gMS4?oO~dOWplY9~^8=z7 znqaxQWH=eKX882l7fX(@vU7}D2aI=F->Bm*~O8c>j~^^QmVKx`&fwdkE)ouX`N z2G1ZB5v|vj6}6Qaqq>>MX=3H7iHnTc9EBcP4|J;Eoj0_1*99_qp_~*RG@~N{v=oCU zNqEB2B4L%@%aV*iLY*|&Gg5sSHU2e9q+;uK?>h(%JiWU|JS2MGQ#%>|b11s}4)1Os zY6#8XrJ^6mZ>Zmu{X3;I9oF<}~bzE~of7K-2Ntnw1`97Ig*z3tmm`vN%oYJfl z9zw$pWzJvGN?Wa@)vYG=z8Xhn)@_I5_B6?C04IZox|4C{*Sll;fi?&%FXUyqO-S*| zvF$naS=S&m>+1Dcl?23qZ%qu}d;GiP`KketSuF4G^@z`N9ASjdE(X9N(0@RH!;W zo$WGM$mpHd{uSwg{Voou!foGaXfd{w~7P!wY0@wjw+WB8C zSIs|SgmkAc&(qlq3pcLKrPFCEF};dvG(EN+pdhmA8JgN`kY+{5pAzBQ7SIG6IK1jT zk7XQA=-UNnQ)r64^EJ=>uLaR{B4T*;9blFyh~i|ZTO)P1MjBW)60oe24vlTcob`A0 zT80*bN3%1Q=if-Cw9K?_rmoPtD_;S|4+Q zMNMNbqITAB*!?RnFX!&W1MVMrVnb-&} zb9eS=3ODG5eNYB6hB=F?pwF4o%c|G0>3NIj7f)OJ|7z&@1^qQc&zt+Jhn_FMbC6=s z?{}C|rAh|+dQ~V0jOopEf33cC2Co^!h!KPJgSwtONS-%Ht{NmU_p8c@K{b<&WVupp zIMALW*bEA41I3?!*xKFY+VE+rx0V;HcN0?un>Qw9k;dl?$~|XLt|*_gyDaK*MY)C- zy)`I+{BTBnteo7{Q?n~V9;T(RfEjfudRLSy_*+~x)mvdj%I6Stg9ED$|K1qoR}RXr z^@s|wx0GLt2f)g5?XIaFqQxZDjS(t?_!w&xBcoh%)l?bhN?|fq5e0^ue#_Onrph>H zQx@=}Q^H8f3G#R1kc2d=`M8FHG+d5RF#{;sJD31E|7!RBz?NB^ef1k|>%p0f!jG04 zkfOVWll&XL+s{7b-TUw8^s_RrqH0)C#1o(Qsy)Rg&c~VowD2ow5h8`33FteGk&^6C z;~$%za?URFZFKOOmj!5A<2}!78hBnW!FvZ;mLDB`fUN2;Q(c!xu|EOhmAmU1Wzu-D zYBxacl#{!AlfpI%Pn@yvirqCjfiaXiZ#T2U#O+oflaX@rs@_`ah;z#j8`!tnv2O)P z!2h5OC^t!=wNs$`WazF@k%-omZ(v`z5Eqvdxt)Nrpd5>(ob;{A(ZPTxLK2i8b?P(0 zhBqNLXq0}k@hRbh>xCiY{H&1B)0Q^{(`BeD^j!mz#$}I*U=5$fz+D38maF&_$pD`$ zC`r3qb$@x@{g;<-yiK>tQThGlM0ivqllPY^!XqqEjDtnmE}we|dIK^ZH2RWky(U1y zQF_yB7<&+8oK9=Xqmsjk|!eYngH+|_Tz%!}1^0IBsG z5Z+3(@)pE01R=DLOG!w3@)c<$ZpJ(%$D{sPtJD z#Rr^**9;Y{*3=E36|mL~m5vRE$y$8j{4-Rt&bqLkC0En$q*|{o-~*K%Xx5Vr&s=ue z%yqwL&cD))v7Lgufq<|YXI$CQ623ZMWHd41I3=+gxs^@2w@+4o)u}$r&mX*9&f((8 zx$Mgyo<6_$I{r%_jP1~Ym<1q zjl7hisC3vne3vrC0*Q{wE>)=oW2voLFqYb^a+aEw^IWQYwwx?w%gIt&PX5)F9q)h- zrW3sbJhGHn>J@6)QZKzE!_I=#zdhLrF%4@t?S40l)>pkuIP5K8bTS>(f7ZAiX`=g1 z`Dd8T;v_Z;jCX2GGt!z7fHON~#c?sf{Jm~}FKm-9C5{O3;ORt5J2v!gf?fn&)^_Du z)$7dWpih1NnH^;;W%?3`Pm7FYg5T*L=O<+Ii$Kng;LXoweyD|g8U2*}$j&I1o3V-k#;kTLs#$Cat?J2* z#aF*S;2=HIMp(P}0|Mw|@+TF+?f&1vU~1djjK^rb>{=Gko?&9Q(7J_#!3OPfm1>t6 zL?-w?H~$}nOszNx^JRomBV|e%) zX`04y9yG9>a76b{L7qzQh*s7>&1dwb^fk^_D_LaExVZbkJ@j0%=5DZ0AFu+4lF2Tk`! zc+kUf?NMr&vR4=~yQ>K{%Ma$VU~0xhxyU2FDBSG{D>H^BnUoJLgECtbG>D*^RiiHt zkQZ}aNM14}17SrZCsv*PAe3gi+m#BYH}g-v3;#wYqBNKjACBurN;dS=eeLVjExZi% zpjCVvA<24jNs?I4=)Gpq|FVd4SsMqK2!9tYXf~?!B~#3ZMN;4y^Z_gyqd-I!vzJh* zkIO!3=rTA0Px>Lm(wjaG%qqivlV(!Ym#u-t(W*j#7kD(q>^uo_+FVv^Y-Bn3LUA>m zl<*TBX>J#fWHNsXPu;#2Dp9{OTA~JZflh^u_6g4nLjV15OjcjY4Lu%whtRRxJ)R;s z*PN9qQlk1=a|-gyXsjK*H;byR4ptKcRA&EUZ*qGy5rnJbZ*ueg1sSs=cq!9*!B!}$ zUNHoT0K5XXZr?8BmTbD#<-*WK*)Z4IN>zHD^c9uMjO+q8zSFc+1br4_#?A<(*VJ4r z={`P^j)sx7A-~Gr@ANWyM-nX352D#i23ocem`m7by zZw>dlvZ&4hjNWrz0W0fZr+Spy4EGUsN-?Nski_LwoKBgZ4uO}`e#;9icc_}>!yLkzw%{0ZMNtqx|!sN6Fl5*r`S z&ddzY%V~2`wxpi5Y?NZe93jzG&(J7G&wiBD;2^?~)|SB5sV?1P@!Uv#jD4ZK{C<+( z4ng(^*)meKZZ<;gO=1?ZQZ1D-X449#T6L)eW?izZclY^$OT7e-YZ9~Bi+c*x5Z45R zUbk6b)^I&D>pqp&vW{!B$U1M_!jvu~#p||6=3%`geOU3ju$V}SF7t!bndA^>ktCzf zB!tA6?IFAzI%4fgIr#If63y7OQk+tr@7Qu$r3i%0O4Va^8MSYuxP1#U$w={rEl3~W z7qlrdAfF@K48kAAg+SvJEHJVX1>~2uW4SWzUl3-8MX}9^a^0?;;!l1fk>jWA_8&Gc ze&GsOU~B*|!84Ez!fAt88w^DTUU3<UIsb$Ve{3Qxl70y*zzm|jpil%3=I zZuJ?}K8aChE)07LMofQWIl=U)@1r%jhL_{q`(Oe{b?SHVTHT+5G{@c{|5cy)gQ>}4 zl)lS+Nc0`w43i7lz9hx})3!X<(fV#16_(|?zSR_*^YDC?`8tInsw2o<~4#A4;TSwanOZk8sI(z;o1 zyaq0Pvn-*)Tl9NSqi=aF1fNmS(V?Otvy5iq-7w?{h76y5?9O-d|Ch*6@}8MMS1{41 zI`{#}H9cX_mlN<-Z)u^PN@c!5Wws~RQ|5^=shFz5Zg)brHa*`Q6`IZL8t&GN*Kk>= z;RRm9CVr^l`M?YLYua9N;2}f(fzDwLlvK^^hsnDYOkF8KpA{yGVS3U6uy#F(;)q$d z8j5KY!XW+1rOZL}DLQ|fMp~~bzUN_d^7qzY4=Ovw!03N1K+OMVaC**P=Ck}7`l=Vu zGahcGE5TZ+;in5 z6J&b+D$Zx+N!neZ3|6~(h3&ZMU1a{A6M`5JLJIxRmNG?ca5_$ZMJ1{JlFU5GjoEbn z{aM!#%hNjlU9ifDyVow~JgGnC_4dD?RwZC*bn0m6lnsN5riUuhoa5Popnk{pOR^W# zb;=#f1$ntqWdikC*V(M+PImu;6(Ll#BV8{_Wl$5`*6%KUe~YVO*~KYmdWBImxFeOF zOF3Z1AB;IJN@YpZHnb_`mFYRt*$slaIw?U#Wni#Jx47#!l+|=J5wjdM(R@+lIU;?Y z@)8HkbYyG-Ed!r51J(_?fdy%lZR;|*IDJ($dn&)NnmsJ{X|$n>)1Rom_=)^QBGT42 z#6a5ywsk;5WJdRzJz_^)NT^(Dt~!bFBcIp6$9T9wA_~q? zmtsJwAbJ(5$qkjngP<8<5uyXY@jcIF4K_=|;qzXOH!WSX4?jUhm8?@;G}&ORVeP+~b*_w7dUPm4b@+ zSPF`9?a+ddjPYqasn*kbp3ASH`007?Jcqn@okQMVJ%_vp&LQtFout>l6X=atM@Mv&HRzvs3|E|r6A1}7^_TJ8GEhG@Oe4XhdNDtIZY-T}yH6Q0A zq1WoYIL&=}4j>>^!{=x?J@7Hz2Ua~c^b|v-@Al+WTFtviFcOwdkSw@(+A`k8uQp+4^?dy9)pjPIyQ&d4!UMNe4<6>5*$!dI zws2m(Ziwr}Wx^gShpRh00$6WF2n*I*8DfRu>M)RT-nW;t?$lZ0-F!^HD=*ED>URYo z*6-w{`4Rn25G;@1rciQ1&rF5xEY*A+3^dR#}y=OxY1&h?CWC~@=Q;HM;DGJx) zNO9>C7l6nHJGbAy`BOjt|6cKLwOeR%bJ?<3Tee&{Ej?4N+L?quVMj5(xM1cw-(pP4 zRm2n%u|uoe%ny;sFXo51}kqJkVtcIXUkofJ`D)#nDh!N1NBv)i!psVk^&}XLB2k+ zKS~PPj2gqw#oANZlw2g!%%)@$Km^ogr4!+xcVqMp)nzx`r2A|O(@`oyw>YzK7Kl;z zV9Lq#c=&!mxiTX^w@6A-BYid&h?gJ-C%Rm!%O1AFdyeq$J;(YLdTw35*t<-GCGR=i zEXg9xP~j;!W2;_E@221MTC~eqrn8uuVE#L!V8k&S*b)J#$?391t8xCDSk-1ug+fm^ z@TB4_}5GNt%AUax7FQ za>TbUE9eAyrzO#0#Mh$oAZ?8DON7-nU@9<*rOM);6c&LnF`(SXx2e#27bk>3#jnW= zemyh7IK@2>X8EBrPlr*mN@@x=J1U(%FDyXK3pie56Ve9Dq@I_|1%nT_vr)$u3{Jw9 z2zrUG43|+ZGeug}%_*`~{xnl0Gyv(;*Xiuhl-rT|cJy1vJ1istaMA~0`-kkta?C>X zA+2^4Pwuc?Ftq7U@2I9FA}Q0KaLv{pz1hf{YTvsit51wpALZxI-enQQG&s##afEXw zK$nw$b{>geSqiKHRaHGn{u6fW$*vUn7-xpi(ClyV+d4F6r-iHr5l0TUAy*D|mxQsou!rrY#g4)#&(kTdIHOBn|tbJ)CNhn1aKS z2q|hdLQ7VdEZ=1fanq0!r|OWA622b-vqn*}5WrKf5p$X!vBJExCv2aQ2b&tzw5sV> z;S|oC;s2qiG$0zOotg>QNbaS z0x^_#6lOgmDQwv~Nf99wE=A-s)sfk;Gd&Na(3EUOz{8TFp9zHl5pZnhO2(qb&K0B_ z{e+MDVdpBfohtx&Gw-x>)lY4Yn^}6DUwR{h*pS7ECTufeR0bBn&xu5!!pzvw>(nd? z=Je!U3VTCV%IfJJ&!9DQIqI*PgH&#`ArBK_W{ZUN45v;?|6#LRPtq&+ks4+VK!=1R z3_)SC)7Q9C%UePj$P#H^GflBkFD&13{}z^}H-oj@|Cz;1*bBo1#6U*86_A|IwjiVM*@%0z8W zv*WMWd(AXqWi1COfg_Ae7;oTk%sapPk4+&)Dl7SK z!A2Ig@Bo8}3Ya176JJeK(6gD3GY9$-0~Sil*HKzjU2VhiF6)MDsdq*-r4Y|=;0a;I zf_}dN`D+<=4D?jhSqON{)Ih+?Ou!SNXbN~i{r+m6b!+*p<#G)0rwW<}m%9AWp+UkM!T%0_2M~ZKd&UlWiP}u`D6h>}>X4y5jb3=%T zx0zr|C*b2p-aA>nanktssrQ=Cg_enrTx&MSGom-p_l}VO3H~AhDB1XEm0X7>S1OB_ zhNbD^bYe$>VWS+|lt4is^5wV@5*-{Wd16a?z0N$*Bl#wHz851>)1?Sn9<1)twDC3@Jym`o(0mrw~`jqfM@0 z%F44iN6Qj;D5(0@9PBr*!DqhC|Kfk{Q2>spp_uSAn6NNDSyqc5r)^c)UqZyGD6o$vR*JvAwrbCPbz8wMa0fyAkcWz*7RZ$}>GLdQofRY`DNU+YdZk*UZLEjCTZ}Iutr}22yn!y-UWcjS zY%)plB^$9FBx_r?MA50 z-&>i@=r3hC*KuXPP~d($Cd~n0zwp=^YpdINsmSl+%OW zU0JTU>56g;L7L>so2IDKyIlPIhW;prTqyjO4Nx~_DBem67*W@#f}CiS>hq-iYo~>a}Z3tw{s;JjUOHXf3mAh0Q`7ds){f~OVz-H6#EwU+^@s3Vao3CV% zcjfKnptR*?ixV@BOe8Yrh&`rVP@4}#tJok9QeH8nBcQlpnBkXUSMecCfJVNgD~XT$~n}J+skkR zFz_=pfGmFTw+c08huUqr8l!T+Wy_38Q?2gC|2Bzb*P(0@|{WebG>g%#21Zyirn@oGUxewgdxuLkj+>?)SoIYIGFZi z>MCmp4DL~vGqZ$v)a6Wy0Lx9{Fnd`hZ5Chn{(Idkhc}XT%|rWQ65Xy=CY>(+zOiUK zTD0G-FIXQizeyOEy2}&tooYa?5n7X_`^ianB=C(s2CaatU?#C}6gkN3l5DcSXOc0z zbBnt`fiu*Hv<+RAkW4_gRqm+H(Ya>MW|kM!Sq_IRcHz7+?Y77)71@IGLJ0g;^H#X3 zxOl62^jwNvRu_BhT#C)q#g3m#v7L3XC(otWuDaOmzuACq6KXH5p?36vGi15C&N6?d zT3%6SdFV`8uBo#eJyVt+uCp9pnkCNHwe^@5&z$M{I@5_InV9t+XwFZyS@I6Ne>!`W zn#Kxg4>#2P`0|p{0c$M3Va>jWmw+vH_0@GR?pvD4vG3-(sYlP0<(4|jN6(by);i1V zktMzHhGx?`&%u!Atd_@ZX+4OC&ZXSkpxk5UQttLaxy5rSw{KAH^XF3TjzPIop`6j6 zVIr}Akn7NIErD>H@4E)M7MAAnd6=FXh!&>+|TGv~T*kn7;mTt31hgAp#ADbFK=Jdd9#&%z+jZNIZ*bl%pZgFN@0 zDbLYCo}*{V^VlHI6CuxJI?RENDGPptvgP?kUfAu=yZbVaVhW41bE)}EHj~e&c%qHi zb^W#^N0Ax%Li59xnGT$wWH!#m@IE-Aqms4u@U?MB1_e%nI}95uo7k+S!XX(;+aVdr znJqhTf(@2-!Z`s7*&!KgP~Lg4Xb#D=?dod@q22KymW{M{>o$q!v?CKd>L)xN z25EvU;|V>$9;;L^m6Ady<*;w=BFZ;{U;e%p8z0!eI3(+*^)laHK)0S%vX@t>)YKtp zWqv$V(83NZXths=*6nCF0+&4EcVi4{*WkL{-Ov%&mxY&E2AlB0mwf=Q+qDxeHbCX; zIQ!=_B!KY&fQ1uwicMh7f+IR`S8}ZJUHMBUGt8RBKU@xP8Lj^9RiKX?lr=nn!svOc zz5;o)NK|UVBH4CMD4n9*WyO~-N9)7sFPqU{YPN9sDf7|6gD|T$g}?|MYe9-7wIfm0 zHOu3_*RkocR;x%Dm}V2G=oWxe@N%JZDf@>ymlt<#^JZzqup!M-zGrr90!C-ZY$qK+ zKe;KuKyC6Z6(@gROK$mT_cPskMYJzSv>pthsw6~O@O2T6WY`oVfivN<X zA003S4+Vz)G>8(X&w*aZQVTYQDK+ROBPmB^7%cOp^@WqDJ+NFPQl}hCFo?%)ExX(% z+U)l?{{psuvR%lm8r!piqmd&csyVBEb|JjB##56hd#5Ld{R7TQg z0utiFr-g6hCRIpk8P^cz*;^EOvaeMcN}I7@a@4wedO09UZml(tU_#(-=J0m;?sjyn zVwwzX_&UfQ7Z^?}Bb%ZeC8)=|AF^tP{4BzzmxG~Hk2&9KFfzb+A6rH=LLa`3u#_%C z2hV41dy2r`72UGLvoSVcB9y zr;{)H!Cxk0NydByN|}wWT56c78b`LvQ2f}N!UTM~7v=h=U6kvD;z>8@{&q%a zZzeG6)%MF!IJ+EdWiGeCr!r##b{@raEQ@5rl@qe}eq_gsIEw{0_n^*tb?+Abm6?#* zFr;#J+Ankn7Bhbt&AEE1)6f}99VY}oO0RD8oR!0_UZl`|S=`;un!Y>Fcd@V*HgRBt-lk-I+g5T{*y?Rn7jN0xx65jX(uy4^DGqP#C-NY(=ium;$ftIt4DrvE zL$K`GtMs#;U~IYg;q8n=Mj$p`^6Js2cr&ZiEVkKE#uq9h^02)tesx<v)f&uqU#JM9*oEKWFSL<);!NTmacq+E}(!KE*zlhf5??Gh}%mMH&yH~!3JqW*C zUgrLB0^yCS3ZqdvCJ8RFwSjoa!YYZDk!aj{>g0k7`;6672_mTgM%uZ4+A?`wj16iO zugD65RzE@7S9OMIK3TxQ;K&v_NIwKLb;TML<50NkfQLRVK`;(r$-n|6e7^uW9d9z> zz-snS+QI<@TA=_)N8eWfM^?AM$&Ucek7k4)&6(`PHsD-_=Dn(Wkvr4y1BGf-$31E1 zPhZ_f5>PA$R7vJlGWZ_DV@DqZmshzu5eMe5GdwcSX2YX8Wk_w%0dWCmzA4$OiRn~# zJSaC%!9CVggXo?OKsZ1wELWf9V}sf62?7FX*yHJ{Nn{Qk9qd$xADratF~;@YE$E@3 z6L;R2FlQa`8IvzIt79+90e!g4*bI4z|3SI=ZN`h#H;XyZs_r;!w^#W*W`%W9O3Pp_ zmv#~sIOrrnIu&84Q$2cEy(4t~d$)wXq0{{I2GqEfukriqTGjExlhqv$OcGj=E3=^4 ztST-fhitI4uACC)a32tP4mR?zs-Wwu>N-yH{UN?*UPr6?GK`tp22*Bk*-Q_h0H+KL z;t~Ytv7GcykU?nG#ay1`YYn31zAMCV1R)j8G0i9Kz9GW`Z?KU?M3Xpqaw-Y9CFWGG zHe+N$sUbv)vYRndpOC~SAc_iiCvX~lJY1jPea zaZ-|kU#ilGO*T`#z2>gggM=!Ms|uR8ajIML9GbJ+Wco>U9`z+xv5iV7((0tO#yuKZ zd!e_w$RZU|8#aqQ*b6Y&UaYbw@YSBMBd9xRV+Qm}q5AC-CpRn!-jUyF@HY#b!XaaB z>X66stGPFr#%HccFfMH1@;f>JyiU2pw&;@!(bI!5{#kh6Ye_}R9i-C?O4aEa%AEsT~q4eAkhjY7D7yFnjV9Hu$ z%zk?F?u$~1QH*Bx5noDxi%aCSlHtjGFmxlUJjrccvewk5I@L5bCDGV~ZQQSJQ$@PM zjhN>uKBCbZS9O$hW8=$xKh%~?;sLt8eRM_I z6>v|=?G40DG~y0@we9*;LbxgXnXVRMBNHK9cd3xU-f&qCEoaptAN>XA5=C6V#o{wu zRlwaR_O;p%xH4NPN1!L3m!${D4OQxEkkCc>YLYsK6)m4j>Q;>;M=y({j@yV<7P`|_ zx|&*emkZ3OToR5wFbrJB4#2e5h|jr+8nCKfb21P zB&2^-J+moTz&lTFVY_SKK+)^%+qb5morSJk21CdN$JCI>RR##Y*hT7V0&H?W&FvX#Av+z54S%3ef&0BrkQ39cn7hG z$+?WVqskyQnd%$;P@wqcbQT@nOPLwR)&7vPhT6cn4nSoyLrU^YU^m$Yx;; z3=as=-E@sGGCrPpU{vg1PaoK;2YY3X6*tAS3$#gW+1fzNo zhyjL}^*Xh?4H&syoeJJaXiVft-WkY>5F)W^UpTB~T>a#aznNwZ8qT|{$PSq}OnV?J zLg=($WVv%LS-7ldy(}p%SuqQ;BI1w>Vn)vtWlcQBuB0^&J8-NaD_RdEA4^uuf~;sS zEm)z;iYc-pyv1cjZjZOtf~**Niyo?0J3;)5`-tWyNY?`O6fR9eSGDkrB)Okh;9EkR zzzRI$SV$ncQI1g+~|rBB+LhLMXh#8o@V0SR>0NimH32*_onw zvAw)Xgmppeb4CQ-s&^;N0q|mH$`+X@p{EjhG$IpHoRda5Hc?D~{c`lBucw70s*9K# z5523SHZiXn7$=dwjq!-ovmJFz@p!x?bZQj+z}#wK8NykC8gaGwwvVSxv z8z30PnrDRI|7nNRM8|9ID+S|-ThEYmI;llcmBlY_VFtl{qt{!}+l=>T)!)5uviKEK zZbZDvp^FHU?p0njm8CwFb!DknmZ>rmqwC7jx-y&eP+9jsBDu<{)Y5*K#so>GmpEm= zflx8RDWaKHLbLQLI(GTK#lOhtgBbhmXe4@`|+y7^h;Qn@6+<*i)6T0_)$oY zEvuTD8`TWT2yLj@Cr&jZn5q@6Z?72ot6q~0FYCT51*cu=PPnY}C>PZ~ z0leNMzdjy{UsOUTqwVRGT8EF& z;lGL<=Alucp6Ku+XReS>OY<+BIX|D4=703e`K`nEr8vsdOQw+v!AL;)IYmd}qq6@J z^MhL7xaIsx{nf8}rC+tCUmCDqO^2QZX8j6$`UPV4dp!JpPK}|XRDVt4b>llooggK( zrFuf&)ac_6tiC+OY0`S6H^qJ1iu<>9{{u)9Iz|G%WT^7|@XKj*d%ht2Vx+KV!u{AU ze(Uzj836l*RoE{ontr?2emiRyT{}+=JohTXu7GK;c$AhCfLaaRrKeogIHA>BL zozXhn#Kr`_I%38@4Cuivc#*RVrNy%S&T9GHEfg~K({v6S^1wtLYmD7Us1szfl{Q>PoOLV%4NU^qMqG=P#K58IAPF*7uR6an*kUVEx^QS#q!h1Q!{q} zMjzGLwK@P}sQffAE+|}j_XaqRlU!8s(}5W9QTSj-t|8mS-?~Xw>b?eL1i$!|E70Qa zxI$93iG!Fb79`32@)Z(TY{LHiX$=LOAF4Z;dnsZb%s;yY#sBq9l%)NqzOSzGWv5k{ z?)=l|ZF$@8p8CvFuN2l+Z&;9h^g)jYKra+56hW&@w+YxFb;ifKw*Mo`_Qr@wW|pYl z9LiG_M8pc)8Cn^%bf#8L=ryJV5HB9;gem-V87CgVbOb?_CG`e5`#g-23?%2t58rnUv)R~7CD`sf3_cVs~ z>(A2A8eOoVeJs!Nz4@)6@N6_EaT4kQurJ-2gg?a{mvI=X`iNc@Z&Ml=5yK@Hd}Z=W zirX&J;pldq9@Qc0@V;Q12dIaqb=&e=cpTZ5znY)Uw)_Tun22k^C=kPT$GBA0Xry5u zqEl!lZ8YO)Z^qNH8GoXgYrL7Oy_sF!40vtLpdg>2m8{W0>ahy^Vj3uK_m8?AWSTk&*k#h++}AQ%D}>YPBPWdq0ti%<*&Q>Yrtm`D+(_hxP- zoI&ozNSI!nb`RZAwPjPLmOUq;8}O$#H+vtyYH@6V=~a z%x|(yRv(DjR#aR0PIb@+zO3YCdW9rp)}gr2d8YI4jG#eGh>fUx#^%O^GMkcdVaukZ z3!?Bdp(FQHj{%II6_~?ROV0{$zm#gF37ziB%5m{cA`D0YH--24{IJ=lc_7^91^f^f ztK>(A-}&K?u3#y5Nq%fMg_t_;m*x$Db8kd2R6p2!RGGbX0sl(>=mmi`}+mhL0a#4W9m) zKANpa$dH@vRed6oyi0*XIYFl)uk1qgYiI)WA3Ezb(Vq ze-RjYcGoOf>4?vCeWhe}HVAh2$4f;|B`e7Qdy~la4{Z_K%PWjtm2`WY3SK3^S+?TQ zZL~=@pkUq&ZaW?o9sZj-QX7<5p%y&cMfEek7Z#;j_p{}18@vkfajN&kSJ@2t+}gl5 zW+(V`X2pO0PDYSa9|<`HIhhNj1c9EdYAd1Q^BJZjrH*z#Q!g2(%+uUyvqxJp8j)d9 zkIPd_u%0pbiyxUx)jw^jWCC?sDsUG-?ypK>4bXUf4upi3y)-}&t+~&0&vWRxz0VUxT5jU)C6sb<&h;F&THC0o z(fSdP>L$@PeiA82Y*Ds~Htj(hEgsT>x>anWwfDr9YEUHi_a9@f^{)5LP7I1|@4XxH zt~uwLYtAvp9CM5@#~gFcm0U^`WgUxV=QNpP$iVmjaoCM~nU+WXdKq1^MoloO{I!e0 zQm84;PE!q|)$2g${B?_=A!EuO)HL_QX>GL;SEFb1TLB#9`uEO6#oEm-Z{Gz8!`6c5 z1D{@me{wk&tC>mu;A-6Z%h}!5N(m`em6U?j07k{i7emt?h`s0D`ggCGJMg=Yz2gaz z(IwPBNKq}1AwEkEnOx1Iy*`53De)p=xBe+5BUM%41|HRUqyLU@bzbQU30KFJUed_( zzc=!{xRK{q8sYz=k>|GvSLOc~!n^E@QO@#=iTLrN7o@g4=|_xSknYl(eIeVdBKt#R zuOfa(DF=uu5_Uqv3}p`ht0)o@Fd6_1cI3r7)=C5W2YXG#%BOWkkZK)XukeN7t!zF$34OQ_MtU(3dv@t3pw^xd$?>tw; zw&PJTI9TAZb&OAd;20IC79s>xiXesmmoJHp6!;viC_4)Rg8riXy-Rf25kz`3^q7A! z-^O$&%U;l&=26P7_nfX(OA9zTiw@+#Zzuh}UoIgk23CKnUN|JpdX6|BB&gu|MgZr(|q(qumXa((FLu))`{!}2d zvikhI^1!unm>T2z>~y1Fs}{jY>>NO;+9a>NK-#u|y6rrDqM*K8P6daN(H}a`lj8h+ z=h4BI^Y_o&-EZeloI4dIaNMH(5`km&No&vHR|&w;gqEEjA6nkZAG|R8+hCU231K0r zaHBTgk#>1tw;#~UP-vHPAu^-L{t($tcs_)$C44A^uOWOegf|mD8p2l+J`%#42p#f@?jvU8gM~f6q?sj71A^Lhve2v-24lMrZq}Qx+m0g(^*_PC)@} zOr892%!bJ;Bdb<^i0wn9?lfPYIQv|dGVk>xp69z;`(t!R$BrlTK{u}c8Y|~JP}ZQmt0d{jGr6*d@&B; z>1R1CX9e$UWvW-s-n`7es0-=lueiv|AG}-<66QX$4A|x;`@MXxWom6{X6l?9Jx1SY z{ThH$SPF7s%)fxJLF$~2DIy34_lF=TgjaHS(b?%L4oT)tas>;HXbwTn{W@U5YMji4 z7YvmBA-IM@Lh#B9(!C+L>4J1N1UJIC36}Gmk2EGv2!0Qw7PjAA9wyzGLjGPgZ&#uW z>IK7Z{^~{|`c5B&*ASk0mxr$q+abHeh{A=PgG9KC z+9JwH*N&4+kK`X*>m$I??tL^*`j^fl1LVXnuc`^dbpEhTq#ipvJ(8-lj)I3R$PLbC zr}G4zZj7)hqJgj~qJglSXez?0=mx^7=mx?P-H0SbSZWqQ5mpF_u;OB&PTdE@O!LI9 zAP%@T;9HU+q8-$mw+lZ!-=?QN@I3rhlvp)4+OTc?$A9^?7k=hqRL|tnmE?T8QSsZr z(7TLV*npb?CFfGEjlqg*uO;{N)|#V}MHo5+hOYGBL4un+c!1zW@>fiLjS;nY?wA&I z-rpDl#;I&jh-q2x_+Vhs8zA0hQ*`=2u=yTYDsjO*@)ZcH1|Spz5SEj$R(9-HH|q9=`Ql?`<@Uk@eG7#(6{vcz6=B}k)e41c({o{|6KS=pOfGDQE^ zWZDuQKg-PQ@_UqUKzi%sDfyTXnP^5T#k1f=rPxZ!SBkCXu2uM1jXdWNuDX zI$B$uz#kLm+}-n&4YZF*&NKoQF9n;Y{EGvI?pJ|@?)h9!)zqKvAleAn)x`i;60YtA z*whGbZsfU!aHXYhCtNA%GiraOq{CG?{4yx%I~A#v^j+N4VdkAaIa#9U{keR! zQTX-L`n%_P_(CRYo>43K=saIy78+$=O;E3uG3qnSlW7?zFLkj?=rTFs$?3zF1k!aP2EqNB|GYoPcM=|>h{kpD))WHcK$(EO8kl^ zK4OWQQK-#4&B^p%HI(jV+yiR{v^l&UlT)@(4w7kyaIA*3z%2tQoi0+dYG8ZT0)l(s zy4eWbuVrWMNozMub0nKOmk2$qi27?SW7QGQ!N}E-M9hC7Afv-@G7%Og5eyUbZ>sAx zjbw(boiFCB4N%kCW@1Hd&}`X4m#mARn}SuRjTdT26`Y=m^kP6Mgz#cGDRhn9V5QLJ z3sQ!aLf0DoDzyCqmQ5^8%JPhen^fu08c+)FB&_kI@Gim#Lc*&VJQvykQg|<64IYK} z5!L`wct2qcB8BG&&skWGsps=Adxs`{OkrBn%z>FP2l^&t^zGzTQy1jnd8ViqzzN|uex8BDB~m`V6_k9nyFfR!J4S=YK7sYesq>bAoFAY&R5G(aUqSDh_Y%qQV<@< zpn-8kHk*MA8sw3EW*~#GYd~qhq|Ofz9JyB}-9fIOu!a%#Y=&}qYtg=*ndGvE;%FfR zKvNZC`AxS_R_JJQ;xtcKCnX5W@kx2086EDvoI5SfbWzODoLE6+og zwd;5t(2ErG2wighW($Yr*rJZ9UOLSi?S2eYVx|L3LpX=DguU&Ub$KAzR;Qc#F6pb%*|IAUt#vG$|Os+EBh0(Y;x0aZi8%N*l4L!Q(|QuLp7r??C8Wqt}2ZAfC?*R zKw(OvLU`Q^)Qm1}WrW2B?dig}VFV4WkuU$=zhUg)jiWLfDMd(8S)PX}J0F-y#!}kB zLZVGMJqET`WU{+l5ZJz#GLlPU5E8PJ2$k^WD+IMbmqvh02bT#}rUWB)={s)H#*Q1f zwvJ%E1Hw!i*K;hL%Zp42sjuK!osrn4RiQ)6 zv~g2BOPp8zkMAi*6LDFmj~>-ri*e=i^Q|E&3V4Ixivi(4{j?*?R+$z|mzH?Drc~@I z84xq{oRMkWD9PR{klkZSHaDEOZaZCMj5QQYh{F{eCTU7a;`$QvCP>B_qdRG0d}BGo zRMrVQ#8jJW2yvJc4cKwUA6F8Ui#3DSaUCg@9BWJ*=O& z_Q%ePK^)d{!T2iQ*|GqXh`vd!$z&_)CM`eJw$RH9R2Nt$SXe&yT{hWO^$ft3#K(-3>wNE+d+`08Dz)4@L>RQ<<2>%2~F)Klq zT}{cDgy>4^&!-r8ie&_4*@H2~V~p1c+-5(46$p!V#M)EVBpX3=*^o9S?e;0F;W%yV zAL2N9xpN3q2;?4>U#lva&Gh`jCxsB(yx4B5F{_zE)uS$?Fp-i95@JlHq``#Pu0ie> z`v`uDhK0;OGjxO8AT_~*_o3JsAQx(CCm5L6N;&Wu9T~Xvx4IOQr7(mQE^p_#G(8ol zsys4QRkSN3R%2qJIb_dPz7(QrIJ{|L*s!x}UFHz9sz2wxmsP1J8xd8WCt35`J6u+1 z>wL2;i=`}!O$%6rTaGJ3zUSFuA*nF5${$6MmN#LEJ+O2IoJJFT-!lxeWIKg&n?}&_ zRei!R?GP?H+$43G?Ijgwl|W8WMNzfhbyj}Qv!zbTj?mZ9z@jj|6+N`zREY49btgBc z=_9CaWB##c>p2_u$7+HTn^%8XrZIAaFUhwz)(8Hztr&c=j>#pcj%ofr$f+58Gd9PQu`SU&@DEG-?D zejn39xAc4o*Lyr0yV^2Cwsn|twvNv!TxgAzkMj>d+h$hu4!Y^`^CL~PKtB7_w1B0k zLOr{QOY)=_dWA*B1cmEa1>)D(AI|lx5Gw5)O|%TMUcPe~-Kj>YSFJ_#xRHmfL*Fpb z_(hj)!Q%vtXLw{#jbKWEnj^JF*+bNLv-< z=SNJ28H*7a@mDBk&BybVu|Ynxh6Qm4UbW*>(^mzngXpS=WdV4~xUgp3dtpQu#qrCX z+fOAzqtKVslzw#p02cM-vy^RP{`fQcD0Z{-LXv%MNjib8-e%JCcog(Bwn4%_rA+S3 z{vG{CzIa6%E)utp>r8p~h(yll#JtEfM2nlJZJ@WMo2|MVIR;lQ|a zG2TSN>4pT#)3Ov2`UOP~sIbNN%7b^YuLv7Jx&NkR5N&^%I9spaQII%SC$9CxHB8ON z@!91aOY1!ioP#VYbL_mKdZk;eRDdOGt5syg?I1npLS~vWD{5a&2hE! zK!x+u>TIT-QDKgz8m=u%0tjAOrT^L9<;I|R%8H->mr#0c2 zo#N=y;62C3xOfzknz;&@r|aXjckfn~0T}P0qKxu1V|i$eU9&~>MOLs#V75q&^0@7Q znU=AwF0(GC8FCskEk4<5mv5vmYN0w5wr^zuJlbRhOGv^=B>A!D786j1&G9uakRK4q zpIcu{n6?kDVammX&Wgt!;yKjcZK#IbTD&eNy%Mi^Bm6AF0}(v<76HkK(XlIjYbN-z zW(`b9vhm=KzorYVLTzCkow-&`Q$BQ< zfa>H!O%pf;OP*R|b|(%C#sx?2+1%mO;2G3A3{L}2`SAln_qe&kQ&z+1+@a`dMsMpn zPgx!Equd}xQR1#42LkV;bsLM*C5xZ38Vh6z_Wru-f_%gDAR};b#%*R0hNUo^#1PP~ zc!v!nf7}lI>@wB{BR)U+-2S4K9MPC-VCThBhH=D+e@kI4c2Gp7ttNr5u3#(%#yS{w zxeq%Io2rIgPedbFO+))UjoCv;Vo;12=F;T+J-5pVrMbWq@L!jTv;n5QTNe zpz+RgO~HVZCJ1p#3`=jA%MSX?2h|TtIrp$5cF^clNd9+t_U7MTPAX0r^1j3|WcPU% zmD3_zPKP1!S!~I!cxtNTx-&Y3g+RKO!AF|PsB|WvDmDMGoO`@#IRJ6MpXr~!iqTJ? zi`L7Ted*<00vD4EUq?YugoB(c^QWTA<&o`Pt>-b# ztP3yv!&$$>y}Vs#K&yh+5%BDpqMH#_@uk_*6c#*s%%xlTnTsyJE_=GAyF9y;(O{x& ze|qV*>>O}S=5gqCX8*AjTVZ*F*DJ*UD>-=S%IuA)&w!6bF9f-Kw=Tj>TxPlQ*y4~> zUMx_qTNlwwq<3y8=M+Pk@O;P8(Tq&v$vAOCId2)O&+2l@G<;cCTh~Ef@#)eV$|DPY zT9-U1t5uct%uz6y2vgSb8_J^#mgQ9eWmT3JPTf^PT(+%n)@QIaQC9Ur-k4E&)(z$3 z3zqB^H%hKsRTbb>0a}$1mmTIxS@zEHxGS6Tnrbob;G34&w2ynW%LhVOo~MUGxFwZG zRI3pUS453)%PgyPC|(v94F&KWIyIlpjGl%1fl z4a)AS>_!FMZP!teRg$ZcR{4s3jD%y08#Sg@BlWeszN6H4WTE=3EzSBYA?-66360ZK zyeAO4*1Ts9!IA>Y#P!z!j5gtc#F-Y59J^S@xrN5J+)mfl?Sp5+0uWD{6rBd&k=rge zO|(lk5&!NwY4CPCoMBySrxeoRAE{>o5X1gOj8_60_{Z*@i>}ZecBu~Ov5p*caZk*g zkJZJj2PDiUs0G_YvXb;ZOR(G2YhAl_lzvI{uB}|2p%ZwX$Nn72}3-a1&A+p|Rb(iKDB+6bXTT6XMar~HpP%M-NfzGbQ!OfRhSm(%*Lgj*Lc zadYDKe)o=6*Lwjtj?>}m;~c^xw0wQM6ta;swb?Q(P=K0Vp69|c0vDQwmcTKKgS zqfw#N&vJxEiD+dX?#b>S4RJ7*tc`=gt1F%*S8P_nkgXcIql0Mj2A zz;ghQEgL66+__PD3QcHNjJ4600b_-onntl;Yb5PCSr`!DAJ)mOpKfbvp|>ERaJbSb zwAWX zx!i?3j+v^rAeK5{DZB_HKKp1|?|*NV19hs^P^UOQE@g^|Xst{+JNbn; zVpPl=N!_?)xL~Kg>Cte=piQ;QV|R0N5!%Kt#0jd@%u(fcvy(Qdj$dDNg2t|I?T!Wd z+bcDeTk|l#OisdE*DM3nxe@Oqk}8I5*L4p|cBEMlg~L+a1fxWul82;vkdC}-N_C9T zvE7m2!d-`jUB5*rNCqfa6axq^Ojz6Dk#*w%+4I*3u&ZCFD+qO+Xlz0qrk{O2@k5e0 zBT24lTEc3%mj)FCjrT@A1eUJE9b3E#WLBsGEkPm*u20g9&-Vk*RYao;Wtk<997Pa( z9FxRfcPf>doJgoIkX<5^F5>aSW1PVtWgJbCAN(6a4b6MD6+qk;++jk-E4+@!CoCLQS?;TuPfZAp}2 z1X{HMq$q&FPWIBu{eX+KIMCVfV}t=^8s`Nv1*F%3H-)=KsMm2gpT%cp9)ISRa{PvJ zhMkk^V`$ZUycl@oRy@w$oYosPe+om+^gJ`;G3;DNCNR*E=}Qo+A?TfZ+z4bTq?#~G z!9$XVaTaUsEGk>BD@;jM9Uf&;{Is`|+EH(mx1MKH-N>i~qskHZ&;q4t%*OP9DN&6v zJF314z^3{tB?Rm4CHey#k%4O77Q;snW zxiQv{$ia~Noptcr#*j0)=`$wNgTs)sV{J8$@*yYwMH^s@#TaJmks;UiA!px&A=icM z#D2y&$&n%JE2=gxv$%}qV7i^%+>U#}Z7frQ#=jS#R%k@CFz8He46X_kb~Hy=e+bye zpsbs4$r(%*vE6su}|6BWxyLQRxl=KzKSW;o!aM&I@pV0% zgz^jdoY`&*5#_NTvYG<3VDK2#$6Ke!s*WB`iZa$vc!P)lu8yEqrmm)FW1?~0 z@Qzfvj3k?tX-+OFb=@|ODB(`XZJ2hu<*|FyE2eUNty0oyq0t#A!;b}rv9hO2h#v25 zBe?wf>y!ZZdLp&>4cA>)ELth>E2|$txB3|jnmvvQq6tAHTb;q=$v*#O(aONo>gqV- zopXK`i%)QL2W5&ZrpDAT*2}zImlu=QiIcGOT4d$;mi*Q0`V$2^l<`R>I}XZn${mL~ zr^f9Hk%BFJ%7`f!hebZ4ePuuEBCs*W>yf>>o9r{j9^bw*WV2BI`VCj4%^ zZc33M$;S~1TufAQ{6Z#0fD=TZKF)8+P(pykmSH&rKpn|tRVq3Nb~FOIt&OBCY-@I> z(Al-xOh|t+)U7|e-k+hATEO#dDH%IpFV=yb>{CHg!dl-k&x4a_M0=OW{2TVzivAM><_#6fxz-v?9Bqyi;Nid)Wu8$Q}#=eq24 z4X&Yl`RmtZ-%cZ^!yoq-T?>cYV{sIp^_2Z3X{+5itJ@nJ&n6a~TB(zZb22VDYw3xy zFFWn@p;u=-Eku_OB`fmu<5E_Pq^ul?pZSclAQaY4ex83jKc~=}?DzNVcbEOX*?!+@ zzi+YMx7qJk?e{(Q`$7ACyZyeqOEGup=kw$I>{aL^`uWBfKYyd2Z|Uc6^>bK1-_+07 z_46&Jl;zhHx(icE`2|HE8O!c%aqa4e@D6_Y5{gB70+HTvkuF_6X=qbTdCoY&va1mm z<^^WCx5;K=A{+>)0HrYA*zZVmp}-X#t?i^Mv28NGnFp!-Db~X^ZP308Mq}z`vq^#RbOA*{xwK3o8he zTt51yWz3@K*?1}j0Go?xrEw-kpn)j5v;69x`VCg)9Jg;sNNp=6&a_%L^F)<$><7AA zM#ZR*cw0X$rw5q@*Rl9AerMw=wrOS%k*f)sl%kLGFaKx z1FFPit%@1gc0JjqCY5bh4e%bIPM?zGd{iWwS4KrFsrI#4E2T$1&#_Rde4+oS<~3Zn z`;sgh|LOd35)UjBj!7>?jQ&d(jq`*PUABmmDL%W1E;{l>`~55X{WJUhlKpUSYlB=&}wp^naPefWkLUD1ZQPRcPR2q&-s!f{3~(*=%PEA%qP z!T#Hp=x!d#T#b@_M9m`VTnAcD;Y7l$}HuEam4^h)DLm zT|h4neGku9X0|uMR;nIiLH_;~QVq9GJ`qRyi}jaUkO+HB_)Gr(@W&b${V~-7`Pj)& z4#6w2pEILbdE}oKm2X&De(<}C$`5^4PKk_0AB9}RXbUcWrvQc}28URsx|m-5&2^EK z?zEsAkqfQ-)^&YZpP(e$`%9NN74^1EMOC+)>Qj2!*2$+s`JJ(eMU#t{ERW)Lw>O^U zOA+>GJdNAtIes0OAO9{BHWrF(9i^Q8-lFpU?=@2%CFLzgn<@K_E-L4TQf_)|QF+f} z%{+%mdCT{kDffJTQF&-6q52fsQ6?tA2@?_bUQ;ZBoH%vy;OPKpJg$YAfPNrHv zEHbQ5#)?Y2qzs|K^tL3;m{T!fV_a-d-5+1J68huV$jets_Ic3HJA)m~nLkq_AdE#6}}EYS`R@A6_ZyG0gEGoL zyP}^?cDs>Fy{w%7(2}XR9K$Tv;ar#76?r-lCbu(pDAFg=%F5Zh6yXdzT2=1*h$0h2 z;C6F&EAnrM&`9YTA}MjbKRV60-hHo{3|tR<0I_pD{0xO;(-iFi*V7>iu)I=I4;F}s zf#qS%63dH0q)fGRRkKm#v@MOHHGKTrzdgwp*rqwDWe|LH5Zw3bI>WaI!4Le7QnTTc zHgA2~&`Fy_vzLHvJGYWe?RW8ssGoxmO^m7uJ40Ce|EqMyNS$5~rqQc-tQk7;%yvoX zOpEOQG7l|Ue~9BT2i3^!FW1qRs7_&rlZBSc_sv3ueC!FWN>*uyuGaylr=pp#?^P}Z zRHBs#&@FZ$&qe~5!{b+FKJk?idsXt%B^Z*P4JXQQ%?p;^_nl*-r8qs#5q!9vr@Kr+&sN5&slz&Ol>^V>J z18oQYV(f--9I(_xT<1PA$vEL0V%*}{1u=SdH?bSpMqK{2y=RZ-NC`>4O^72pUp39K~t{mN6{XawHULOu2^P zwfk!rlnwRMtu#^gAJnU{4Sy(;A~j3eb%v&>e0_GZUl+)DaE_n`e^Nej^GP!R27uXd zw*jpx*ow_wE!33<{ERCG`&3MaEi|w-;D5Y9ULN`A2=mYpHgzR)v^MK-P0r0{WdGS` z54KJaD}}>VQ`qviI->Z)XXIyf|oEP>Ld^t zk%erlrG<p_&ni2hqF%BZ?c02vwB+GPy^uI3t241BSSvHn3FSi<=lqvscA* z`%jk}S6~b}dZeSWw;Ht^`l(XO-aq*f)MC=(04Nt;4~lxEFIboJ6j?Wqj3%1>tn=wW zR-Qph4J4I+WF6Fn)g;OOT&spkleT4&OO@yby^#aR?iUy4(;vy~n`Ed18PX;h8Zl8! zQ_>B~w*(F&FcESGWNY@IU+8Ba9#6@Ft2z$K!}o(ArQ~1!IlE-lG8{zH0CN_8fbawE z6%ErIP#sGuH8~2e6;qhwyZVI@$N>pfbq1g4!F-w%U^+~ujZnz1(PVDLKc&&sL~dLL z3L1bvI+_ehI@g{D;|kC8^#S_YG3_01Mo-^JOsm09b$l%*$)K3TCHAU53f|dp65+st z>Kf3i!)jEKq)XKU;6ryHS`S#*djPFDyWC}ZH3W?IObM_(4g>w@M{5T!c&bX0+MpZ0 z^1~oxaR^Z8kS^+wFnZIwH4KaPvz^J%6Ob%oUYi!-q z`PnAoBs(RkjxivAbLcGVU!^T5W?s6L{oZv{fWpGvfcI1ij2F3(rlHf(1n%#Atf@gH zwl0`0Q!iP=e5~iMT#`M9_Ik|Uuzh4Ob}%Q4=?s1I-aH!@j&9DSnbD#U6!Ln+{%!I| zlKmn`N~VG1#uuRSQ{^KGIttZqyWBq! z4rK(~lgtLyn)^VBy8a%X<>6P*$tGgGU{t?h z!(s^Y4>_S@+=y7Cv_ap_Wm7@gal{zJoWXl8FoB!`(O1(t>0l{E$`g&UkSh!2I%8C!_J6qwZ5gKD|Au`zngB!-FL^Xsf}E{j4wh6*mKP)!&qh#1-rKQ$U39w_)` zKqqNym;U5s zND(<_NTeBA&p{{PrQCO~G^39{hcPK8iH4!eNDvmT^xbEpw47u0&4^9Rsv>z9wGCt= zd|`pR*jd=NtQdz`T8pz6SuZ-L3J3r3QG@lsS^sgpmrZ$icA08oNfZu7wd&_y)GCj; zx{0S?#tKd8+YNGYdDC3O5+yAWHyXAI(T$c-6=*Ee|h>{F=}2KA}ElV zNxYQikf&vNTEPzWtxAgweXVot)XE&IlynX5lA6_6qBxVsB`h$1A2+^0uod>&`Tdf@ z(4ls@iJ$s#zsFUX=67zrGV60hZh0_O;D0lRJ;@yaFu$2=606H(ibN~x@EWz#sIhJQ zx_T-8nEkN^)#^FTnO6t$rhMi6jiOI|5X6M zi_63ufu*RlWZW^dTfw3xFI5XCpt^vgKdy&wNiA+|Tv=%moN~^W3Kdh zV*a7$K>P{+xfujv@vjHjz&(8HuwB&)T|+jZ8&+8-@QauaqrkjaP^tt#Ra6_A6e1~t zY@x7HgV0(LO2MTj`$|%4#4RQWt)PgiW1#H$)u!CA-`~_p7H)zhP0eDLN@eaeG+{GM zhjo-eSljwhnY%+7O=V7HPONX8fkNmCt!<=1WmepASEC_%MoMSVH@JA5^r4_+I(vZWVSic zKapFK>EGJvReftx*izH%P7#W8q+tY~7{w7Quv`-1z6W*0z048nMl8TJr_J4g#}b9a zSm)Kom5Rg!W|ayNnD48$-I9-S#@eSid`SgT$=fdT4eBV{Sj$xANtCGme_(a+<&8xb zZkQa0ZH?}_uTL;vW$3w^L6WDvMfF9R*;8<0f)$6Ba0O0dji zle`Ce3`E0nwvyf}dv;GqwT3c3=QGyf>E;K1e`vZ%bY!~e8LXFKOAoCsgZbwf5SrIO zU(1|I`^!*AWDAi4>Ny3n;cImW|33>G$P@;Ek zfyG<$_hCRt;Lcq0i;@)kNnLW#!zn{Qe1MlDqo?{-a*-l5wHjC}w98NZ{edN+uX<{n z1V326bek`ag<^Np8a zv)sK-n$2&2#@8vdcD3_eqy0#VuU!>t%OBKc1G|vY3Y6-S??EN1N{X{zubF_XE}pBZ zZr`5<3&^9hW&xpCW9PXl2<)8WX95c^0PsM~&cT1f0%A95QpyPBsa#ua8-e(m8YmIL z2x0&8An;c&yZkE64&$T?yB#to{mxqR8 zZ@E9b+h9~%-g@6;`BDCTe0BNh`)nZ4ljXNU>fGvb=cg@|7M6E?+Vg&Lb@`yBW->}s zMV>$L+)H#3bQO`W&i=S=k;%sVS$chLgg1<3JU7BAkTIY%oMD)oDFl0A`m9jfeFX`% z+&hp^H@Fev8nrI6-LPB>x-LT5))!;zqA?GRuZzY#l&zD0m)=gSi$JJCi`GSpJT$p3 zlCJ@2i;b;0H-9+sV`?gQf^zQNX8@T_n&1S3cb}0zya7&C-t+D=%DK;qE-i6>gP7=Y z^fxJIbcP;zGM!A@W?p57;p*B5vB32a+-s^ugcD*SS@RNQC39MSB63dJ)|o$PdBIlG za0oHqWz!CubUl@gT?ejknd20w)}DU&T6vQ?fdR*G?L3;v6gCqsb`T@opV5g%vY7}(D= z*8-em`jnxsAZMarn!VVyDsDjD8G&4&61RkK4}2`wyr% zVBi7k5-wxF9h^?E%dojk5p5-M|UmTOM4zwfwprJ3w2QdVCV!RMU6M|B<{*CeM9;bIw*{uNh5Y z8w%g>d-+NlTfP!=mhKIXw{b?EhgV)#UQB~Hw(W|QW%v5>7%&0VF^pbY%B9y|mwn%< zlC*z#-R)mNQoQ~>Uu$V3#^vGeZ zDq%QcEce8&v?9ULJ(!!(ON%kY#zOmX*ae|Gw_jJNb7aqhq|Wrma(|rW4;hGgH{lN) zz^##-c&7Dhe((j7#y5gR+X~Qnv-zPR%Gg zlGc0ZyJ78pqzhRC30;W7qdw_b7lzA_t1gU6bzz_LKGliR8IozTSJ*Du4@VPv)^9pZ zVC+)fdZ%sUWKs6m6Zan5)}LJ2&qD8@?^L}*l66H?*1eOJ-aF)~dM69L!_jl>MyFm* zou+v0qtjue?5P6_HeRUknZLCS!snn8W`5>V&c=~4-NxXP>Y?LLtrP6gvj!zM^v5+; zwz->qhY|G~Y!2*{553b||8)>l9cofl(2OEg zN!!DH%Tr0Xop~w^cQ(tu`dslbe7q0yiG^o=h`u~O6g#Bi=D(Y9IPQh=^n>gz(&u{i z19i@snOo-gbgr2&?Jk~PaL9nYG}J@_8TJw{T7GM3!Tr6v6z3+=ojl83;o9gK_2AAB zIjT21Lga`dl^4WLWvkpExb~R>k|B#N3Gn!a3735Xp<%PKHq)YAJnPfP_bO5yIlfPk z>ip&Xs0_nKr9-F{4Ic)g=0?oe2p*4~LDD8d@;$Vg(kf+awiP^p;6?jc8>MveoSp6i zF{)PKy@dCot7?TQ$vENZMkiCVU|@<8V-NIbyicW}9LYlj%Z=!D+H3v3~c5OI-iQV0W6kzCALT|=L-jrnIE2M04 zscfZ4@k?1sLmv6J#aN7Mykj}H z5yP>s5yQo=5j#Z8ktCQO9*WHm#U2@o9Uh8(+hPc4FrT8+n)V~N$yFDmn1K{3w5!8H z@`O#M$*C!IzVAF&m4vSI`rQrxs@*Q5kIfhu-yQhxj( zkAu?4g7OIUs*)?~>bTT3RJSoy_arMk@{2EZ2sGlSx7u0Np($(Yve#(S%9CjZrB=EL=rTTRRd>Tv{Vlrw8^Yy0%DW+VIK$j|Lol^BO(%5U}~4U8VNf?SQ~2$ z%C~6=vfN*1X_x!za8m9a&Hi@FzN5~S^2|wn^gvL;pFONC&(&rWNTWcQx=sEeh&C*r=4P~W*%XL-q~!Xoj1%01~f{F zHJ|IeSdhfA=-hzx#JL8B-EmZh=Njo)i|ce;(CaXMoOO6_+()5N8U1b;&&2k}XlFd< z;{Ny`75D6{9_wM~k?COEpBUu{3$wvRqJwEzGg*!r6h9I&Sud4J7Y+|l! zu=eSC1|?Y==^D|mk>dH0X$S2KBGU|77er_U#)r@VygWE8A=El*RLD0uK-W+Vz`IYAzi z6)C&e!h{xOf1TEA>}4z8qfswpSzh<{^2k^-GEPK3*pk>HL(Gw-j{Gfy1G~idN~ztx z4+U$C`HiV0b;OseA`Ce?8elj>Nx#7+Fw*r2a@<>sXMi8N(;-qj>#Mo!D>@xT`K5R*J58dARnF|b{oXky@64@eNr(g156;ge~-O>tn_Z73Rc8H$EoUc;oLzTii?nJSshxdA=yHR3||Lqr~YdmzVCIyyujTHA738fA= z348*|^WAoyAH3w%H~>Fc>2l>11Se~~<9STmd;6hFzj41qdzado=3-`#EK<7P9W)BWD= zSp_!t=}2z6>QC%$VoHR+U>7l{R;NXO(QZt+gjP}PEJj>{YgA+d*(WSwtl;bMLLajH zd}`m&^>+Q!W|!NW#o838UK2AW#GqLcl^*Bbdx9nYQ(^1KMwsP(_$KCE?i$&on&S;o0FS;ELp`U?$OQ zjkTu`9b8fvCPc?gI9hch!zZ<}Sk0l5(HwD=Q03re4*wX)-7vu_UD4jK?xr~DHPk=#DO3c!pjHouqYRD%Er!s%aZyYUj6~JJo>ztYALiX@@vr zn$!NO)jo98UnmP$18vh4Ynb+4`T|FDmKc)zUE`#7b&s0U=MFy9Hx4N0;%3%M#H`?= zrcJE!kZDhr29I9uY1^e`S=tPrti^tbPRLi_u_^%UqAXe-6}!oUvjnfi`t;FrS5)%+ z;;HfE<#mLC$T~|+!1-G)V!gHTc6E9*so%b2^xe#0s(akK9VdCWvwC;r;^V&CmGsAM zt{@23;bDm8l74pb9J42ZdGATy?LW!81HbZ@jx| z$Hm3i9yiDBk9X%EIaiD@P)4kK8I30J0H|mpl!=h1oPF_b)_4hoZ{e`vTP~js-{RV` z;J3iw-_CixoWUBtg@=c4(-VAK*BicV)!%Zqe)yJykVCx;G$b~rZWGAe>>Wv*yxf;; zt3|}47elU$IPst1G!3=m2~lkxCO@SN6P;3qNlhuk1f~?oDjg*nr9(`uh0=#fNI8cI zM=8VPqLg7G@zxiqT}tXc^+ijO>=j*Dj$HMZA=q~^(44l9iec;hx_ryBeJ!i=US ze}>6mO$9uuEn=d3ID+qAJ2(0fyaocA7$u-#el<)=!|#TPYWUqSc@4iCCbShr1~;0} zCG8dG7^`s)KiXY0q@t0Yk>2Dh@<3|JEXA8L>;GH)>5XA;#ym0~A3kv)blw(P8$TRC z#kMb$vvNpGaM@r%O7FQbrOaFyS-o>>s8vT2e~wFS{64}>6^q$$ z9tvrhX+Cp?e2ppC7-l=WZ7j~UgK0Klv%Uiqv<|j%TqH11WUQVUGj7^qFAf!!NGC4ii(zaNxMC()*3k~oHMTq#nA0h|;X)X$VOL6TY&sdfFfk~rMdnraNE-%%3# zdR6@l{vFqP@F)!C8V?>JxY@KMuG~Ob%BCVTq=}>deW;bguLb&YI11G>$QcVGrR|8jT(blvo5d^R<@10hqQ8i24q>0ANuLWfoUrhSO zJG0zLe2L<8DMc$pm+ru3>kSVkPADkEzlH4$Lmcy9)N^{$X4XOFY86XdZIYWM7-v<_ zW+eoh&PgL~OAWg?ec~f97j2)NY6N0MQcxq1WAqtW57J{e?Aep`S9`XLMfU6zllC<5 zu9d1_A?6%9b&Fy)tCXn7-KxW$4i;+?Sdxq^DHcr5G>Ai+ZH+mLNV>y(B?;c+&CS-aJ_urMI2vC7)uEA zzW2cI+r-#Wz(DmFfWd)qXVNjI*h3WMpWME5;;=r|u2yA+{g=3ohfgI=PW&kbdIy)* z>AxbO8J#%c0N)b=`{9eMM)M*FrGv44TxnO*s`e7oq(Q&??RU<85lN(A2DsxQ`r6cO zdarJhHAgU6+r2p=%t3PyT_jnx}g9iR6T#-4g zsfJMkn&l-T8q%>DC#D*Bi-XS$@=1Vy*G0y2q;Jf$d@%od6y_&}1oNo1nVDn5;_kT1 z`+2$U^Dzt-(&6AVkGF|4T2>{8J>DUX31U_9ZI265HXYX$txlIS{ZYqY0>98$7ok-b zYTr|+dv&^5SwP9^^crQc(6yE&fU`Q?t}Gy&$zZHJ7TiIQasrIZ0f~$Il53acvf!-e z(#Qjo{0DmqOplPUL`xG(%=#e%_LmTn#U+@OWs2$5c4ygNLLN=LLLQl7dfD&FTG@Iz zz3kp))Qum9`f-dW2Zu5EL0ULsjO1mw*t-o?8@FM=I4D_2xAh8talNvUe*o`y6}8N+ zy+z4nLy~zhsYKbn@Y(+$N3h729UGrfy%N1<3IjXX32R>>{DTHo?W@Ve1m6UGJa8Lw z2Sk9fW4jD)3(1XiLaSUOwLEWe!QTFOy){(}%9iq#^=y zn}`LliFgV|3TUyL-^wvKAjyHi}*hy;VREW9v!sa(dsWS-oCooT?_C6bd#D#Pu8N_IL%xqO_*!7po;k zxN`1Dvp}Gg*)WO%t#p0pV@T+yyD>6wnZa)9`rN2jjA#8`U5VfvKp;5bgiJ^1%TiMW z%(yn|lm2gSG~eK&JzmR3Z?91+JyT0BRY0w1+VoIc1G&P)P07KtXj;f{%+`%V(bNzW z3*i18_SF2QPKpx2vVP%H7un+tl+-3c9+kGjIz567$yk@hxT+R-q4OusUD+6DSF=|W zj#y`SNp>B2J?KBkF!yfgx7|JR!*5vRpJfW>QkeCu42z57!Cw0&B6#u)7|7Fih)UE_QqO? z)R6R%wm;S^S6NqSMbkn>J!@sAe2u4xPcQeN_%yRZeIrKB=QN{;dEo0n=O?UF^K%#q z&>QcRwS(!HR^3C-Sw_Xbs#So_ZDJ0$&2IS4HDBLx*U?9QX?JMrkzbs3!WYUk@3vXT zMhTImJVG3GyjsJQI7ByJYmiq?D6B90tOKB22M=5ip+#oo7Xz(G!_)(64O0(_MZ17j z$rsx|y^OOwTmeDrHP8AoYL_wE`aTJ+^u&UzCF+&{Fa}SrE|ZhKoP-KXp_FYN($Xrz zXm|iK$aP_RqUo3d#uxenc5-!Cjple`+33srJ~1+ zj)*EiNg*BIK@70D$_!D;a3RX2S@IZzhcu`%z)?ZAYI=7O?$%&#p=tD3#8{C z#$D4|q@I%*d_dusNJ49zs`kL+R9l85M|MKd4u~$XPsy7EX-+fK{0`2P+-BVcw{VJ6 zX-Ky+W=Sc_-Y5ptr9vB7V5?DcHMShh)q(}XSom?tRioEL7LLVZw?K#({;$&2=6pC8 ze2wug9=`=1!iF?nwTDsM_Wkk|ibE7OGK6B|Oi}owfHHL=SF0cjERrjWq||s(>JD3SqS>%Eom~ zMj!WT=Ygx-v7xhJ1*M{BD-ZqffKy8Ij|EQ2m?PJudHHxv@ZtkN?!L@HL5)Jfz_3pTir55ysKCBgE zhdR~{2*X@xA#5Smu`)A+Jj?4md7XzAO4w*RTGKwnzMEthKA~kbyrz68QARG2Ga8cf zblm9wgiXuRzU>~o?`R*dw!(1)P-n=v&XAgj_#m{2~Z($)~(zGsGqQZtJO&uF+ zL(->mpj^goR~Jbpb^yGtI`HWZ2Z8`n+a-X79Z5^-oL8H0@K-_p_*Wp8yr}bWg)&*= z@`eav^5-a9TI9u2UgX0l%ZEi!h*HK7%8nC30q6?E6HB3xkuhR|NYUZKP9THejV7Fi zr<94$CdGUte>#YFo6c$cX*$f#O+*8BNy2R0wD5Mb{c*{iEh~ur&FXFMP|D`%{VfbCdjtZCh&x{?X3=!Qw5MoFP!Rw4>UXnU-6%^`|yq~QxuQ$h$Fxwi^jT;%o3t5)X!WUSXpn#!S$ zv{(`fOpE-aWjBjl+98ZHhWMu=_@^`rJPNojKuI8 z*CW)|6x1;ZTS0>li@s0bNWq0gWuRsnk87FBD19Rh$inhKWqeg014bh5GGG+qAI`YR zkWOcD%3(lu1n{eMjUgCV?HU5&h{X+x!vxENkOh-fOAuKcBF;ASDjonnAPd_8Y|Xc6 znTUSdq@M%udLFb3$)Dg_$#b)Q4iRa1Wm>!BtKfyGRsn3cfozRs|!46bP{VuJsIdr=+UaB%=Y`z!!zl z@zxG-{*KC{x>k6+g|^^f919&{BiYe$D#j)@#=@(?rGdN3gBs3jJg9!xB2EB&IYD}2 zt*2;!7akN%XhPJImei8I(UNxYhuF!TCK!ciGHtD_+LE-U{}sW~@o;cfK8_<>Shh76 zI)f@Ph6N66ytwCd;=K5vb?-bATiH|&X`wmv8>JINv(`Wg+(q<-NMSn6*fcuTBDKzd ztv?c_YMC02R8c_1k@lgx2w1g$qmMjc55GR83VWy(%>Pg>t(8ZST$r)+QpN?Ckb;LT zh)mQTRISxUsrd-;xg;eT9fU{dH3Lz^~L`mHRN3LP221wD0 z)HuSL#=(s>2Nadf+nQTDdn{$Mn$5wh6BU4_R_$fKs#N_>NbNG+ zyHdTJ6k7u)ebeRY515aU-)u#}N>sE+gIE`|6Csd;65Y1BmrX&7ilO^FQ^p+!Q#1Nn zR80*aGG!&an7`P(Fy*o9`rJa`)2gZt%vQ`9xdL-g@W7fa!5rv8mHgA z@$7C_qjf@^znQZ)`}IakS<0r-#bYzVp{>An0*0cU@$TES*kHLd;&^#Ym1`!hvhA{L zbeZ03Ec3Xw%C@7hM`23ncDpN>Rpg}{TKm%P zysQa#+p-GH#7Cltx84o^WMVv+CYmj?a@^u;P0Ch)-qxO*Gou&kTan!o*2MR9xAuFQ z@!0t{xm$7{wym_bZZN8RVGvTb3?Swf2$9UNcF018{km(w%Fu0+XTCpSx zs?d8bB2XQGvzc&p0M31#m-j;fLj{4(RU!jA?()+lEP zuOeIlo)fN0UQW0IyiooCcMqEpZmg1oJxM|n46gJ%K1+h|R*4mLe7F=WdX&ekp z67n>c`NzvsKLDS!jH*P55-^TzgxZ2I2DQX7Q3Wp+>}1xD`X@qYS%WTgkf_FMw|s3_Yz;1`0F|?U1ZP=5`imnuh-UEtL<1END-&F0_NIWaQ%dZv zbBmTN3QR??GhkvJ2k35LT8gLmKu!u-5=l9PwLMkRY*J-Fp6Oj(VXA%yxq3+%$tF(rl>$s#FRS$ z?!}@}N|4DWu8q02XUxD3d&V5puxIR{({0b#=WMr?be|;b8AAm)01(Z=HX?}!1GJ2N z>Gqp#T-}=ncpWAPXx~1aUR^;D1D^zH7<)>DR^T=GVqnBph_x(C*a~gd5}1W#dD`qj z8J=Wv6W_2ZTG4ocV7 zlYREz6xnUaK)mE{W3EX6v+UT0`;v&iO}})tmhWC(jOi3r$buVD)HSRP+<9I#m(Y^< z7(N8`CoA($tk0;pvc0#nPt{A_2P^lIn%yjHf`d2S%Q1yQ(pAlx7&BfE{KzKHo;gME7oIkB?Qm z0gt%fLc!M32Yk060NI_Xch&LolLYVQ-(NkzIq0zUzPL44d+3Zh+n~to<`rL@EPv;V zyk~OG!FBV@EX%KvKPl(Y-m1Ifxp#|vc9%RE<$fNQZx1On3ZR#B*2smV-d5#{%RN<) zPvl}g`UxJ+e?0QZ?Tt_D{;QE%FmD|9&7IXZ0%LxG4EviIu;Cw9!adClJ8AX^Si1Zx zF6WxLj*x4(y=W-r6lCIvoA1GCViuG;E`)=Cf`KMB2c)hmRHPDBBsS0ppb@-n2OX5s zJA4?IU$>Iy8oe{fL^V1Ig{&0B-4p*^Ku8VOm1oVq=72EenpLj%SgzSnVELKMQ zLxF)Hiqw!X)l#a11~`-cox;g$9u($83wBu??{={}Zbq-(Zm(tujWlHP&avw9MzTe0b3#Is z+H>?X*yLJsP ze&>)7!jN@*xSMUki#OYgV(>!#wyZjq@30s7;DtHf^5Q*uVStq<=_R{zFWkGwXk)(f zGcZR@58V!GL0M5TIVx3#(q8s5JN_PxO?M%qUtyl(Kzq76$!DKOci0=GFk)Eegi@k1 z)OjQ6*-9~YUhd@vT(Yr>9V?RbyT0=@^{ym@hpmz3QgvS-~ zp*2d?;l@U!yw@?SPPHC36j`kkUO6l0svJScWQH?W%Nxrdaz4800YHEXz;F!^d_Ecm zflufZj&kJDe*Sg+4yU85Mk^BcP#7rt9Xt&xK_ALzp2H7a=@*t|_xgPaB8#M){gWkd z1s&`Fejpi%!C%nv=?VAZDUo(?R3vriD8*8vnNwr+#;@X{PnqaY zRdW3nvpTjcJ*8;!*h{d@tW-y^Ly`nE72?9ONX5zPOfW$MXpgD&0VkiT6aM_KLnmba z-VY{&R%n3(JfVws-$Gj`O9UT)apG4yL{vOgms%o21UbVk^j=~1b|%5Wz{^-t!5n{% zuVhuQT2Shs%o;`z%yo%a9(hA)_j3+Eh4*d9!Nf@22*DYkTy?Wm1?k{;NL;jSDCq;M zLC4c3g+4|hBY~U-7=WXbKasNsxu9mz$G?eg-Y%LGKK(ZRl%1wJ z01~*30c4aETO;ZL)UMjyUa5)(18As~hD^1x)fhklAh4_fZKE(=Ni)RCvp>?^AwL$- zBct9?K0NAA73cshgkU}7d`Vpx0d#|PR6w6Ws=uze3PLM8t4?0pa7F1iYZaN-AqKY(%uuU>pF$1V6ys2?d2RLaMc^l6n$Mq7r+K%DvxPCsQSbkfVcK zi*-+kvzewi{DF(Z_qs#|Ig}^!tgvAQuBwC6l?B%)!+9Y4svzL6nsg%SbZj_h9+4Yj z?JhTrE7zl3tHZjjEcG?BLi*=T=_?1TwI|i6J$Z4#Vw$;jIn4$ z!!q~Qa4%@xli}59FZA@CDH1ZLV##25H+2y8g=HD;532?$uT+zT`=Yy0U#M?^xacDK zy;rOPgS^Jyg{-d@kojurDFyFq_N(1#%m5dxnl=ao zYs2#><$i0!&kkB!0V(>ljPzm1bp>c`bRf`3-7)r%Kmh2QM%mPE3Lz5M^ezcO=o|6I zC>Ly0mi_yPig1V9T8`;5pIFb|H$;F`yx*3wd%i5C6i0r zow1?6Zcpaiay1*d;~tYw!f*WxGA2<%OJf_UZ@w?OY*8D}8P?!QNT?RPDF|c))2$A~ z?xy4&5Pw?f+mf%x>D$s(`lc>$cJO-ZF4vW8)-=$!I~A#QxTuMa)wIae3TVcZBA(K> zn=BI0YRL<&!X&diGTQMN@MKT-1TZ^M>GLo)=Ot(G=2)omPW>O4&=lJ z@F)w){+%#TzoS{sWTtFP(n9h1lzl2`W?3RnvD?l4_4MVF+GAi}GDZbPAQUOtT5;DMoNE5wlIlwQDdw1{BaBl;ea$`8VH^1hB^!)@L{QfOoV* z`eK`+L~n}IScL;`5AKqtk}S}kL2D$%y)~m)49&yS!32Ixw^nLcmT|$L8-zx`GThVS z2Hr_tbaXc2jwtW(B> zxi|U?n)sUz_#d6K8+98BWMmnFqf+%8uM2}|;^#QrsJo%d1p)NCYF*^h-JcN0X^gUD zI(15Cn4NTd#f(5$rN*6Yu^yq3;kJy>{K9Q%P`p~yC;?^&N46mlEQcnW!w`5=!n_Q$ z&M>2YQ80PjJZ>}kFPg~%c_LZwOr=>aBMo@~H`j0>mI-B+sj-7UhnIv1PC9FCvkRc4oyN>Q<{pR}M0f z(>QLaLve~OcYTm<#_=AdX(l$a0vr9P%WT!2C_4h{pIn2bxn@gVJ#x$i4p~KAL({Bh zs+*cQmxgrm*FF!bHYRVQ!)Xa2Vnt|y->fuWTNLtO9jz?YUB6UR6U;OF%!IbSKobFH zE|&y?F?oC4lt_(id0+eQJ6m5?v~iL~Y6({~(sUJLTS3iWrZTX-M3hxY^l>< zcA-nNICcFNnnNcfR7c@r)$-}o3nI|1bRW+p_9*MOI?H^U$y|(8nHLqAad^mYD^x@I z&!38m2`bl0h7kpGSdo;Q+#!!Mx8#tAwUM2c1}#m+kAqfEO~4MNrc6jj5#-+NqN<0g zF?GXVL|CSUTBGotuNzhM(o`H;RyAtS9^nmuXr~HkGjKxNa4FLCv?9x@o0FBrxXo9% zZIV)qX)?92Vd4Xg&ZARL(*WixjAREY zrJTI@5?@juwaz3|hNdMDW8D~6K4n4vEzJ`(P#Ij1Fl#}&x|>0jW}i&4s!?8zQVd=a z;ijcptu~QTV;NkD7wIiuyh|i&l|We99{FYzMNhAW?LVO zf(H476(jcdTRvJO+Ga!wqvkscsrV>p*E3j=h**O_n`+TsFTdaQZ=q&UvF2JumsSBt z&=gYbHkPgl1BJzC*+lHZTVpaV078!$AUp-gV8$7M2r@3q-q-9y*TZTxteR`77pW-p zBW>Hrd1DQ>V@xM>B}!@}3!&x0_JaX3j~dhd2z74ewGslJkUC@;S!v^BZ zK;$V9;TH=tM+RmsF`$sp>6l4mFVD>4fyVcJonsZ}kFC$XlEht`OXROyXCqcX)kXue zMyuyZHEKgE2-Mbo5d%m~fWoAL_3;U0?92BRM|I3V;#gg<)3Y%I%n}9*j~5lw7%$eX zluBwXHJVLvi%-uAl+N2E(_xEGZ!li)msSf9axEzi5P5|h&RBx|;38oy1oWc&{Gg>+ z7qw;Y;0qz#hm=T*7BhsxI=A-*ZJ_0eTHfyh6usBYE^69TO$0RU5mqmSGm4+jzzi#a zMBM~($&dLlfC!B1x0oU284`!u?Je7F^xPT$(*fbzv4ftkw_JhCp-9SrIdvpQfhY1N z(aVMGqp}BInDffuu9*+w10v4sml2_Y>3 zX|}MLd|ah5%_9xj@n0nD*;f&2X1~U>Z{)#vu@dE5!w9x8yE=!mU+dYe5h1%>!4|UX z+M&AiVH^smE5XT95JRrEt)AjLaQVvYE$z5qM*~;dp#Sl(YhBooMw5tLDXoA#(YQnN z*eETQoo+hegw1L=Li4z03K`D{erT#Zra3K`$7Y=ntjA(Krl&YgiQV>7w&}i@;{Hv2 z$0?~QlRJl3r@g7u^;k7^h91{WJxz~IQ=G`OI?bn^uE&}wx?Y7xeH1e`;Pubkt!S_R z3`N)W&)Pk!c;0`e;#c+yk1y{}DZahm-#vRF?y^y_vUvLL;u*yX-aoTg&hJ$5G`$n@ zR~D!1-5K`oG<&y9@6?YgizRxu)ZXRxZn548t7jIIyNk1mjMSnS=eLg!KJTF5;^}(F zvD~CCw|7tTck0m7_3jLNcbdIh=I=xsOZ0B3z02+0Vt=>U>RY7xbnj46bgjOQ>Z6;f zk9TbSvHJAxX@$KL<(#f}XV|;b?7L`0y<>*XyT$&l$R1DPvy3WN4N>J< zr^<~}O{%=osq&g3s@x7$(&?+9N*-%;hP6oCsB)uI#FWN@5g(uUe&ECUCAZ8q>}CXE&|H5O*E@LwjgCw*BB`C z5wvE=w7Q4%5?B*FDlyBFg_qV0u?pgV(m34;fkaB2xGa;{fXz4^Iyft&(M*%HdP4Vd zuR!8-2vHI`7@F=V-E_AzMj?^rv zt;H5nqRZLhGE$<#ON-5t5AC#BKv6dOos ztlPyjshy-+#S|%xRa#7vn*W=C{rg{*2JDjx*e4aRPihu?fPGQ{`=kQ)Nd@eaI!rmR zPby%aRKPx|-Mj_+qyqLy1?-dB$y=~bDqx?~eD*IBlq%k^RSfmEqR^|=bYEYS*_P3l zrDs`%*JTo?^^3k*WxkXwioBvG{PnFEHDvS;J`5TClR}hHw}4knhZr-lXjey5X0X~G zsqKV3vhUF{wO_H#9UDwu^m|MZc>ErZv8^aTWL9%lbAa=VOc~%jrC-5mA0ACZ1!<^2 zK3{8RZ3T)FPzCx`1^mjbRzn4Hv?Ei!@LZM-equqLSk>9K*#`A6#$w@LesqD5PWI0W zO+X;zxPakmO00|qc)Uu480)q@T1K&|;o0oRd|FitHbrN*g+0@pZc0||(0kBk!sj-J8bw9`WM+X2n|GSHn+K_^tu@d^l8ZE&T5 zRs{^8RiIy0U;{crxob&;A>bI`qoQJx1<~LkIsdZscu4Nx7}Sv5$AXOwdu9{q-cCGZdx7D?i`nd5tYOLQ7Z!|sm`#z6PU?XWL_J_&tp;Ix_7%!FEOth>3ut~h^?@Ee z&@YnqMKedInf0pEStweBPv-fA4&?8C>DKJiL^Lz%K^imkfjC?+U&69KCp(<53)lPl%T_3a#Iu(BU_P0l(yFhHo}s7PhMsP3o`&i>+^?^SdltR+_iPM=`A{;7>=L>D zsMKQ;uRA8>t{oG3%98B!+HW1R-+BlJaNe}E*9W@0CMRE$Qw-${)d#yx9tZd;Rc`%unMjbMJb#+v3Y8oO@;xtxFE%!4JFF5&>k6E40nc4KV} z?tt~$Y-hHe5+vV>-wDzFmZ?MpAVEzMZ-BE~pBXrBQq;VLxS}+Z9(i(J_`0 zWz^Sn$E+wM=V-?5qZx-ohFuz1176S!`U9iv7P-U(eY@uFZy493Q3M^p*7@Rk3p-E@ z1r_0i!VE5AS9JEX{9+ z71_yAt?Xyh8uKfDr4)!jQc}I!{XDX5v6Pw9)9zv}&XFIPN9l31=pr&7BU82#Wwc!Q zWO!5=e2e-4>#I600!(Y7UX9+xS`C(2nR z&XTxPi6=>HR^ki^yfqwmNP-dy(>Y$Q&TAWA~VJDn&mQ{piaTa z;HfN>fK*?20zVwdV{y6tMEL?GkMjBoC00n3I6%t1<-{CWR}?R}wcIpEsb2Adz2zly zd|DK}y=62}Gt zY*f5Z-V;jB)+KqNyKAX)wt~eu8gHmiPMjcz)sJ<0wAuh&m@qM*jjB#HZx+Kf7c^nm zI@7$_!^|{vbdRm3(tZ`$I{R#2Sh_3*Zz4+lMM_-yK)KbVmSuwjGA=V@SO#4sV~h3K zGJxxWa+?iNI=C@FI#Ub+by`5ps6JBGz~chSa!6Uu69UR|NLkJz zd}%;|1VOQjXY2ni1M11RcX`4q39Hh;GjvI;;ff{uY~1sWaXR3o5192!;IeDHpIakV zs{#U}y!>T;lBreXz=|5q2H>6w;FcU zS=7pv2vdnSy?HDy3yIVj+C|{a)cF#4Yg&7oDbbnRSUXngqBmMCLOj@uXoHik}v67cg@f_#M0yyr_8xt!n&o#AYvV650| zpNL7uCHpobQTYzhL`4PVr~ytGD~u7_DlP`(1w~+pu>yuAM+`MsfyBO-BlVcd!Md}6 zLIsF21$iJwwDKP{{X)2+JOh*{3#6F?dQ{&#pmxfaO7Vso4K&^q>h)>=o;QoeDfZ2x z-azZ&n>|LB_NI|$aKX$PNhXY#2aj&3(Q?j&5l`xBctajQkf$x0&>#_KO(L8&Ik0V5Oog7#ctz^Ioi=MW8HC&LJs>{bh#*o1&=Uf2jZiWW7rlgp zl1UU7N|HG)l%9;s%T8dE^o7!9IfOX`9kCODx~vbBgi(`Qizqk^#zq(o3m%x5JgDZW z0OlUJ+JM^QKpp1UI(9fKHp?V~&&|ww!c4DH=lt%%a#<*15JrqA#i?t`tO9{Kx`j=yPT~t2L`DoMqMJ zei`%HGCUIJJs?!-RBXDtRg|XPTM{czs?TWQ)BI{l4J%a%_(CH>0pWlac7>6)7LZpe zbl7Q~LWctj1NCJ3Tu8yM&@J;OJT8jMV80+70c?zr9o!7)OIO^VRM47FcD&08||p_mK3t9MuFMR;H*?TcSi1+b_ z3ym`m$aJV7>t{kv0u0pdQ5mSIfrCf5?{IN>s9FPOb~cl%ONR#zQ`E;T)P*ft%nc3G zrRw*1lQm z&>O@|TDS_gf$x{8#f2!i~LX-5=Ao@$N-gZ8@o{9w9H`H ztPE??+Aazf>unTe$ml)!X2@8+r4sx$Lpn>K3{RzMw;f zH{sJ4cfeGgzbMkR{6!U;t@F_X>Hy{UFeukVq2C&^GfYHVT^p#@=kpokcBO%aKX zfigDD3DsCjQTAH4XIwB!3{+vMMs7e&LFnGAv!6{235j*9MpqOCx9IRnzDTE!K=*#v zv&rU@Vzk8kXvU7wjGdzyi=!D!qZzwKGj@+=>>16t02i+Uf#2eXnSW>yFNezk!U@Ye zqt*WnLRBA2|1S20D<~ss7Hy1=^km_)(9F=Ab1g;btNEdl7qbART6PW9axu$Js^xG~ zOBMTC!as_`icq>(!Q;s3xJqAL!IS*VadR?kE&-|ML;EBL`lAKj_l)Fk7?1U88gK*@V^0-(dh~HVyD0I(wgEA|mznhGc4HD%-;1v#Z=(iQJF?7Nwv=G##rFLj)XsKP&2bMyG zsrQuIsi~=@cFU-=6xOE08KOtjbBdxib`@>gM&){{NV9(}iDOGu3~0&-q~2ZoiIPP+ z9Pu^5=fw0dcHUczyJy{78HDTg77FH#^pvH59c8Xb4=W6UMc0)Q7GYPF232VV=hblv zF4YBhdchrD56T}7Pyz6_q~Be2!QEbPm)F0?e{S=i+xc1X|5;?Vrn4+Bow%gdW68in z!#dg%lp0MJ?d(ULL3Hc{_@ozhCcO+tUvVisWnE(naG-5V8NFMIw=fsh#eJ{j&(FRR z<7AdFs{B`USIrJF&wm(0FigrwVMD3nq@q4#J&<`qVgLG9;3P9W@zgiI@t1Z8IJ7s` zKh)QVvEP+i=?q@F6*sRrm|e#jSiRkmI;b>?qJdVLubTTI1}m)_iXtl28Cg`SU9;S1 ziL5`l8ostmFzXYDYg^p{kuZ2zX5yN)ztGiN1@>gOj88D7mt|QB0Yv`a{B_Q;QOEbb zN}Diih56`$m{1pzJ}V$u-nCBBPilCTRoFD33a9$0>eU?_>~#ONVKT|8|ML0i>L)i; zzxKz|)yMx>$IPt*&?`(A)ChTn?4*80b`3XCTt;T{-Z@F-o|5otJ}i>^72yUm=l{N5 zx;6aLAM4T}ez_rvJ&a;Yi>mADDCmYF)!K0wGwO2V3WZjeH#{0`n;?$^^)M?dCM3g@4sy+g&U~?+sC}Td8kvnm+j8{u0 z8wD>m7Wry99t`CozQ3F>Pa3q)ON>f2pD+8^67K4OiOk}pK;OXhJIx8#h6;)5C;Vo* z#@?q^HHBhx^2t@M8Q)FYh~<;TPJ}09jp+hdPVxs{>ImeS zD$1JhBRf=BrR)wd#-`VCub;ll@9V=<_x=?Fi}#SdH{q=^!87HoNp`Eg6hyL;Z0-b+ ztgOkomXMmoktf0i#o7X?Ew=oDs!cpTu4ApUzh&|h;f>r>EGmDP8%eY%M`zCRhn)Pw z3)zQTHEvhh%jG!2bi7(=?NewlJt8<5ulY)g_&|P-wtC!uWznXSDmEMqJYGPsL`rh@ zI&K^NLXXG!MSo~>kL~{1T8s~lF;1EB>Nw3s)fwOWGcI+FPF&G->?EP}qalf2JEo+^ z1+0c71s)1X3OuT$=pK(^NK)W5NEO%8i=oz40w5pfw>nNF>i`rIq(H&$Yq|1C62S@| zT9OD>Ht8E|tD%6P2n9$I!Dfn#yfMt7JtI+`HSaIh71=}L4r&Cw& z$B0f&AQVn>ZVwFXko9o1!|VDgpcPoExg+SP9_qV$Fg=>Pd$2#6TY<1Wo4b4VkftH= zS1<_d3UzYNK*EqcuZ@BNfepja=JgZd)*#HD>P$#u2{lk)KEZztAnF;H{1~4tk*+Th z+CiF@0&8RF-uKFW8au^!ioXPZJ5)wd>q3P=)ZArMkgo4Inpe8Hv#5`ooc#J;qgj0$ z%Ge;J&jgYfK7a#h(E&)yy5UhY#}a$=S01ozb;8w(Ri8HOwSUc@wEq4<4V4^KwTM z)QC@4LBnHKyHv2QTXCHWb91Y+R4fv>4^ao`J62p4K>z-uh`QDAl2xMAA}K1_sY-;t zS`UUDd@}oQ^Gs>QL3Icw%=-O3X+s+bX+vj3$@u&!1s!R;G5qxhvSu}-z#QF^K0 zV~T4-Lca%wBiz(Q+Tb+p7YExE=vrUsxDQcYw&)O~DV)-if}oUvPlW#=oc574Phke0 z0);9QPN}4;NlN}gg2jA+sP#}_~+FX0gB$V57lv0TyV4E!EV=mo#W5=GESk`Kyc3!!7f0$Uc4 z$@*n}cbs3VuhEg|;J`mcw4+{!!R>4Bk1g=Mzp{qJ(IntyLag;*oj~^nPUfA-@n6`G zT;c>I-$RSOM%C)ihiP6<2CSJw?1f|Ai(SOvcw&ptp&T5>@0k(RQ$ZT}rEe7*z(=xraQ7qd>egff6|9G-9t!N_#}tB9uw+klb-Q>oP#quL z=GE=y2f+6518VqE@m;s;sR8V;J=Ha>XjOc_@$OFR4 zA95ZvQLs0&JjnTD4+X12hEac&>P^-MY&SY6RSrZ{EmuVP58}_CEdA0&o`18Ckbqi8^&s+BA zYT*#b5qgoF4fFf7$rzo%c20zep;SJ-I@DqSp#s<{iX06F6$F1lfHn{aVh6&q3hJ5) zSqXw5>5ER6V zcxU_K&qxHUM@AwkkZA~vM8boUNO*7(2@f@igvXjhy2B(Q&B}&Zd2iS-i=XRYk6ppQ zinI>|?JM{LPz&v`QLY0N(4HA#kd+5iY0snxgRBir7AB@=O*dInhVPnIG!+)IYg$27 z285@gjZFXvZCujO27BH1l{0)W0wEJ1Cu>v4B2uPLAU2?kun;=4uiQ8criOpOJ7J?Opl!6<)JbbT+Tp=R zJ3RPkhlhH!!(%<#8;3``t8f~hAs~b zw0h|FSToR@o2>He?$#Nz%jK)lAr0BH}>4hD$7Mz>3&o3_OGfFlHlHzIq3M~n|lMK?a|u{J(< zFyn(;Q#$NJF{9B@Da+4MNs4K*P$^<`{b+`v8K2AyR6uFAk>Q7$rJxwxaW3iTI44Cl zn4b1FRHN5*H7d}rM&2sMfoc{#MK!W5$)lQGRuG#`k4S3S$skxCfL|HKXjz`RXxZBC z6e-{4Pq*_kpqCF*FB=u*599C%DG~RmWgm*eAtVNfC=4J0sq27{5?a)__uG+Hc)8GT zLE-%tShAoDk#s^GbF8e>u^Q_0&p@*GrctseYf#b)?WZ3mQv8suqm{hV04)>%oqj^* z9)F@6%4Br(133D*&41QG7Hu)?MS6dkJYYSqEvi$6G9o$#9IN`{Md?$+TAa*SSVwce0T=Ptp7Yy2WUVbANHSt4%EZ^sUx;?R#30PvdR7I5cT2H91%CJ z-Gf~N4qhT!N17XO7 z)=;^+i*>nAQQvOLX^iSTwb37$FeHJP3djB)|H=Ia3=Yr~IBPghLqhd|)O=)YFuI_7 zmUcBvg&yh-*h3AuJ=7o@{~9$O4geb^-p;5z1ryu0(jw8QAg=Xe%?9uT+>4&M$A8uZ z|2BVjJ3lpH`Ef~@U&-nqjs!nz14DSDRW(0UPQ78SGgMBULIn(z3pGcU|5n1vePAdw z@L?zf}hLOWiaFF8Z)3V-yj5HjD%N4AcyN244CZ=juE8LXgz} zwVe}@V{=?A3@p2|?0xdxjA*cOjHtBC!j5Hms;-1lc{^KkmlHxU5PNmFo5h_O2HBF=M(i#kuUQ#!X=CjWXr-)sQf}^(_ZOMQ33;2HLWtSXi}`~&AXMl#PW~0gU_pFFS*BCMwO#g> z1BLxfvY0H=$h%@TpBB+I(NFr3*!g1%x*lOZ*=tA0sTAe>^xWm?PDDfr|7pegZaKey zV@i@gJUl!FQ1&HqBH=wRK29k;fuHK(f%Jz4()SLeKQNHKr%9_;JD^P)lWyo|fq_BhM zs)XAs2%xu7d6qp%>YgM6JxK<7k__}D8R!WI+BeWA>-7XWVzh~~%s)CM8z!ze-;W+> zRDAI5XNNx_8kAZ14+2L|F2Z%?lRnCS#o`zlxn5i(s?kiNbKrzn)9+-Q{CdEN&bz&M zoCpK;6=~w!?-VMoIDFuA8c#sO)ym4dS6cAqy&E~g+-O*j`LcpnAzF*{;9QyIADhAjd4$vD5hmU0j|iSPl~h0Xh3V>dzYwnK6GS?^ z66lDJQ5TTJlli+h+9340x(0)YUx7^#`UAMYPNA`ViX2IZe?}V9C}DQ=V?LoHI0VpHwtkTUSwWMa1An=laE)TVPAvh)gkz)) zM)VQ$q*pC#U#T5`Oqc=CMfvCQIexqP)0v}^Ec+!HU_lJEfYu@YPNB6d8wYH3+j}-< zKbLYGG;3*up0$|cx1=8IBD0s{2(_zH?b=NOgG`+Cb)jR?j+51AT%h+c)0BG1_T?Y3 zht@Swrjv@p08vSYD|BKGJf?Y+%ZGD25&`r`c0aaYB%&9;wSdAm(&q$XLddEwMt079 z$l}0fjuf@rtk)HGRrMPDb_O+4c#uZ2!?;aeihH5~IUWtj@n}GfM+0&^8j$1BfE>3| zU3C~ObfIIDD|YQc66(&!*|yHvLEQ(2OxOcK-2X@ce2x$l+SVvHVyr=>R{_lKoMmI=>W(wd+T0kc?kAe|ZvqL%a|$ zNwG0jLCdL|+W#YxznFSZCpM?L>#FSx3ix7C_^fC%R@8@>>a{d5JLPSlhA96T_M3;S z3Ba;;ti{%=#DupTRQHk^CVJ>~Gf@_>Izm~TWdnOIF~@LBB(J2BCxX11C^Esu*|R33 z!!IBwej#D~g0mYi#)_0TOcn_)rgG^4$t$v#dm60_NIgK%hQmehdad&Yfh&30D!`3| zZ!H-6C=C$QFAM^dz$gR~;jTP{R*gc@;aUPS1U0n7{Q z6wNYYl%p+Ul&>43WW^+_VbEF|wE5B79c6OR`NY_xT;!nD~H;A6^>f zg!9Sn{^drM&SwT=SODGm2)1oiv4!|$uf&!F3r1$VWg&`HufOcbiWQ)T<14MKTHM) zE+WL%clhV&o{5h{x6*{4tg1bE;?Da7I>ZA(&S~U9A7T%S{Rnxz%|Hxc4g784@f;y>708tT8^u>{qmdV2`#Io&48s&RC}e zq%J#v&)g;;*k*r9)Q^9qO+xPW2Y4sf`u zUzKgh@_%zPB3A0YyAL%^Rc$(n1V~?J$6V9lfClv+N`tSpk#5`%w?^t18bp4uKh<>d zV*-$A<*PLm1*cP{v}PAm3Y(#tMAcBpu=$rslDc3ge#QOj^Q?qw+%-R3=FDu?eyJn> zJv`a-$$YPs7?E+Ad6g_z+qAC$=Gj39_|ZrRYXwG>{Or)@AqpP3LwdYj-6d@CN=r$6 z{&udM5n?oa_S1R|6b)`cFznglznC6Ci8?b|Rf{CHF+ARq;%12HxGJ45yXxheWT7%! zpHFUvK>d^Kk-Ogc;J%{!pzY`i=M_?gj;3d{erJL9?1BwK0qLLJx7*?C)z!iimn6||mg6Us zX#Hk6abi`e?r)ZpoSKu}{8Rt_pRE4Z-+A=Ly@;76DEaIwdgC`o;eWjeG-8~EXWUNN z110k8BxW;QaT1+~kUH5Ezg5P`5#$|%G+A~&Q8w?+ykhlU+7BhqRJSsw6Ca^!wDZc! z;fxWy@*U8o?07^T0fEpqRS`>9S8r8}4_7fhSVdb^KpEHwcPxt?k-4Ze7K4|?OLJ(k z7DbfFTX4-a!3Sv3g4T}+mAYQaXhX})T^V42FlIJCy0feLrGxLV0YFF&WiHE7=z3xC z{hGpqnalLJXdOgj3RlFCyLqW}FOoJkjqLV85yNb-T6@wDSP``zv`c&9Nk$!QMh*rJ z6yN~ zvt^rgfO4$~u5%V#)A z^^N!d)EA5QUdN^YGpkKFq$-oDu~hYet=7JmMA6O!tiamzJ#}$uHy=nEHzj7^*X+Hz zH<&ydU{oJWjaEh6NuW^}QIb}w3tLR~E2Jp3ipVu3ht(sg1s!wU$2&A>Be=!br3DJW zy%rCXL!wZ_2I@LiF(#9@xb{KsNw`W8#WJjDHt`DHs5?0L7-girIcY{X=@205oRyFU zk{;w+X{e5CDAPDnJdEu*6;HMDr_=ez+IA0-UHK+LpcTfKv0TT{kRcFk3<>%X8)E>E zlW1}wfWcuBufah#^lQXeXuIixN*%5drHOpd%n7Td9=|@Np>C3Qxbo{)*SCbu4d9H7 zpfYMOLMH(&0GI!+Lc&@*lbP&S6WxA?8V{ptgRDbO!&nFEFrqQVvZR_KZxo;8lL8sI-P z3PWoSIFQ_yI7n-<0cW5TIzxFEtzhLicnzHm6c&o1?iO_B&KEKQ#lTE%yBKxJ#*=vg zpq)M!nEmq8t#tfI>#pR-+^Xy}RwXv2!EVNnWJ3>jf?TZ!Gf=H{q(etojqs`ck=HZ1 zHjom5gviJ2fb)D_0Rp>agAcp{vZ6o^ax55GxEp0_O=5MZ7XFC2(gPTd@RztrcFeEc zEE9J}IrX3%@?v7$1B3vP88Q7}X@(!RSfOlL(Uvj6pbTXglkAca#65{c{77%ZO>u7$ znkO<|A5Iq2cNJ4+dVsecb5%wk6d?I2G~xW5aJ&Up05p;9<&NHBH0@{O;I_}~Am@f4{&4V556$3>7n3LTpi1e5jyacxrK#u=DqV47>1 zv1Wduffaj9GQaS*@!wTB4G!X~A__;g0%8B*J%+stY)x0%=bx+4@N2TKCh63Xq#@L< z<}i?TBCX|=hJmXV5mo?$K`NuxrZIZ#;Zj3vW&7H-lhk2O#?fC&f!yD|}7U^_Tn4A0Aq9=b*e*>9)m z=jt=4m4Wjg->2#3NA4V-R*25@O2QNSSVWc~Q7asaB zBI7xHqnPxkOl&>j!}!8DW=2(g{LHkB0|p0M*hm^<0Z)WW;7s@Qp0||B?xtWhFWHW( zUNt5f*k4@K1vkdgw2*H^AJFmLa2%eK>yq6?q$bt2Y$Qsm?Zn&GUoK#LEcT-)7Llir zT08&T0v*CkA^1I*l^td+o&1*dj2ktaJcGl+Og4s98nu!Z=ma zDt=<~r2vF$?_jC=>>D6E8rYyus?)(zU4#T>FTlmJ9>UW+Pvg4zHWH4t@+hspL0kT< zv@b&9Cy27Y%_reY*~Xy&Z;G~#3}EOm;CN#2NV{$)y?c zP|^l3*dzHb87*ttW1xtTw|Qmc6<)gx4R20%hy{JA+)e{X3eb)=a7D>+2UYBj!2d3e z?*b?@)9aUbey`#b7=)JaqK+h9bE+$RZ>PH|U?!HO*g#3Al~=br2uoqXX;!6n+1HJL zK;=19Lf>v{E?M-SCHslq0DeE1YT5**7O`QfLJZb|y#*h6GX7-B@*T^+s4>H%7|G4f z)L2@KZN){>H%0jq3z)d}O`QM1g1q-YgNPM7k(y6-U6<@qNoe)2Kk{Z5$1<_HrxO%aZ^iMPWF$)U4rHf=+m0Sa%n`U?NE;kw>;9UDTjOi|8sJ zI~8+Uh0sC&ygb?c@+O-D1N~NiS_3OGQ&Bb8()xjt>q&16gygi47)+}0jxo2CnStAe zsSU2VMH4`@M>{YH0?6=)7yv^-l@wz(+2LEJM_7?U2B<)+f(%We+s!Nod@`2~Hj*Bg z3Tf;$Ft9ijjSB)3NK>3nEq)?{i75^N&s_6YPo;+>RuFdXZ;%vz<|&RTTc6?t?`3rjVj7QgleG=sHgu3ioDY$~BrrhtKg?jnkKBd$ z2s%!$A)qHTHH9>Ygif;iW4Nnv-aCRA!|DTn=GvL{w=y-X`8!;zdfHYQ_0oWi8Y9Yp zne#`^9PsFApJbX|hUQTJHU~h-v6lJ;z(6y*9x|KC2t|Zb)weuqRp9&D>;kmeSO3iY zE_Nv)woKteybUwvx07V7S!HGB#5ib#V7La~Fw&oLyjYW0rjmO}HKlcuq?(qmMGH2} zToMMc*M(<|+6q`+LQA@s3d4^o-&wEvl;bx=e}~#)$rPEGtowAEN!Pe$qZHrv@@Se3 zd14mmqmyH8+UkkLiLW!FGD`*q^(w(DT)veXak5LN{ms zdM4e-+%1+%2ZP$^Q1{g1m*YMy0VM5VI=xl4UR{p6yHCi0z9jW=Y}xDz$%^r(*%h)~ zE5%^9NSAzeg>sOHVRmJ%7Pbx}>{I@W(?u#`Il$#jpIDKUu5q9NDt3*7vt<`kXfxWD z08Y@v?mdpG1?}XDs_ojzrE~Gv$(86QOBa(h|Kzt(tCKZv)Vxv>#RGdTPmjl4{p)t4 zF3g}Q%+>tZ>}bBgSh=3>?FTI-?}0edlm()f?V>mRv~1|R`R@o~ncgwG*vQY*e?EPU z);lF&zxL-ZKeBE@f5ip8pII9(#*R>~L~+xfeUH7@{- zc4VXYHdoA4TH#H>{9xBp{w1@fSh-*_VmZ~3{Edn8u@MO zb?j%>c8qf}s-ODe^n4VR6W3{3xq8nR)wObyckb)fx$=_hqtLyMbx+P;TNb~t?se97 zkN()IulLD&$pmv9$m!`#?;n6%n=Bayj(Vw1esTH-qUZ;0NPgbncBtPU($782HU`Xw zzod=SjKC$$2y|@(0PQuIzZw}Fh+d*&Ts~Gx8P#Dz74#0vpAE7S>9SNDCz=e&h0kskH4f>t1 z0ak>`vl!rswF7MQYnU?gR{?O+dHD3%z?VPC$ro#}_nkfbqdf{7NgqG9$T7+5^e3rU zrDrJMPaF7RCjWovIFG_D1SD$@5d-PC`+?$|(~^HivnesdJZdlH=pk(dYcj7J_2u^;nmt@_)79JzDf0-7Fu65J5*0bwoSF zfhV%+G-q2>b@II-1HYo`R2YSA1rs0!#2fDKo(xDg+q z3AQV1*;HD0l*8$@bGRHe`n0u%!>w+@2(TJaa=TtN)dn7%mN)>dT1NHXVRoq)EqF_~ zg0f{g#%#u@mTRv&`V3C`bwOLkf^LPQlVP zh?7x9!I80`ih-f9wUx;+52&#zTmO$hOIU9SUcl(}Q&F;*E|Mic4Mc1T9$|)S-Ll*% zo^aQWlZ|Ff47&#DONpu^JTBlim>mszcqq_b^ zK?$`x&3(lRQKAhs?Js5XbTrv!i=?9-Tn>5AZUfHj>wZeXOuIBDsaHV1ncSOx_`Cito05?un*8bUOiIV{!L!WwR|9`Au(m#EOG`NQAHgKTUBe_(z z^h`BH@;YreVzGs?irqok5Xu7z3W_k9&%b_CY0(-K1yIFhm}`f)3qq&-z58CoM2W@o zQ}hNduN-}XFc1fWDh2pPi}(QLWgSo)Ewr|6 zU;Zf+@6~i$>(P!cGX7YlCMkF9*yWd95NW)uK^Ii^Bt$y8oZ8IAhz9|MQ_ry|dlr zYWD4NY%v$8KKi+jd@eE(tCz&s*h`Fjh1TIPg5p1-B3KjryhM?8bhG2%$$Q+XX7?ip z?I=dNGrM242K}S4H?hqCj!W7>er{FcPLhL&0_YV@KPCp$kgrqWo5njXCy3UD-N4~{ z*#+j|(2{QE>PV}z!BIz0J*9)APQEFInC+H=OTx?+oTQ7SEuc;_sMB;&&kDf|fTz{W zvPKY;i}+e2up{%O1UOQH*%9TWB=tC{kEAN4DDgRJ;aHtUpz6nblXQWXt!37q1P9_? z6REDb{lT(19E3IqPVt8B)I(zMoz+pKubyfFFw&dGjrC}Ssifw*re_F0GI`m^1bdanvdJ1lX2Cz^;cOutj9bA#VnfE{6seC(kzsNSE5OK3 z3g-$aV#Uy=@d(v*D`j4ZkfD{=PfhZ<4Mb%_f?_zN355jca{|*f7Xg!bB}6OF6HG6S z1D*&|xllY!JkNOgfs25NXocr>?4URkNYbMhil@e7`8vcE6oEC|9 zp?H#FdOrE?Vqn7Vx(Jw-P~sONpK#^IsKj!tAF?tw60#D)Kus5&TXJC%KZ9rrPOtNd zOM%1Y^s>2y*@LLN0};?p`~;z#cTkyVJOfA|^ZpI{w521>W+`(@ZA5`+SbJS~#CQZG}l&l`jT&!MJ4ep(ZbFCVzPFd6%Urwl$ z{GIuKSdircmsK4i*3*|q_e~|JN=?D54pxH-E>-4XO{%tDW3Q}TD7cTskAwz%CygrC zJOw*`7qMch0=u4l*;A-0Gc#oS-b1uefp6e${SxP}V$XD!O;!SebFlZ96KM5`dP<2Y z!UI!p^EWUrv?{O}G0E84I>n5WK?;sOa*hEk`(3U}j#D2s0to9W`sv!})P{#mR%G+1 z#dJV-jdi@X+hp1;SDiO%^}8B>4c-!JSSU6$W#mBFz!2bzS9vY6%BkLo6aGaI*f?{Z zFruk@5{^%~H(s+;5RmRyWfmYT`=ulMg+KE}$M=4;nayeKzov$;_oIUy#D@ww;g!)A z@I{%3TJN+m63ddduS+;?IFS$MbV^aQPQ1Z`C_FD3AOj*eN zR27rb;*3r5cWA4LP~_#jp57R(x{FVN(6}VTfy}4Uf?Ix^AD2bPspH({EFfbZLK_;^ z%!jo_i(!-?jD01Y(80wLM_zb}LKu8*icAJ4%Ly`u7BQXCnyBM`au$qLs<321DP zaype)&~|GKcY9jcW8F)<>db6%4)hSm$f)Z$N%FVZjuG={uzZ9VQGBNcr`MH7jhS(! zXk`sNy$a!njIjBC+f`At-lk1=?Ap1bXfLsh2QWU;6B;)Q+o@%Teia#c-_-P2r`<}E z*q6{wr&>7vkBY#v*tomd^Lkx!N}R>-qv5ykM=jpLqK_#TKv$-&OCGXxwK9c+?vs|j ziUVZ*j*!H&A67O~Sz{(5a312doO7Dd^9T1pQk`Y3InK{6K;o_ZuQ)Gus)Ij~7wf~f zsw#o+7{5B6m1N{Ocz^}saRbFcJZfo&w#HpR}|C^0cL|N}nQ(Y`2Pd_E(iX z0N<0mUNZQ&6-*%5Uq|8~ge`g6Y6!nJGRS9CDUdxLepe)5dA{92d_vh_luujQVfnnJ zui{93oi@gUXMaZ710bH}^=$^?QwAJYoKe(4{LhZW|DthDwfX8?Af8gCfb?kiT@4^| zi=|Vd@@q=8v;*@=OGAkO!4;A`dH%!79{}snGBwZChjOpsx`_M!f- zBZyp8tw4D!{GJL>au25yqw)ikM=kBp+-qrw5hxxTfbt&Y4?ua8_YXOgj~RRq4M6#n zg$bv6B7oCbtn&>T-sGG zE5KcBH~mBTtKQ9jtm|tqSwzHqsY75VD`4VXz2;q1kRAriZUGev9eMe(A3ydf_Tix2fg(Lb=fi?Eg zL9i~+hxBccgVHYRxPQ*)Ke91p0fEWldY@<6&n2#ZS!Zpde<>GuAH$oJtq$R%(E=D3 zMG?*Uckn}y#+N~1t;OOFA(pd#shO+2)MU=K7BYLGB6%f1c%*gK|5u@(4=VyU|JfT! zo+8=t0^HoaDsB#PT&I`MDdJQQ8kDnefE&t*zSPw)&kU?728TuGn$BYcjMr zA!^+GVXS>cTQw5*CqFIz=;_qOc?JO zZsFKB(ZW6b7LEonE<#G} zdEVJ>aNkhUjjEc?&&?2!!s1 zrF+IbK({U5D|33)iZ!Z!n%B&Z!@_K58+nOOQ%CvrJz8Wi7Xt-s?)$oVM%bKU6&PFB z#`$oI$GydVJ9TU$)C)=DIs~c^+Na#$^%UoLDy(qoBa)X9cig-xV;`ba;xDPc7Hy%{D^U1w&d2!k{ z?rU4DLAuJ(7d^BE`bHTr7+l;A_RgFV|w>`RCgK zGoNMAixdkheX($H>_4Bpxi1vd8&SQ)F!)QMAd;!K@bVFq#EjoClilx$@W9)=Y=@LDoN znNhMN*OKhwl0X^U^9V#=oo?n7e%697{A1>74T(izv$a_%$JX{`e?rvRSb#vZIi04x zWf!4Oh=2@W?KhJ2lzpCR_R?=%`$L$4D#V|)Cr)%wd0NmM3GGR!1RpWK)xPloK(&N; zPGD_ffQ9eCy1~A~q_;1o6DnE*%M@2(%jForvfQl3#{?|ay@2H?AOUOO88lZ5N+ugB z$i!Yyfr#Ap5MQ#fHIhETr`_uO2V6Fy3NiF8_%e z=$-9gw3e?92btQ*-)d`t30?J9(f?MCGYhf>a;{A!WB+osT`|U*$^(4?WMF@rKgpYL zh0f16s5v>axLhj>))ktudKGVBlSP(ZindOzyW&A^GQ0di+v~{el#9$F&47ECnLnb( zMi>!Mo-XS9s}+UI=3iERiICy|SKJ)KaO&da8cyvKE_9j{dopbI@g$4IBAlM~L;yIR z74TXzqf?{|0lj>wco67f4!FnmEdy=!XSVmYOVll&YUHddJ9vhwbwYwU-Uwf@Nw=sI z)Iz1l#~B+7|E8%zDRZmnB1(6)umv)>S~UA5ssiVUMSd3>tI7T4CFlgO#7{SW&kenH zvIsd+FZ7_fDLbP`Yj6NQ1l$tGkS&+&1r=r~ifaN)0avBqS~A!IOMrwedR3YO#LI`^ z4SR*7B&A`7XNy{A%O#OCN6;DC-Kak;J9JL{lfQ|3=9E9KpeNxJY-x5J0b>T-wgqm3 zHp5I?kjI3bEHuUzCIT%ibON-SFGfc%7*#-^g#P1nW31VuqN8_H9mkgXk;j{<3`l4F zW48BL`(qrKpoRabf?F&2uxs%c<)(e*96! z!#7mJP9?~J&;zwGqYZhywXvno^b}`Y;0j|n!r!%PsLvW>Y>ZZn6n~I6I<>c!DVAj~ zwUb|!NZv^N5;UdOw67ONq{EUe+C$`H2(oIP%|wJ^7Z54Qf|QV-5~9(uyJ@jO`|?b9 zD{R{YOzI5i0>*mA{y3vRj=(T5=(@ltzYTra;(a0NW1uf4B~u9ub%LIuv(&RjEj4}7 zhSvqW^^lM@cz`evso+ovSt#t)Y!%W-hFxgR;T{|qN!Y>llTcGC(b3WQHYf^_m^f2^ zT9O$1jHP;PUg>EkorxF|9wulsM3OfgUrS0)Py~a1sUUJL%#Yq+!o@;+Xfi~vrXKyM zQ`pLh`lK!fV!;%MqHUlYxH5=gM4$%Ak7iA)loJntCT#gZRO+o<;&k|GA68JIAR%O0 z!u1Foc)nA|yqcU8a;B4*2t7zk8r3xNPTNRYw4uX+s&$|z+XN!>B-09q_d6(&_9;n& z@p2h?UnO@cNRv@8$xqSw(u^6K{_4`>?X~aC7U4h1$V8RQBzM}LYEr9BYNbiZ*;*Q8E0sWVMVBNECrsZ&krOq0?GhjLBoRFgW>q(m{H zT$2)IdEOaMWnYQoF`XP@MrLG2rZI*bC2{rp?01W8`y&r%`+Aoz^eW$qmY>jN7Q$&8 zw#{U*Eu^xOI6l~{_gfJ2FxnDgp6okejNUkW6?>y$Cz!n9T;_rKgCXC9pJ;i(CqYF^M>4Q?a6c5+S`nc7<`{Mm`JcJ{n-}g4Y|)7CZFZJVbcZA z8%#b7&KuZ|icQDqF@y7lZ)ab^9B6pnU_$P^^9J=rM)J1rwRr=v|2Urd^9G}(ytH}4 z~@U3gr5JtwP zvf3#cpW7*FR!xklf~iDZvTAT8uCZ#Es<`KBtyP1sS5^&&oVX8^)6-`;!4F+lxDT{j z$bmKOZcGe7Rdk^~Q0$TcALy1~TXlmEbVD(1E|YQFzbOClR?+obv6Vygx9lq~;YsGq zCB>Y6w5_q^oArF4@&f~P_BOdXF>MPl$dUH)hT6gOm*F-Xzs0}j(Q`QMF3^zhN@_zQ z8*)HG614+TULs%I7;r$^(njNe#2qOIBpP|Z0cnhhM_2eX57l>^A;IiT&Q#;hrY|O4 z8bgAfX_iLMh8>X4ks*<-=YSMj=w<;qASch|fYhdQA0o|PMjeowpQQsbb3%ux&S6OS zEI1ev(Yg-E5kmrmngbFC>8Jw|DqqI|X@&$}t?PjFqg;TmF(ee4BR7Q{BBKt-3o#^q z`&(s5tb8{a5(GP3*oy4;4T)mNkRVKAWL@@q)Q}+H=mHFhL(gnSz!T@OBUZj|M?A9~ z;p-X&lpSjvX=C{SU@?C64aLLv8&4_QtjQB>(J|^aq^*QV;;cAluhPp1aAlys- z=`xma&BT%|XufNQ8zXEfh+PdhI}$se7Vobuvobwc@CafeocP8(A!Kr9EoE7Ks*kL- zbr%la>F*NKs%yWkbq)w@wghc&Vw5>8&Rb|Mo{*K*G!v^B>n5bsz+<7iKi($?7&bebJc%^9J5);}XO=H?O=zNPH? zQIvK%r~)^bV#RZ^dv!XfE*wqShcK9c+Wq8su zP9NqnI-8V^4YkWyQAffNm^#!@%X_Tn`21Hbx9WCP$m57;FdWWl%m3MXTO6@S3?NH! z@pd&vC-X94f~6}A_>2^hQ@VtPH=K6}c$$FdjgG9fwMW9B@}IncNQLBxTmW86eSW{0 z+!T`o!y>JSNJvmMIrNxSF+gX_3BhI)rR+wHksJl5yPD6Sj9!cajd?Wsnt-P zHPfiqDYYgEZY4Z)n2kVYt0134FIt7NNh!(5l6(YuF}Vibu{N>;wct5Zf4?Qr!l!5UG|CViY@D}_fVs@OIjYG0Ia9A@mWM@WkBdB9+U zu^L>e(FtQZV*TXe^wVR$*2O($_1Z}@Pj-777?ushA2I4w(bCE_eJ?okN;o7n?GZ0_ zXo7?gaWr;SxmRP?1jo!^DJ*JY(#Ne~oY|`BEiM$BQbzFnSTCj}aW!tnV#2&XYM^}! zfLojD0{4}MNh$@$!81A3dW-xu#D%E{gDBc+DYmB2`M^S%LVC!idnu0z|FL{+Eizc@ zn;m1#_)&;Gdw!_34*cQ2*OhG@VQd@EOo#+zhRcy6?G&s)JwZMYY4*`1Y~9Qy-c_Y4 zoirZeXp=h+LNovqh6IxGfv*bq0OU~?me-HY>7rGAtp4i@V}JFUA_@8bj){>?U&I(b+I5t275+S4Uz|c+8)F5KgU`ect%!5^&A^(MBAss6Ug)<)NB|?TFBqoopGt(z} zQ>R&p)>zI*@4a4gF+(2we17C9)GQzmCKRfqo{VS%=7du)hwu%@tc$d2%u!O32Lzf#(svyR3%f4Q zx5-Shm0D2Y>l;Qu=T62e1`7xw9YMCH`{aYIb-GVgtj%)`nDJHJeWxAROx>3h>|Z!M z*3gbF%+swsfp!$Y-gMutDhcyGeQtz9sIU_b)!HfZFnM#WH4v)%fpF*^s8IKPYpBS+ zP`C1B4#!(dK@BPWs_YIU9gV%SkS@P;l%)EBVa$*kiC=haEn^YPl*h2MTFtjS_SfzylK>jh{o^)PQl?8aWgnZ>{pI;#&FW)sle9tsn}pwB-3 z$}*Mu&x;Fv=s z%OBUE4~Z-Eh0$f$!Cbe&T(X{#VFJ02IiW16(tUqw&ler6a>6k0*Ud$VlWL5x8mgy?7P^rFA)+P2mgP?&T+H+$o z%03g@^ik9i%oCED z`niK$1e{J_tOJt635*jDBEi5t$FJGZ42u8;{zVSsb{QCHx9o|V6+UG^Fqp=B;c5ZM z!*3Oku%n0L>NGcgZp(hJ;pfR&#%{;N8Op@`n1#|NOd@Tcw%7sK@cQb3LP}0}K#?$PlW1mU( zZ)69DXBJ9R1vI=JdgW(cte=QwI*czA=vtZfcrV+ZkpSrW!oZOW`?xMpSNk>ms|r;s zmG@y$?b`uA+rvq~LJY^=mVR+2h+L4ibm72@$r?c_#s4q#20WiVzd2af+e>IieRJGe zD|V!%kmfR#5;Y0uP^eAV`v?u4h`keHnpXDro5~I6!<9>B2KE)|U~G-Pbzw#WrhpYE zdIL;?4vyb%;DM-!7hGN=Q%2W_J@_;xJ z3V=w^aJwp z594;LugN_1C#FH;>O=2ge^$gu;j*$?^Y`6|*RI+g9wc6>d4KpY97Tg79l1}rr|2ue zfnF4L@c|f8G^pfra=KjKG#+)-oog>^SD|W=iDH7o zrst9zAu9NXo)wi+^Dqk)W#X*R^_j}u3HA9B4adecB*hpMYzl)drLblTla>fZKjz;w z2VDYqu?3<|b7M&-O6mh!BnWE=jK62{N7&VN_rlkCwK)`Z7>GdrGS=Y}t*UstjWP>W$6nPFQ|TBmytCduP!5rB41WR& zZAlcUxyYJRPZ1kKo1bAi#ZlswQXHlEgcvl97qc~;fgqj9AJ*(1vd#8()8AjT%$H{) zVg`;*@z?CchpVIELcjaX5Lg5)yC(qb*%gXShRp}-P4JQ|dLth^k*T5%T?IZtCnmTt zMUJ^HO9^A*&!TKy`A`t2l0=1|r;)Ea^w4gV<@}gCv4uS(tp-x}{Gm=_a^ir6+4ARZthwu?U zZs+IIPs#$!{*w#^pM|=`t#*?9o*T>;Vyx(H2D3c%EOT2d-e#Gc#w6BY8^N|CfB%i) ziyfL90!SA@m#Rsde{Gl67TavEVqsMyXXs-Tn2L^kuy(dc>r6J8?mBRBXly$ZZr1~v ziiRG#bTpKC?Sj?39j)wi8jts<-V%4oqUu891Cn8Y2W|oi4P1+#zz@{tT@?-FqW5Ga zjq5_xZ3dOxV-qUT)#P`VXvX^=3=->fy#!-|P0BQpg>9Zj&?3S>!fQ(=m<@Y%$KLxSZrJJv zfPn_gDw>>#yodSvc+hqKJFXe1mwT|K>VzHWil0fkXLL8u4~LNHG$JNTseU+h z!)HeJBk?RW$*e>)KXQYq6`Q4)gva9WS%dMFz)LZanfv@|lEg>SyOa&73@iz>nd>te zQfrJ({?j)a$yRs0Iumh;7%+c~neB*`FvH9_gJFhBpy};pi!Id(9NWvzOn0NE)*T@y@`wN^*{^AzS97OYQvVw zY0+#V7e*{v1mU3?_yG}802)K996Xp)Uj%Er+EXkvmKt`rmD@-mJ@e?uH{I>vZu1&F;IL?B&kh;tV3g*vg~0Tri8XGv7j2v-B)>xg+I+%F;)zX#Br$+ zI+6dvqEV(tC+Q`#Ga)N1NhE(*%hq{%2L_k>o-9J1bv0!)4CzY$cEqZ}lQ&;x?zFGTtp>ng>&dlU zbX4{n#j>iM|1!PT`dEz^u(7sd0@g5u*hUO^9|w^nu3Jm}P9s1s_b~9hnx{p3;!SAYT&=4s3qxh<*Sqe-v*bXjt zHCNcl>Acjd9e6&$aI9{V)uWYq^)q4+wE7tZ(m?-Hd##4Jcdd-T8?2y9AQLS(qx<}h z?kM|m>}Jv9gy12l7?^xt{yCOt1c4lJeBqb)GQd6z$^M7)-(RTCm>Z3%@;~G}AVv_w z3{8#}`8>BNGWfw3DrgldF!3rl6h)dWdI*K}60!gC=uPy8rzQQt<+m&$oyh%<0mmgu zQu5WkUzyH7yI@0Dq`~##!+l+{WP{QLFLlfM)*@D0)#H)$*pK=Go!f(IXV8ceWHu6v zf-M$A%?h~;9lD?n74GY^YN=q|b-@4iThff^?SqQsavjrV=tMx$&$Wm^de>c&-u07} z7*027nNU`-yvKDJtqZcgtPE7MQTu`xcsg?uu-5&^!W^5|JM~&laZ2w5MbM#m0TPg? zM`CN*$WI8^ga{<~;4(<;y1njDQoT{~zfn6ah17ky0#<;8u z7?5*GSlzd9Fi^rEG^gymaf%+fmEmk4ZjhDHoSj4Ed|_6G?9#@{(6WoH46}Ex=hQ4q z(f+jn_0U&*aoCJiVm=%^Ctg!v;XY% zk=f7BSq_u>r#JfZDip0xxdRPiHY2op(tZBlWw_BgWNAbQD~kw2{{uFJt9$>W<4V*B zQ3OKSO-kQgpX}-5;GUX3VBl8%>+ny&PGF#*wfeV@{Y_z{levLpEy4weKO3Tjp48uYuU`<6*^v-A547xaMq|ZBg;%EGfA-sHf27mc&oM) z4wDBIPGV-Do#UnbGt$9QIL&gpi9Sa=+RzXbrMAgTA3XaWbGUaMMmJgv6#1cCZtDFJW>glJz;{sVnzZSU4Kf^|l{Z1yyJsc170!>nC*S4Y ztdG4Hx;YxWC>X6wD`d|(Nd%WqHh&?dWOM#43MPEc6g+qVcZA<-!|zT4PRtRMweS{a zx8poAPlyMB`Amup1CyMloH8{ZiRR*7Dy*f!&E?SrDf^c6Qv z#@RQrWSSP~A-)_j3Jiy>4^DB9SyV&UW)~gJE@Y2(mh*pV!9^r3-bT)7XOr#qOr?_R zQ%12xE@7%E;=kk0cz#m&k8`ViYR*!GyT`RJLy@aGW|hDADs7|2xF+pfY@|uM){8Kc z#nza$)dO{aKh*M#^Z*Ah;xR=W@JUCjnUg}vPWgCn$k3~Dqk`?*TD{=K4vu{0=i1&T zHTRD465aGIQ@Y5?oF>YwxI|mET1B>>4sI-JpQe_p6oENSm3-P?B(Y~jEcwet{FbV^ zj&HG#=!MP>OmAoE&9}GXT-KS-V3X>$fw206`n#zj`yuM%4r!ZYB+DH79p2SN+D#Ew z*jN;r!zVCOgfo*>3my^m9unA(3}%wDp8;^lLW2g+kG^EIHTh?lme?M}$`9X_ETK=S8r@LOAu|=n{-%rk?SStwUk>A|!j{7{ z3|Mc%1Sj?hJ6%=DIfPu9tyICpoumA;CZ&0DlOw&ND0vQeQAuzXNl!)|l}i*nFLcDY?iH^k7W)6GHkdIEPhB78K~z*e!D|a8X7BagIX^VK}lhVv4=MPnYR5 z@IgR&a=xg9ZMpGbX13PfjKt&~mY4EIt$Cn~_W9br&0sH8Pkh;<5Rb>M)x&(6ZgHSf zqx|oU@x`Ih)!p0s*@ybMd)%(CUXfF*Q8hxRtRxmi2*vqbZY|!!_`Vn z-l+;gQB~tQdP#@*!&M3pac4R%9k0w7j_J4V=o+(Qt?@Gf174cS&oed>vn`Nj(M$|m zG>j%Yin#++FMAVxbA|=`^-Ic@ksldS{)1lH;~c^Gop|}qpm@8u`RK+u?3(fO*)?c1 zk(R7_q_@^Pz_#tO9kFZ1O^Ewh(8j-}YouxUZ%K9y>X`J9T@xhxs9j?_!mw+`v1?vD zXxF^>S=%)t)oXh_{m4e?GUw18^7i{^_kKydD_uQge)yr;m zjI(xIn1B{Nfuh9geXkZUK#_I>5t)JwG$!G}qqYUfrVEH%wVk_GM>Tm+1!PPLyU^Nv z@-T(xlZQC3&WFY;!OB!6m>E7A4~I&0!+kJEn;O);`Q+p0so}9u!^a0|pl5iDj`F=Q zuo9aAXR@MRpA!%QA?yOYE?H5ItQ!Tj@FgfRdvPn-7!V@-MXZDl z%BH@w?u5|W*wjsL!L5UIoB!-Y9Hv00YVfD>k}E* zg#gY@op(UPusm>;<5gRiB4eJvLx1ipFOv;BE>KkwxA8>sA97PoxB74Vmi)3s;R=3c z_w}yihdAafO244k!Yre=o$unM-X$hpG*U1{xk+QG7AIu`GxNdToWzWsV{du6fl$I8 zrZW*eak1eL=RC?p?rlaqO`)vE%&y}&v=PxXW+ro_!izGQZSShuFgrWgmZ@xI8F2h; zEqZ!A;U=?O?+#QA8+WPv5HkhV_}q$IF0xURnS**l5$nR%bxmd~a*5UjoPDMwtJR|k z4LCM4)wlLbP8W0gdjwP3&K^!56d=Wrk<61qLIZ?)iA7#NuC<`67Jc>0U#b?t|3W`V z<+^IZiXmOe@ChMm8My`aQQiG1$@=)U`N}YU!$($|Dy25zmv|CrS`8U)sE*-WTJ3?Hcy@JLh3ezOOyvDPOWdQy=UQ$0Ief))Yc$ZP1Fg1-*ZWr<;lNGM^_tCWSPu)mIc+CIrI`PkLd}r^)OlRYzvJiD=1SN85HS@PFu`mpD zF~$Y=MfS)$%XySba#u0`Ag@?objvpXts9H^-#GaT9#1>GZ(n0Vx-;yrh3JHwTrfs45I>F%a0K+d-OpWRRx zaNX)@+RY?g2nl@=gAWODW(O5W( za}5=%55?s+&){?J0aBLDj3?`=I9BBvHWt#^0l8o{oXG_~>rm8162lWhI2qdsjV(6G z=u}yEFr|kM_Bf0p6;vsg8)1x@8dDRhF#v+02OpSl(GZDdrrthUBSJgU=2KT#2K)r0 z5~?t(A5uh%^d|UERrV=SG^bjJDbc3d-!vt%biO_%J}46edj;{y^cq^*3hk9IevqlT zuEyFYVtet05Zi+grz8ra#+fT#bSF+*=OLWdX2q=R*Xa=>>1V^eP>_m1P z;?@BN)~&#k&8m88-m&W)ow=brPO@3FuA?QehknWe^Q_W20W zoY`f_f@faw3c6V;n-Ne-7fH~Kq$WZJ${THLaih1q(P)* zE(}N(2A%7oV?r}Eb&Qc(Q#nBVnq0C%n!>hF#0#)iP0gq&+dFt#HRanD`#qW0yd9rd zTd*8VEL|w%O9ZB~H`#U!0%L5T%Yd$C%7@06`(26tyUY&QvERs^FDUjTC?o(WDI~Xn zLTWKp0;!T7ax@tH!v172%NTW!?K~rSX{*?`#2mDWy@GcO5@_qg*_A#WVzMw(hQ_LM zVwiy!zToDYXgJo@<^l|s+&e|fzw%CLamtZ*ij11@PJuhfy5rs{UTx3Twuc;cOZeIr z%V%90hgP)A=vgaTftzd1N~3Jy+RD~6HYJzwy6a% zYcJaZY_=G9fx!x~P0P8smxjydoHVQFoIR`OoMEfyoNBA*q#v)IKUjNC-tg-A{nhj0 znb-vz|ITD|7`;_eHsPdqSh6=}S7+r|^N#ZZr8F8}$vTbWyoL2Ik12$09sMC14x~J5 z>sY6chB3tzVhI`zBf%sxS;*SV3J%eY)#r4W$0q#Cz!`Ag?{!k&og^&BgOK_W;^OFx_t2bzE&M*_?o?MtW<&? zXJR9&!LRmj(5UTWzaik__Cj(a0K-(=P{c#_<)rtPBx$v8xaLoX;N*%Mkm<&YKmH`K~WZE`?mKHO16*18_mY)<7SikM`jr?!Bv<%$%5&>q9UpmbgSt-_TwO#s~_e0hyDGWDO=& z(X(a#q;K>B$x_e#0Cd{Ze`BtRB(x0n#U>%a7kd&1XfUL4ryx4is->5}yL{57E?{DlEbmy4+R+vHGX76g z48}`p8fLKH=j282<#yw(qcS&_i9>KGrHs=a>{GcYZ{n?_b2R8Z3TZX$Xm`WDTBh9% z`0~NihPG^2tExprDuM5UU$TwYiR(yNXI$3F;Z&7XP9FA)k=Y!CenLEM)ao^05An;B zd@uHXLWQZb0kPJfg?!6B`D`|OTt9%qwP}`Lbq`sZ=Z+uKFPb5^SHA6@d~3G#__2Kc zw%O=l%N5s#fT7hf zALj5iw*R$Wt_zD?XFh3A;?bEe4re}z<2m!m`zjef^MP9QTi{a_wl#3{(W0#4-|!im zvCe!RmqGND#a(Y!9zPeW+2dJkXFiT6vj{Lq~cEkNB;Qn zV<(q}F}R}L2%qXK-V?Ee9iE_R*iYa^)>P_5AEoxKvq)YT2cH+hV;J((&QgT=ob;jZ zoEz)Se}5jmVbNTq-uw@7fDJZ;;YCZ17mzDBj3x?&7Se^XZN0=+0qd^0ZM~yGr#t9?q@OM`R4QdFo+IXz5PLS03XaY z77U1Q4A-+87|UvzT@~(Wl@vi|WCRJY2tyMj{2!Od2-!^6gLI&@M}F@hnfF|v2r*{N zfQLicjqq%kx##l|P=GRaq(!PEu3xVGGZHr~WHaKvGrtV{f+>a`!bfnzPi3TJr(Zt| z+vjqVRZvUpcbRd5OX=Kz;irNlaEvi>MQnttkWWC_r3}Q-7h?tI;AxtZz^}iI`Z;Rh zmk!5(MwPQ`>n=UX5X2D#C_GtuT+bFb&zat}fl%}=w4*P&%~!(f^R^pFGeqtmgPBYp zJD$Dh#7Ry;S0fbgd-1|%wsGZ96a!}3#L80bG>8Jk zDIrVoxTYa5&au~wF4{WDA{OytJXB}If0`XKym43LzzTGiy{D3QdO12IuDvT_=1)c? zrHkQwvXe_sq8iW?11ZB)?wZ`~vS%IcGM@**TQQZb($=y%TSpc^CnRNNqFZ)QCP-oG z6Yt}zKVf=W0x1w_-C6O}Ayg12f!PLwFdG+#YD-XU-9#4!{2lCMy1MTv$WZ~6JohaJ zD!ps~N?ul=)Jq;Vp|=J~1?|?9R0Wt4z06D~ZMXD7a=5l&;9oi@t)ylxZ|T5{97qQp z?AQ^QQ8!pbf(T#Nv8;$N2W9ok1W6VdT~l003dJy0HRUri9!UigfX!0QQxi}$^s+96 zf(cnAh0+s-N;yfPOvQnQKS-f~(`qSH-tcpR+4V|M+rcsd!c~c)g8C?kqB#Ai?wvUT zjExDSS{!AAF`+fZkY{OzBkq>0#R(gjF>+Bo0|Z^P^9OXSl{d{H!KCXhp|Qn1$QuDp zN;pGDvfeE+pJ8e!kB5OgGnj}bVjUZBCNJP7ZKV=c1wD#&#>S$M1v8i-8JGcD8paM3 zTnvXJv6Zv%69T*JOBZ*~BqV$9gK@tJhOMBo_IksxfKd z?ST|RG_%gK@Q-r0n=mmTQ-^Q)sL$#$0i>Y;pIJ@-u%`YQdjNFA*0nM;@ELkt|IGTw zXBMp_9sf*7qtC)n`Hb)d{+Wpu`K+mx%fha=hBOsGBcVM4VdCWVRE?gW2%N z3dr^!x9AC5kG~Q;=8H(8diNJ#ixSB;d=og>R-jTWQgq*x4J*)K#h|Ts7|qnbPix;R z%!_m_5>1mJDFE5~FPj~zuz_&O&37cOU{m*~|4(7Ffc?}ofe4EicbXhwQZuoBPxVddiATE(C!=*Y|bCzPq z@BwRw7i>O3dPi;J(mNCJO^{7$gHISDc2#H1vWFEz_37xtJPY{3vPe4vgoS zW}>OcfS`ej#;g_EY>7&HMMo4%Du{%~>d=pPwMf{yKG5*XL6yUY1q5EgTr<~wwVzcc z4c}0j3@mlwWm%gcv@us%*DnT)EYo%zB$}0!)cR41J;8hivrU4$(2rC)%vU4-vS|Q# z0siGQoUI=XsC^mDNkKVEd*&d4t@X%dtfWN>GSz)~?;f%>^K-!jr;_1qIew2@mT+(4 z)GVV=4>>6Ir$|I4ch#Rmc^}~`;=ZQLP`F3gaQq&`|8s~1w^GSGvchA~MU!z2egoO= zLh@S0<#L@*KkFYn@V2~nEbDzQ;?>#ehXjaoqhG^7+;)l~>YXg!gk2n}2J$u$1LiC5 zJJ^0p-aBD^&s-4ummkaNdAXk3>?O;t(=X-~n<=I^iLs1Sq}U-%fo$&+7Y$HdK2Qv(BCZ zId|Wih?1 zB5dhp13`xIP>!9rY;f2y#4-&cd%c>{cBEsQFDF?p!+goRvorDo4NGkux6h6oTG@i} z{)FpnrKT`eeIU6`k)dk)g~^^*<}WKRP>wzkoq!@#R(3$t?7-aZ)7Wz~zpQ-mY;$On z;H|C~dK~KQ6E6UsPbLQ>%TL^}x4t)}WKqa@can~IBcDO&u)S(Zp z_oa5|dlB549aY{B6Svx-??t>?8ziGLCA<6%eOH4{6CCZYNMm=oE>fF!Y$ATavWob#HW=EQN{BZL)5J^L}D63&1)H= zHC}w#90CF4DCWnfBZmyqId3dOD#QqyXGeSu%&k00ob4(fikgVm;(0JA<7faNG*c#! zKlp@NBnW?GpbqY0GLQF~JRd*FXuh!Tn zdevz@+i$kb$z#GlG%gxhoRs(Z0G=gGcI!!K>GI^+)s;cK3N z!>@q0xZ9sCe|gmn0P|$8Iy`?_#g$i`tuV8}hm+f=SDk-k&gvJ-t4>WYj?3UxSI5N7 z@Tz+;XO?v^agA5qS0L$fuR1N=E5@si@CNcoE4}LGu8WY$Z@{Z=j*VvXyz1t_2BWZ0g;^v&a>cW85dex!OQDqnGRX1=ERv3R%G9gw zw5OVqUP)f4pbzBG)G9pDmN+u(49ex zZJwvn2Pb$cahM*;c4C6&3Ka$un!x~>w$GGL#LuzgskCvN_bl>6sm;?_n@}y%TiJ6( zHPL|!Pad}h^S`Qet!ER)Ct{Q^hbw)Ts$)7@_|kW&^2Zm3Sf_0CXe4 zn@M9!h#5jZ(6r#$WOoXzp|*6vcgj6c6pwVR?-IfQX~rkm2QUtlKtZjJH{y+^Nw5(U z^$efpthL5>sT`DK45}6#v@Ynab;$&qjn!Fc<VfJ#){1irZLf#!L(hJP7cR~+f45oPG=rPx;z~;?G(o16Vs*$ z)0m*!X+w6qHS_86h*ExMT5>Gi!$HS_G{_pq4m$G3(V62AeJVpc*n@pEM8FaqfYK1W zA@s@)lkk#K;3@mT1oP?C&8O3m`E+A~`Se&FItHP1l)>aEqJ|E)%5-37Rl$z&nkA9- zJvM8MHK54iSB;FR%d=HAUO{h_F?Agn?x|K9QyU~QQ(oE;E0v?!(=W4L(KmBj3%x3# zJ}i4Bw>aaRJvfwl6-=u~C%&>*Hm9q4RaMBTTuI7vI~&T#b!1v?{lm1DxeEO&O{-Ix zR`VyAR&%$YpNnfvt9iQ0w3;V-RJby&#?1PRrq!twi-X^PnXEIvL`AH~R(~eh z92@EjvXQpnGlr*b4UG|i*#@jKl9p`%5JxH!@F;gXXp1?&45Ii_!YF~@H1P(EptGnR z_9ir{6)CTXU8&n3TpH2x6WTnJd{Wd9ruNMHKZJ= zieew-WOYWVU|B5K>Ez=?{_aD0Th5F1=vB-u90snhECnkgkF`cY=z4_1gFmv+e1yM~ zk0W$#@c1G$40kRJrL+R%2q4*u$c`;5MbnE1zer*&`hANW1hrGbb2WPX2;yh2|LqOE z?~daUgeXHwE_*5bF<|}D4XV5|lqk<*97%{C14>*rGKLz<-pt=&w?(5*&_jKp&Dp;& za9h${??e&zo@~YKko#7gAYkcZs=$T8*Jgq?$Ot_)6Wbv0HWNv0CX(7rBx`5F(uO7K zMmx-eg&@K(?@->+Oa%W!O<3YH(bh~Lf@Ur-clVhP49IQflaZ8@F(6Qq=7YDpqaD?J z3~WA9vbrI{whOMotlZ=X23k1w#D~?Nz(Kuir&ct66j23dUm&6=^B&bnGY(HU+_`#49cq;hZg(s zVM4?8u3WS6_A*;vkyccsRYV6MPAp)8YF1gJ8@_T32w;SF!X4{lL8XF#6hq|`%M*y#?ntt%zNMDp5*-rYt1@0@ zrdhJQqR+j{@-&tO_g2;1KpE)16lDzaMlR34hoVw#@%T1=e-1AHffrrFOqq-QrgiBv z@858jKJ|e!cIoQ@5~~3;*lr})-F5Xl3%_?2WwOUj%JaBQ%0~`jW1&>pR1%P5JkR5w zHj};whs_8~NvPCGOiexM;fxQ3Ak?fs&kjRs2O^k@KEu}Etv?nZg{L8m@J^hxA|@W& zZjE|4pNbUV9yTYsVidubjeELru46%r*)-Yj;PA=IMbX<*;aGkogD4_SlE7sk|2bv# zqo^V#lqJLBBvhX%dhus-M>nj(S0`{GI+=9A6tPoI=^qzrC@W%#sgy@f0BJGz7UE;> zEjD5cR^KpuEk-MIEr!Ty$1b$A=(b-~mKJ|mS%0rJ_s(WmMMR@Pb&l=4xtD2K5frkW zZB>$sHYalYdE4xc{NdF7=P!e}$f-}d^X)Pgv%8&;Sz{6)7D;LFyoq&7_(tS$$hR?b zcomqN_BhH&J|s@s-PZyHCCwi>WsSx=VVuIJ)k7^unP|KkB^%mXZlY931on5$xWm@3 zxQCD{?N`lZr3kS=h^P@BzgMYOh>oE5O){q0OL2Aj)%4fUxKWUWdRPS}P*qwS6tytK zOaSawkVV2tDlktG4;iRTgB1!P;a0*VL`giFfn_uT&9Bi@3rl2n_%R>Fhr}f{vHuI7 z;-d}3_!NWltbIp5vz&LA!D(S4P2{Z|-158u%E75Xk||C-99kG1Q=d^n?gy6VL&Nrf zvBt@=uF6gW!q8!sgLe;vUe#d=VV4s+t9u1A?m5$|)l91fAlSJ(a%jfPGRuPDSWihR zim$^R95m@nZj3-mU#aGZ!5*=m5|3)_yg}#ONDd~%Kd2a!ra4@A^dA;oQ$`oS@m4AU z=7)C7YO2&fexj8Y^Ty`R+$=xtHw{AtVF14c3gUeDU9_xdHmb#=1o2qM0s|LY7IkP} z4fRn?mJnsnGS3K`cwJW1u(&&{ zUpIO>ZUkqM#;jcR+1~M=3-zCb-oAW|Yg}i|kGLCICNsr&&nZ75p*UvjRNqJ!A;YrH z1*NZ){D`mt1W_Jm1HeI@KPF$-Av~D>9?fL`< zX8nFt2j^OjKzm4zK)b%1bp~#>SLFp{lTx}nyr3-hlwcK719lWTf}hIjr|{?|0KY!* z)PPovrv~hdQUg-eV-&X3fFT>TS7NCF6$C9*;;8}K#hTQBe2b$be@WPI#v0s{uw9I$ z284b01b>3YN^I1ARcb(Y@AK4vn1ZeN*uW1@4M>1Bk}i-ZO1M*|2E31GFI8$l7N#%N zTDtAYsR66ezJt?>&B5#aa=Tw%>zCX7a)_xZbt_hQpFhWTjA;|xywej?1CHy z4G8m>Lw%mH41sNPDEunAuuB;N#kUM!!lld*xXUvHeznoukRecELkM*=$`CkfR)NNy z&6+rP_KX<T3#!*6FAE_C}&JV%&Y6i1%AjBCC7rV1%2=s--;chHLV4oG!b7qqafoWhiL-2-7%x0er(@v!J zYmC{fB@A54Y~ov}Xgd-rPs$Jo51|Z!019?P(C6fDfh&}eaUJ-MG6Yue4If!;;)*u^ov(Z8__fqf;Q zuQCMo+4|Sd5U6T!RxB}Tb%wz38fHi%t$Z1w1B$c3TRK?tWrV^giW(aH-pTkRcJLqp7 zWcE`T0{uoA0@3}!QF(?y)Iz~8u#J{h*)KX$@1?}RyjDRvk7w7O zUj+rVuM0_!jm)bqi2kGufowx7h>1tOjPzTN+$*}Qidng?mLbsTf>Mo{?5jL)I!Vg4 z(Ku7m@l*@uF=Pj2c39+5N>NYedSnPBM0)UyvCJd3n^hSCy(6$XysOf;ivFtVPjQuxVg;UvJyEHkljJLXk;R!oFe z%ByxQCYHX8Dg7R02wXVHiKEI8Xz3`=hHNJ}Ys)taV0C~`nIW*_83K9g83M6NH)PCa z9T`_;2sBWujOFDL!gn=M94sSqm5Xsv$P~%=;*?URG23+H_8y$ zr=;?U)G`Fx4>QsC41r|+sAUNJDuDR4WrjesL?T{*wQ9;T1lr!g)2b;Q`{1BZf0P*l zePSUuDY^W{G6c3am?4na81rQ`RQ1T1w0}eZDe;oqNf`o5dN}6GC>P|+G6cd0ky8hT zT+>kOB&rc>!`YQtmKl>Zo*}TU41vbB=-9g|RbQ&P8d+ScTF(&J+i-@!H3B#``z{@U zR-7S2;KgfpccpQCvy+H2|3q_l>DnXDiC z5SRsGrjHIFP0JE?EvjW0Z4M+R{NtqAUNS^S5uEiT$ZkoHty2tw>{cbnR*G;b988dH zcP8RR-5^=BcxQS>N7io8-ksOk`q27fLlptQ*j(M8dAn$-?rp zohgiUEi6Azlst1REI-c%bi!iV8;i-2WNQe^|C)TpGyJhexX0(r!t!q=EPtEyPzuYh zd*s?4M_>7k+h-2TUoVg$Ag}kY3d_$iM%E>=+Cf^3>fo$l`ER4tSXh2OlaN@I7y#0m zm&t8o50`szVU02@|7}FiV2*};Q;CyBvKE%VVJ0Y9N2h7vNEr*uuN+7n41sS~r096?N_5{$GS4rCdrCWNzCnf0YrOg7MvmoFpuhIBP zr^<;EW}AT4M%Oc*z?iyn{#g98TI*Xm2m6!ay; zJE_ zg05*ZDO$yIuN6%d&z+*g-%(N9j3`>gaIY2h80ZkYBT~B20ZZ92vy{!;RW4WiQ3Y@J zSAjtJEA^bHNgjONLVEeaw&3j=BgCgCwou0sjnZaZJLIhyqq1%x0H}YTy7tJ5l-eWK zf)s%4i~7(zzJ-+_l{OQDx3dm>1lbJ1+l|b#yBh_UD?rw(1#j<`!Q21jT#XO9z=G1?x<=V!bytYNryVR=oDXeY_08q)TtJGw~-By3bSBa z3|5SkCJJudbiE^SaAk?$FDvOV)J|JEW2vM>UDo3Z#P(oD2)WkwKy-d&doUVrU`^{b z78|oY=)y3vMgp@L)UUt(KzRbX4C>d*(zw{>KiqL<4dV_MaZ48`PH?JWv_PTk@?wwg z3s5;n4I`X6U)D8@Jh>NEAS$F`tYVc;(lAbrReJd}j7H&M8?}@0=V)%RO3l1XOLq8` z42tcn)G)pnz$s+uL=B^bEZvn^$kL!;9EB`}w;gF1&naYS({zjTY?LK^4*E%=a=4Jb z3R&98hT2Ke^+T3Q%?S0~c*s&hq@YA>Y0F{L*QTW-Q!QjE!9#+^kr?vg0!ejM^>lT} zQntrz#v_-aU4$^&K4SZt?IShK;rOoN`^Xt=*v=BN)D(^;GN|pJ2)sSwXt{+3UE?(% zw{1})y&-HhfDkCm2~hqjCaVr1OUEl9WGNMRLvruq?Rp_g(Liv(y%THkJhc?nr~9bQWM6)Yg8diIVwm# zYwN7fj9FEn?scx_vyi2$)c=?h)>u2K`OIh$HXjZT+A=}Sho@K8M9l~O*(yVBA_O!w zUlp>{JCf1~Q6LBz%&e*TX3wVP6OoYbR~U<(dq72dEOZ>hL=s%7m#Z*Q_<7-&i1Fi@ z)qJqUoPe6v{ns3(CeEmwzS4TqwwMws6^I>f@3R&98EMzGr zp8(L+e9W{RClGaZQL=$0c}sTIb8Iy8CImelAI@6H>-eNAY3(v^AOh0%NlJ=Q2pr^o3%V#|sgce^$Uu3CeQLTlQ}YE{0QIP;`MQ)8NvqU+VL)SQKAKW4 zBWr4{nom-m7HzslG1VogeMU{0;#yiWPMtX`~(d zooL#EVjDRJHJ^uY{)PR?VwSN%NIRdEYCat;QS+$<)O=bW9pfCNabvPj^Fd>|S8B#= zwVH26vNf>=oY661)yb|3tfXpP&DWHgkHcs-GagvUtJRTGRp^X|&_oBlk%A(a3qULC zYQC{%aUi^C&8|`NL8I1D^Ic`Zb)@r`by{|mvPE_-)^I8YROQnZW z49F`>4;B24*65+uJ{O$6Tr@Qn4-6_b)zp;Af^@2>nQCfuKCe_crhmbdVXCPyoyJsC za}hK(r{knixrsKh3_t=P#Y+ftfb0z!29G#1il0R)WKsu2QJ8?px`QJK`syG{M_Z?} zcvEH`SVPLpqgk1Gn7<;Lqu{T|ott@hgCYFJBrny}LvP5?))bG{q?=F;XnfS9iSJjn zS~iH}sBBQ;`(`P=|L@R!4L%IlAmeLUHIQ3$H#$X;m=hX8!6uXR#No17^=*#It;2F>XCk(8dFvzd-w;LLj8$e@e6iNyzqx1~R%e+APLcpUglmuso7hA3>lHnJ z6=GqO^O2<-A-jZ7(_5(*%E-=k0@h0@fC&qx!67)*s>(6@F*kn6mQ4|lMN6<~N6U+t z!@>&ek!eLWy8>2yb_1|BG`rrxKDg7-B=@RG?rn6Eduu0I$Xj7aDLJ1%e`8dOT=Od%h+Oll7bkMfJ7X;I zCedHkteKWVN3T!zmj0{7r@jE4hK5VR6Z0L2zblKu_E^iD$Biu(UZgnnpUZ+-Supd0 z7CMKLgHiq4%KCd2cjC(FkBGoW1tOM^FnRE2Hxx6X;^lBXCQI0EKnKiUO7hPT_u*Td zB5Nv|#M3FiZ^(ICFNZLj;o%U2V+-w4h$2Kq1E+80l!llj{A|-?ft_txyol{VVcz$& zdRwt@^A2)fC%yj~*MmesdTiMd;RL)J+x*OjU7KH0eD0BQ^Gk})JW_6cN%84$A8GJf zb`X04&1my$jt3r!4&fq?g_cxjL2Z|4^Mj~~e%PupUvCQmML>hF?2@4^J1FIP0dwe{ zso+AWC4va?r1eS-Hat4f`vym0vw#G6>ZF;UGfOf&6L#7y(9(SNJ{>3NiS#&dt$zZH zh~cPUFrt^3ER!SIESJf3-#C^xmE>J0p0!Rcv8QS>vZkU&7(kfk!xTs!SCELTdCx!5 z4{2EC$O;4oKmRhT6WcCXLl0$oCf?wfs#TZ0?&e8?iwxe0BWrBsD4-_BdP)b2So9?A zE!fD#D)nh(H1ah=zKXZ?VvM!-mx%|hBA!^_tQa?S5Q<2bjx?Wkhp`!-Gn@vD3tx)B z*$X7*csZj?mULX{-L!{B{E86z;2ta=>p;m9x9EOlyPUDy1|NSrp3#+5H?g&6}zH9 z>x<7nGC%lwRl|^1j8H@htnQ*8mITyow$;>|r0w{M=RzQzKf^#OIdi4A(_*(K%AAd@(wl@gus^@UI{;N(^LJ zc%$MNJ%{2di9I%-tvnwezSBSK6)PENDm+QR!vomT2@qLVi>$CU<+H}px;;ras0pxu zBnN_FE{p^;Y$l8ZG?)WhqExv;#!xm>$c#giM(DH9t!N*)GX$3KcGBMeX2D3Wi~445 zsRc*0&C@SYpZLKdbn9)65Veq@lxiYE1u%dfcThqyeUJuhz2%a|O5^F#J6Jzd#P3LE z1nF{?EYqj-<~-h^k;lD9wMTfQyF?@qB-N%vSF9dbb@j${jjU@@U?hvIzgpKUgon_Q z8;FN3ICZUIT}!QN&?g$DKN-_0;h5O390U1!A*eCA9G&fdtu{I7S zpMLfJ1(d|sMFQiLu)a9%3L+Sve_+2axa~ZkzkShsf*2aP^;;2py<$5kpN1`f8Z3NZ z)cd8x95!skPWK`Lx)kZ%Me=HWq?sUCvYyC*U;CSA7q1$_BweJVAO_v?PJt1r#*i`w z`9LQYx>=twgP?{M3HH49CBqG0>da0fiPUddf-!Rsh_b;6z6112+!tUY5e4Xw2X|{2 zP1EE750M?vV5pTUVyvw}-E>l`ChxY}h@Lx*_I_Ds{9polX+G&b?P+z8hZuCCgTuCs zvRw`lAemn)rA282eY>8G$R{dhqT+I*z{j-{Wz%Hv?|n8Q`kPJBFi93|@}R7&G+R`f zY9?TN!8+$upC?D|3^CYTW`5jcX~wM~r~V3lZXt=ZH(a_NLk&Ch;eQ?no%ECmfo;4U{cogdlM zK+SSf12xJ`4HzpFEo%%ez?hS)X$+Vnx2Ar3uiRul@Z-M8{F1^^svVB>iUOz!jgJ;FU-|{_&?<@x2U1XW4!BIe6YfgSnqb8c6E%3c)92JH4 zCW<$``QFYQ#dZRoJq|gQj+}mjNq7f5#)HVW5dRCHGL3F$4!v*613?9GEN9v8va<(u@PvWD8Yj1kRT#>+$q|K&)Qj3>7O4cmlSC4UWKMub8!X(oaqmRI5E(P`Dic~@$VA0EZQCj$ z93fzmID5)ZCF?4(HC^$5gWHF5_Kpn!cOGKrv0{!KCgiM&u+aKPvPAqRTRW#1Gdcl9 zU?&pT0NpoTFzm1p;-;?%FS2Vp6VIEHpuc9CMcbx%QSc!ROaoxEV14 zni{jJfpQTs5AaFaf1rfYkcF-N4DRT3R6c}eQ}7uKUCfY(u?qw+2}g%)>t*rN@hh2Q zfjX2w4FPO^b0nsVfGql8pbt_+eE`~we>iViUSagIt^* z=nyu%NVR`tkugEIh0@|#VKVL)A-$J>7STGA2pM`%+;`Gt-a0m~oGir|c&L8J@2OQ= z?ck$}N~{nhzGCaGZ{tn8cWo0_Q|*t2YUM`xL$xNpKir?K2{E6&cZK0U6WIYAAR=O9 z$?j*uw1p&tAKwqbZwx-M-vF%R*v`QZYunHX6_P#p5N~wX&|{=B2v&ZMZ;GG!kSS4` zm_|uLpXs$LYS`=am5$MR)A9O1Hahe~!nwe?xMr9S2(V2^T26>j*>XbQ5LJ)6mHO$~ zIKGqn5p1dLRQwjD=(y%?X~3I8MzxsM8ahDH6o#)Op4+#$VjC!$E<)8t0mq$<5;>66 zjLpE#Ce*8%A!vsj_i-_XV*_@NN>3VCj11) z)NcSqr)+Ce4_S-#J|=fl53Id_3B<1Bdq&7#p$~AiyN2#!>eu7#g0Rb--qen}oP|Yh zJKCqX3KrY|MMW~RM-71{aP(47Lna zU#9v5VIAZg+L&5wKAlbsY3!IHdSYMDR#`Y+udo^gG0l3;J^W%jAIJsS)JPoZfz03+ zZsaHP-{ucL_(~L+=<)PsCg-57R_Xn=taV+)aZ2DnQf$AQ{U>i~DtRYG3HP|aLp$w1 zffGrwzL1!v_65JGLrhoRqlqrihl*J64Qw@cWWB)`59Up|zEbFBkKb&bcs$Y67?pp$1KsCz3HieT4 zYYMT?45+~!Vh7sI@Q?Ml7_J#oS2(?U{1-arrL>jNnxT@x%@4Xl2?HQLpn1y{2;(u* z!pJBMiDHyt95L&M-t!t@bLuq%TfohE3)K0^{dCagz|hgYv<^1ZL_=L_c#WZhHuY!k zpzXrF&o_u%Td{LOE2nQpYfO-u@$UO~8Z|4`b?+X8i`{JQ__}V!tgXfIx5O_&`VakPe8}OH<2NLIYaW=vkjlSp;(r z%xN(hw#*?qYksQ*fMuP)O73;jzy=1(oWuSn{LyYfS=vZ&da{xOaQlH0?f{XNj4@xJDFzOm;l?f0u#V`5EDQX zk&q?Y9T8>E4#@!7SsaaI!2+}BZns%r2w0^oN#hMHL_-6ikPSq*tsgcJ2`%))2GT5? zh4{rV9M4h0ybZ*!`H}A6JH<@BnSaKwHN>yY(b7i((c_STk2WDK$cC!@ zM6KG7ja8ehU#&Lv?~5y8R?3VRw!sKE)`D+5TBS)tOyJ2>-tnPQlNJ~E26hudP(1(R zCF^O^PrNE|AwtwMcjcST1En;@Kh}_NIR?e!m*4UBYQvKqb$`_&)h~A{%ia)s8`uA46e-iI{u6Pe}7uENE#; zi3-f(!N0jt$~YE#Eac>u)Hb3ba3FMv4w~hPn6ISHdWp=7En{ay1Dq~uPt||Iw7{$} zef2LOt(uGm(&#od!&9jb<{pK94;SW&S)&k#Tn%fCS!18}xcllHXiWu;C2dFy()Sy! zGE*%RHob&@*bW@u%I!c@(vli!JFrO;_VffSDhDj9ShwB>M#P~3ao{6cApF7oFB$yQ zep^i16bNa{SsTKei;&LjfQOs=&k7&9k3J0 z)6IB>i5vaLp2Xn-j@S-6)tu-&STWEuJEY^#fLZfB3)5ro5dK6;DTF)^=enEo9%m4t zCtYC*c|JQfcT@v!tl66}HU$k2ac5wu@v0?Ezt$7gylPg9fGWToSZ>1=4cB@I-l&CO zTc>4#`o)<}nu7OZF&Pcx1IQ2r%~p=FKyWGA5QRvL6(&=!(D_p5Xl!HS?rPkIy--Ar zk_gJ>F@(YRs2P;-UpOlJsf^)}4M2A!s34rK#@iL=$Jhe4lG2P=m2f-)gHLQ0%RD2{ zI%dSwJK*==r}uN)K`dr~volI>4Cm>-(V!XXnsMgb$wdVhkv)wy%(1}K0y4&L8Dx~x zO^~rt#t0kKaeM+$)IS2j7rTnui)CX>A=aOg=|GwC2Fc zFd?*>oLA;r3DgfGr1|D_B=G?SfP#(BCp5j;@!G~`c+JPJ7GC?h#p*B4>;#8ul?oth z`Wn_5STMO9d{Ti1l4X?2bV53j!8|vHu$vqZvve3aF@sdtp@FJGt&QF{LV8LTO;rqj z298gJWP#_jyf*_IJZ#w0C*7d-Vu{;+>(P<8?bjb2iQAqI_nN2+6u13(s7m6tKdv?L zyBlo6DC3zxCRj}Z*-yvtQ7#dK74((3C-(qdWBlL`i?zMCWNnQfd@-Z`{lIt5bN^f6 zUi{#%kN8m;Kx;B#rhT7qewTG)^b2lIbQ21&7fBDO$-HX&9DIDgp6eHE$lysNS0Yic zuYaH&U?j!k5LvO{&4ce!KgU_{dA5$f4E<4<{r^(+P1~w4->tLoYu#N+OCcsEEiIn@ z#ra{k+-#up@zPupC3LJUOF77ku~jVBKD&zZX9)Tvi<5jzVdVfS=A+?I&Dln3rRAa} z<$FhV+&bTy(AO40a~0>^aL&6?yyMZ5^KKOPRwzQ6t8z5#tVG3i%TZIri9*seG;Khe zP&D=TD?TQgG?&502Fml$d2tA#9N#ZImh!!6Jxd=#-hlGjo)j) z1XLMwYyhBuz#-|7Ng1)6HG6*qi{_yzAg&$t1DdxAziial8y6o4BZW09-dh{>JFA6Z z2KE8Jn#PBC_t=k3ULgEJ)${&eU> z;EbaK%syQlUc#oc!OPgjz17j3=&HL{gUb&<4ldS?(S#diCm0)*$t7?TN!H$`6^_)K z7qF>3!7hxu!6)@ZM*+mwJVAzwWW4vjR@{QM)O1b|Uo&%{8O>_^L6E>ErX5>O5s0q? z`D!8^2P#x#EQy)up_rmOF1|JZf<5}uM1ZYQ_PL(;+P? z7b>UaVFfXdAlCGA0H6wx)UwHgdO?rG;>m7QK!)h7I++BavNY_H@51ZZoqd&i(n!sW zRdZ2<%C!ko^MZhcqv$A2A_W*U?RX@{liFRRGZOs9F5;20K&OzFp8>*R z9v$!}qwzT%w;8shb2x7E8>CzRxUJEg;kf;?Nh*K2L?#%JdSz5mSC(BQ2^5>XdSsDKo0hBJ3C_N$TCk%WvB}qy0#iB(?SX1UD%C*8q15B zflZLb0V`K$*pFM-i5NWOfEB8d(U?qia25GD7QEeu2XDVAn|bI#8P!s+gc5ALFBhAj zpB%fv%-@U>lY04G495-x4Rs01T;0p*Z@( z1s>+c$o}{2fh>M9i=KQqPd*5`0zNwV5EGZ``r!K{rAm5V%~#?Cg7tYI>ER(pmzsvG zH*a-QM@sa}Q~P^Am!_@SgmvkT+yvPJuqN1^6PDUOzyP7T%hX0Q>zy>?!^oyI4ccI> zBHi@t{58V+$S3+O=Roid0%tbe`q+%&JsWvMVQ3246u-6g(uP)cM~1e94QSI zmNm4`JNbR-*{H5;_JdjU^=b6r{|Gz+qPbb6G?9#6p}38X0B1Y&gTvK;t&>C|HeAK} zdAthYCRn9Y1NZ>sBW#rfZSJZqj%OY0>=dfkIE30f#+t{Q)7VU(Y`2#=--0QG*8EoGJRsccTlXV{qF=Trq>e&h_g`Jc?9>r1Vi+OjPxz(k;gy|{vPZ7 zbF;PvVOd^CN|+0bsuVC!u{#dScQYEi+O*|i}{lQV}{GEFFmIQS)rR0OYd z@bA#00FB|a1?z1^F zvL=H8=6k3S$v1{MU2JhUc|VIi8dbeS_E=8Xas~2nmpR__kKTdRrEyn~Q+*Cu5}qSV z*(K;BmXjzoR#0rBXwkDC{je97eujz2*p#{KBvdPx?G_*$69QM&X9Gf|DHIMOT`Zb- z?2&*qaR_I8J#2+^u-aNh`>;}m@nWkEL@+@|vhJO7IM%9xCite0)t?5F+4(|;Oekw# zud=>eXJZ1f5i>k;7$k#7z#&y9Sm#Z~2H3vWUyjJ2(dOR%@~GZ_Ke~N-bo(o#+y8rX`>W@4D{q^$_r0y8LnwR!4hNMSULxWi z`^Hq}ZUB?RpCH=B$4ag$kx{+URR!Kbt|}_Qd-e&fE!m-pdn%nQv`3hf7YbvNG_&Bx zKotUD_V?^;iLxCU9df3fS5BrmHVCz}0oVbeh&}w8dM5Ei26|clzKzwa$wnZ8AC0CP z@paZ^y3GRzTuzQEKdB7a{XeY&><#A{NgPM?8`&O9FPDjAA-N#k72&4!0u(E7QwbbO zmJm?d40iCk`SwtHy=H+W7QL-INkx07WD8myB!~BfGit(Qh!RrRy5f01M~cY`($No! zs*=MFQ^O5HQqgMcC}ErKHm?*-R&D485cT0+o{WJvE@<*<-Yz;MpqK%MBoP!dDG?kztr+cOh-#EC z^|@GgZM|$IwCW;%*lLYX?42IIT$&m&`0-tbndzC{`%~OZN*}LuS0oH^m#xu4aEGr! z-ad#maYu`RuF+1`KDl&l1mejqp7DldkZKQ#2mfK@w`EJI1E(pI|6yC)2T>#L`KL^h zIH8XDDYZ{Bt91{q&Pg+zN9td6bu%aD0U#Zk4zO^qk&(9X05(F6N}D(%^!*R}8#Yq8k{t#?!Dg+G8N=hQunGMx zJOJWmehGs5Jg}BxTD7Q*F&J;HQbAY_HTfMo+Q#ET19^D9iTC zr@pUlBIN%cja|Mm4C z|Je;g9=+*#LcX&K@^VC&jPTf@Ysv7rBr^+5k0Hw^mu^9KE3HRxe>CWC%(-k|@F zn+^KE-7KvC+!A(RxS3uZmH} z8PkL}k`?JBTX|t}CPFqWPEaQ#N&C%F-As*3$K>Z8b;sm8#-u37OM-OIjyVKX*d1vw zi0)mAlBJKsiz;n3<}$(0*A6_4^5E&{Etw>n2itGGS1c+|;IFckPRBt=x`N=ZPN^(Trc6?jc)1`b9ZV?|JG%aR0cmqwReI_#3h*Tj#9#cQRV@j)D2(;=YC9TTFX;s}!<_fTrxlD#KD-$hc zCYZ{6U`nhmGqD=Cm04{p^X@6NdLdA&Qm(#6u#QlsVy&(K(K`LRJj~UpiNbZdvxdEz zQmn(5n_`WL*H>1_*B=-KB_fPCqMW$s%Iqa6_Q-M5RF=uX4@w{UR{mLZBJ{B@jfUtQ z|MJ7`Se$YHryq9rcI^L(-w*dnRP>wSp0F^*uZ4RFQe>n|1f#?pf^i!*qgOMF!@NZczY98z6raGW{%Dm=GFQGK|nowB*DGVOr?gfP4AU(>eQ?e&SYvQ@TQI z&};fehPR$M2wE(WWea7V2mal%0~>Ru?u7o`0#KL%)w1Ibnv|b>On-y76iyhS zHR9?X8*b5CS#$=&aAsQI1xW@^?H_J6duEMHcHi=X0C(i5=w1*}76T3|d6jNtP>L58 zUOYoQb(nv1HEp?@KHq@?2gd`a;vMqR=xX}#%VBlQasR1m)ijHzs#Sw!2mbWHv{kv$ zt3jI@4|TbD?L4-qF^AQOqpG(fq{TH~|6aM{JBxpCSF!bGCTc4noqzoJF;)XEO9$h( z9s^xjFY;o#ig}P9)3I-7q&=BlGuaA&+>qx&^ld=YTnmK|atKqHT_KLI_N-lVU}8NJ zC>1x>XNUu?HhEH_7AS`iMn4LYr2kG%hyrP0M-f7p#KJg9Z~>*rd@ zEDjm?LVbqh8qgft`wY%lQL*Adp|MD75XXlLX2WuX#w$))UO-jDI^2Ok9Zr(vLEX^GcQ?N)iQ>=7Fk`)Pbq%Errxo^?gF|5=G1|M2v)hfa!*6tQlRF2UU zl@~U)@!_5on0YDQ=;4BaRwKy()b9~3k5+`)aszU|&gIARt5mR&ZDslne(kY?>3!K% z$1t}aoMeI+u+T2}uMNJ5xPx-X1uGUkFbDaVj(BV1x zOR0FqIukBA4BIT3#4} zRw5$JD;GuSCKsW18QOuRIDr^xN7*SYKqYH{1TW@afe_Yq|Kd0^-zLY$cOMq!%k=_+ zMxhRp7uL>mdR)Y0`LQI8;r)MYobukXBHjt*w%iI@2{v=dOyP zbsg|PfKDbS>cILCeF{q{y0T^2Gmf8F#dys4>y3qsXI$1PpN&O*0t$C6` zEyQf%VU4LHo@DAB&)l6afs~jFnf$r^KPodStty7fk-W&Xz)Svl-(Lcfk6@G6C z8_M}lW?LT)-G5^7p>X#MSsb>6du4Gb?<3FRu;rt4gRNh_RavrwsV6#~d5a}kS8OOd z|I0C;)M(|U$I_JRHR3P%X+u@TAll|;{987|2xmrYBpf2tIB$rTggR-@g+jrV>N8jl zh$4{pVH;Ou;&f)fJkXJyFKAu-V z&^9pNPRR~I8EuUMs-{8TEX!{WJ6HL~YhNTM1GUXn!3Un8>W@FHS=M~eSMzo5vE!6G zlQm&+dV!Y7R28rSz|x4ExkWxYY^s1UQ$CKZS>wrv-k)!&T+xf~px;|Qq8Sk*{yjZA zUl6vBErw`C*^GE+O%Vj8gYpQRg_EJ*T`@E^D?o208+Nr4U{P*ykEmt#4aJ1g&Pn5i zB0;fqHCl_F-1dONA;*F6?$DjMeW?=NYo=|`n&Jlk5@v_~*m#@zmN^9|_|6GaG*g;8 zLl|+s=5tBn8UuH=zSy3rTx8ViuIkK_B`UKG5R??6v)VWZJQ+USbKoKQ^3Q+rp$CT$ zxCsbKcQL))^1dG(Vg9$Q#r&TF&Icn5Z!HPS;Qu4~N*fNgN`3d&o?#1yliU~LH_h0W z&Mx40%JgxUv3cUZCc|YDOYuknCh#B?7aDSj=oVlI&u}01bQz<9awjE%+BZjlPX&{z zpxb4QN{Y{ui>#Vd&J4n)9ersecTJq!xC2+V4Ph2Jmx{7k62Rfq|fO^ z3k73v=%O8Eh-Kx+R-z-obCF~O4o0B{)D8TNN$Vwo!#i9OIUBPUjFXjYVQd}K3Ya&} z?R!KO?}_kVK`NZDS00f(DeviT=B4Pp^Z452>5eu6K=;MK<%_)P*kW8UTt3K%!)Ata zcFE=UN-kemq-9m-^4ZtK{X3VB&n}Azcp!9PEeQ!26Hhtn1diAitR*yPiHV1&hDxcd zN|UAxu!@am4UgD(dsk=U+hXHw=TgTxFnkvu8vl-&z4&+M_i6YRs6l54T80l&F~I7V zt!=&?v8z1YGkUt0{n{cPn75_kYS>Zm%KrnF%dKFonpomGbUoj_g*k& zWYStrmS+%D?vl$hW~GljyFBBq2QEaO@grXblCHO1FlMa8>!U{JGN8u%#!%zFDQcWW z2>N{&jvC4Os4=(_WYW&L!LXG!*8BmS4UpQ)X)<=y~2+}Y4j4Zz8v_CADH6C6gSqf)%d|FZcK4w9o)EoiW}#J8-L%4`(#|;5Jn=uWx01{X5N|v z)7SDUbJyh@qd0t%=32d8)WqZ&GdkH2;c9>tm+m4nuT{X9r92E z82k!I(eSES92DeumI4bo&N;Mho}~t5Tec~3zmV3eX3f1M6v;YDf&sYQ)x;bIQmaYk z-MY)E!Q%0odAQ8{H7WAaD?699%r^u(I8}nwd3mL*(HCmIX| zuM+)|6BDj;LZhssq0H*3JmCmDYtwFh1dd8y;KRgNTTw{bJ%L324g!i*rV8*zDaY4$ zzZFZ8%{VPzB!)TOQ*u^hGcVsk?h5a&ne(k`giHjMrAsAk1a5-I2+Gj_ez2nIEVtzZ zXvjDhsIeIbaR5u!lUocB19Sc!?<$8h27mTjGcsiH3_<*xW;-&(Z_Z->jxW|SMbc$i zNc+csv)>^S$@*Tfu`2Y`E8y|qH*ds!S1Uta<#+hq8?H&f1b)c>Bq`>WnAN11mleF+UX@ixgvsocsCGkjMV_B;Y$8;dwPLB6)Bn{+}4Q#z{3o;VHDKmwfU1#3`KEmzv-g}) z&&jX|MT;0f$jwo4p)G?-+Iw>nH))rev3eamzu!ox-~P+9mg@mc_{ zky6!Sk*@h&tI%snR0|cTmQWc>k2u}!9(J`_*}q(a0Ta%K=NpM?hg2~t-TB_*IgQDX z#uBkiFj3ys{b*@&+pR$%EK5j)IuJf-sQ;FW4chHvF>eC(e^gTq3QUf}(x`9v(qM@b zxq4?c);1J5Aox_&pisfmDhpqRS`3x}uGNe%&ttWu4ojO6Fh*Ts->Ad#w_}o+1MWzK zG=huBnHum)q$TD78NgD@W2{btT>-@mxFO+F>e~>=#iaXIPaknkfex|XsY7|sAysyi ztvy|&g{~tDNRTJCyAKnseI=2Clp0!76{Di6)l~$-qD(qCLJTn%VhHTIZuA~dL+p1|tr`G{KJ`fxOsp}yu4>PnUQQF>WoF^L?Sc8L?A&Pvf_^}%6ajVqS zoCWJy1USvFt|DMP?q_hq+UmLmVUNL&4!f=6Kl}{{!xJs(7-u+gyQNM!jeO!`Yk*Jh zl=~-WczEhv_(Jq9{GkJ&;?%ov>Rp&^hIirA0r>^!fc)MmiI_Sdo44bXL`-887$U5W zQSif45-}waQxajv)G3LWNFpAXl87mZn39Moi9iTz0hf@+u8MT@BU2JFB@t5+F(nb} zlZf|CNyL;yOi9F)M66FDesoGArX*t1NW_O;*dbL)VRnJ)n<&CG~OuVC7EJ*cO{E9LMV5E046Z~f{jW+Y zJ=h499;~L)gR@cTL2>0vJeV1y(t|6KN)I-TN`K}`qtawN*$iI#z`0WCQ=3DjPfe-x zrKZx^MW@nNWTvkNODesQR9|ayeVskoSN5eqr8B3}SENJE%I4+!I(3Pt^z$2)OK(cT zFAhPcqjROxE1N^7SKf5xrTu#Rg)N!hbJ6Mam8m}bU`ePqq(59)tw21x264}q0;LWr z#4A*XNVhhPrGDayBi5UwMLd1ZYD5$*#5TF$9E~e)o)YUzETH~!CDsp3i8XB5lvs~a zJ#S7X-6?N(VZQD{$t%$8;NdB`n34iVvqmO0pev6bbdVc zxSkGSrtR?yE%x}7{7lKul>A&7k{>;tlAkI0nUbF=`MId%=SL@!A3dFtpDFp7lAkI0xv1pl{S(QL zo=(Znl>AJ|&y@UJRPys<6UmRBPRY-d{7lKul>A&&^7Da-ASXd&4%OtYlO1|PdIj}=xRIe7mH z|GSreC%!3ZWN{I_G}^}H{U4oQh@$JG0r&TRbpF~X`lhJE{X4>aMDV2TOIb4b$;DwK zOLj-S;>4|u#VlD!qQP5kB6jnn#@@TOv8$={Cqt!k+<&;%*as&x)?BBtMP@Nl`g&ra z(vLWc>B&OU*pfunnSD7AUdQmQV=cYDeb`qDH{CQ4SbvyUufSvn^2~4 z3zudQ-!+@qNOr60B6(>PW%0G?;9&G^<#?G0-o#6TPu5!rw#3m81S|$i zLe2SJxGkdI)dq*foTBOx|KCR836Hmzk5wm4gsOLLNu%D|JMq?l`^{Yu!xj+K3)Skl zk!^{hyrU6dfe%uC>hP`bmCQc^tDMsPY<=`?^Q=znNOksQ5)V*D!NSMDIC^$2F8k68>E|MXyVp&d`kP6(gaau-|w=?6n4iHMCV z)ACmfE)NB@FmL{;2U?8aIsp;-j)xUFLjg0%Tz>Z zU^t9>SwEXy;z4{D{f%C6m<8Hn$|yU?wpeR*l#H_e^~qjP*T_rnKpdZ-BkPa=BML^B z*Z^5~A_7JFUAkXbzCO93*ZajdPEWACeCi|f!hO}lOhoH$H^oywUlz=AZAa98eX=Wy z%dK(S=;`*+Qx;8QF*(6RBx<9d$Dr9Y%rKh(Yl5yp#o40Ohm=~!$t*ClspZQ~Pu0>@q`hCbB z_x-ZXFFXCR%P+hAvez#+_~kagyw)$b`{nh1d81#x$1f{%L^Sz%Xy|?1tJU|1>;2*S z4!@?4axG3=pF9$NKM}4^>Nz-YO4m=b+duH4Yq)qAdq&a2g3E;;d)=v z`?-V-q{b8qGXaFKeV@p@0rp8AJ_X5`#Kire_MvY^5xSIf6&*iRm86#Fl009SoPOMJ8znW zDNl?m29|~sBFP}mPN*=K?LZ4w;re7yewMeE@@DZ@(UQ7ptYic18GPJ$LH9CPzilzZqm?)PHkFfu6c;=H0_iCFF||Mm<*mgAefZH z(HD$ZQ+Wdr3%yRQs{pXxRj>m#QHa143A!RteW3fyCyY=gB0cbq#kQrqrFXPe>|Dyb z;a-b=XzmV)5m2VV#m-JBpVOhS|1;g$P_N>Op00g9F z#;_xJO$Q~b^wSYpyl>6As+MlUQfJL;69;S7xHj2lz*h%$=V=4hfI$zNAO;AyP&AKZ z>8rI2ig=kdrbC0Hi60+$BM#l+yWsH6tS+&ADJOPNUf<@eqIpy_s>v$W^wW2Bn{PC0 z%%_-sI8@+7yIHG9j~37CsBswbHF)<;*I2DAQ^u##eR`t)dPnz;ceI(!R2|h_-lU_= zs-vI^6PfH*mHVoWLJQPU@ZCDvu62}SNkFeX-qGEmqkCBwlRCQ3J4$IVOwE8MV8q}z zZ>XUQPZul(m=j55# z$y5A2Cr?hUS-U!UwyKk7+fJVC@|y-H+i>zsz#EPuc`LKV`CLDT9Uj}slWMgKgL#b0 zPM)01ozKzH6MlsLUZ24KG$ftl{&|=5{pr2D7sNbwv6dr590;Y{rD%Sw*qI?-W4Ub{TLZ_cA@Wp zhqF?R4){(9_lAKzWn*3oc0cfIvjOZ-;lO(;Cq-VZ3%0@ZcX#KfxYQqZ>d@b zJ2+K>owW}aEP1RLLVylj1qs9UWKpAnJvNLM<;yuZ-NI}3a(yT{B6yPu@JJtZ_@nM+ zUiX&*NT)Pm$it0DhuN!&zWU|&FY z`9>&A$6lo#Py;w;zros=|G?Y4!K>ylbykWGRJ#R)*V2O0G{mb}S=FJNWl=N;wDr`! zMA#R;6bjQwYA3K1*g^;!FkoV0Y&8B;l#WM&rxCnJXbFf#@VJNC0o=HpM70XlGw^1O zYSL-{?rOPJVj)3MJ7a)Bh=;SXQ$C2!X`rO*CXG6__cE-;Jm2QH)I9{xc5 zh*$|>WVgJxrb`9`u0e}C@0D*E(rbCf<@H|sK7NI6TWhz2?5vW0))HLXx-^EY0C5xl zD|6qMVA#b8jpENMZla&FL+_+Km+i)pS@{NRlLT4%ggVOdB3e5M#c5>IHh^t#3^c0= zMm#MZLIBd1Fn9`X)2ZC3r;qo=PacQ|8>KvSktY*e+s?HiT7p?q3A z`f-~r&&Ul(1Y6DGb~t{^WVb#qFu_bO-Oc ziAVJ7ut~qEONDh1RB41}Sr#uJy^}6zgn(o9Cls9cNpa$?!+ceWw*oc%{Z>3H@Gc_T z-KA5WGjg_E;ZQ+oTdq%O%2cl}*8)Dit9NPx7HZCzd zx}S4?zFK&s>c%2j(5*yEbV6al>zZNi^F8O;a)OaRRIY1K$(_k9ubr?ge;7 z!=|u7cajEjl!Jjcm@hbZft`lZ)a?5z&~B9kzF1^l^A5HOo%u%7#YC#t^pwc}0&pLb z_~M-<1D`qrPk+6m#=ws{iV(T#s4?)8*htRkt$`!L9%JAas*Z{yp`%(f&Qy*%3S0wT z?eUJnz*|R=m`?2ILdn3Vfq@4-V93S5gStLrCAJ_{8c{Bqv8CH^%EinsB)jUQ3gFa# zvNKiOhHZ+^h|?IccE0d)JN|Jtf$3@Z=50Ku5fRRY5D7MMur6y&-o(X=gb(4expmlZ zf;Ws1t{Hv>-YthM&>+1e=Rw}XK_g@lt7AuZC_93u6#S9s*fxDO4zI-ahEkvn=pD&vVZEdEU4CBbww9vQ1yZ`vU=j2-GnY@7?nJ`Zjeik%VSW`6rP?Q_Ekcd39$OWDe9(_Ive4|J;<= z#5bFvk;n7qt=_zC@++suudg~!>QH%>@`Jm?8Kn=@V8N*l2e&y)6GY6-^eOylI6oZx zDOQa~ug*8FYTr1-8}KKZ3H~&kAIis@`g!&GS7C&*Cc$pj=|Q!4d=qgFUJ8!~0}}Nm z%^2BLL7_q;OxU=6JZ^VUiKG3W<3FjZQ4!Z>4+L6adxat?cfwbCI4OaUg>wicpo>UH za6+6rOe~Ax5tLRu9yXT4;+>lx{bu;r*x{jablxr8WcZTtD-fvx5BW%ufiBi7;$k)?|WmcwLOw}S0NAftPY6G*;6Mtq;P>cjj;AVE&gl6(ap zZ+>?M4=(5X8_IXnP#!7YO+$IOdW_WV&-xECsAi@HUA0k!y zd~LW!u5e@-?B~nL-B?P#Og?e?Ib|MuhVKg}_B}_y&ZLr<=$Qoh3kEEVij-zBp4F|p zb69v06F>^@ctQU$KDby(y@>HRdVkCUpK&1Ai5ZJ~vV(4aRJa0gZ{34W%$AXWLi%g5 zraLEqxS_E>+Ysuo-IHEWE8wHqz-r(v&3g6O7~WJz`koQ8EKcb$*x%9+ozZpX8yx;B zV^lHRGtaMY&u@2WtTC>KLpL~4KZO5jv@_fZoDcwZiLgp5TZDcnT6jRiTpt;a^7{Qv ztEYlee0hDmUHtR=;rpg)&*)nK-#A12xTys&9AqRL39Lny4_}$k zy*ZuaLO12~17A-PO@Z!;&}Er!;sPH?4*<0Sba_kYI#q|evzybXCO>=a*Cc5_F*W>H zj~afsM-A`nQNs^D6>6CLTp7fv-XPv4Nxo)@B&E#9-<`2N@!(^nGve0s5BCQ2q27QV zeF_5toQBr?`z{0?@w6E^PR|9HTnX7SD!lYTz#9uS#NVeq~5vy2dB7+4ScwX*(uXY-Z}pn4di2 z+*ipa@)OtGTz%2J4m>_QXq&wU*FjN)C(W-dbZ(p9%*m{s(^A#g>EB+9)|u;Sdwacq zdo@fWZm-AfB(vL5h3V>!*_v81E@s=M1z0%jLspF$=Se8DH*(>4#&gy2IZc$u- z`hluuGr1f(@|h;{$AZFL9q1k$(817b2rrgU`?n+%F%5sQTypMX*hWBvbw6$%7XoIs zE<`q33v(kNa$JbS{l<(iX9ieks>|*g2QcstVad+c6Bt0L1IBtE7!LS3#22J=oUhw_ zEx_1d&R`7~fQ<0y7w>3YjD-jZYYCAJVU-XGs}ds8Go_8Wp)l)Zh6Uz==#I8OamWSq z3(5v)y2}zo0p;>rZ=J>krPuwQ#BxrQ$M`3dmM<45fk)kCS6@VM%-E$Q)Ru z$uBC%C2a>Q`G0-~8(Me_mWW^N7|E~G;!_~uIGi3znnVmrlQ+#aWD6i34)CT%-VKYy zmI=yC#fdA+$&Bj}S-g7eBlV848rfM1t9`GN$CVPHUJu@&euaE&u?=3(e5;)<;KX^^KQgg+Y zPt;_~;72)!rr_NIt$2+`gxHCiL>m4+k|A@x0g_H@d`IQI#g)mg>=E{3`87T8T;WDacg z=FTJy8+>u&`u4`b7OYO1&aMS|!F_3(ggN`LX<*l0#ZqmrGVQ$T-|=scs&V*o92W1g zcDRlC7|fLWP99pvAOrPL)g$K=tS|^~W-7`8jEdyayLxKDCTsHu>6GiJ2P?xJu~1(B z$`t->KU-W&ju)Ad2#K(+-&D~^ zFtnPjPAk8?p;9e5*R1LgF_Ier|8$7+gmKr-d%lY4r1xBY7T$BP=w-*SELzWbj4d;E zN;gx`3s}=`Xy0`B2nf}3>*}m2#&V4Lw>H(c=&xq*4is3fnQtsGrpUVK2

KLVP$S zCM-4V`A9y^E@V$a1Z8WHKZ@^hYdYp_SzD1H=AJU$2te+A2OE^En8F8DcR?i`Gai!KV%tZ0tXuozwdrG?H}Q-fHw;yfES4td0>ir6o%|ZiHI^fSf-W*v&3jpG zG&ET_sFX~Uh^YC<{FmA>K&MHyW6>Yk8$nX1&HuSmmI!b8)pR~|@PLB*%+CdOAocuH z1mHGg^LJ=e4Im+&h=3V#huM{+cqqWQ(#$Z{0k$u|A%7ji4D;>|q*D0qSoAqjf63rR<0#^oe(7u?$Ua1mTFut3`+nQu3Z>) zGB}YP*_H3I#*^>6rhON7-ZH~mazLyOT`)vgux~Nsc3zG<#NGI##_Q@G|8aA8msH~V z^F$a{tO?g)Lc?T2g%;ox0M}u`J>yQUyTtDxgS`bY4}+LXs|U1&?1{XHf;i7m17!>Y z$G!`3!BAL%SjY$EX^Dt)k_?i6hvCGWe6S?MqvprVom~qRrZKna_9-1+G!|Fm-NrEO zfE20sU0xm0*Vll_;Wq}Lqy~}ukZ41AW?!m^KC>fD9l@b_MEDtjh@-m))ZzP4zQ_Wirt#9L0VirmkO$vy0v0J?ElW`rF=dD zf^PGv=6IRl3Di(p_8>v~#JU89cJV>lJ5V4DD;Vd8asM2#@vUHCnMeG-bC46p^@ z4QzG^J`_}3jPa!70AJXo3&?Jbi?h2SC&)JV&o{L+T!3A4Cbg2#(*okrK&z@ja`OiP zMz9BrXg2>GYkarAk_#I+S918u;2k&2HgGjT;+Vy2tP5SV99SlNB%zWE+TOWhb~%@! zJ^n0N&ity*viVQcgRCljkOL7!(u3w8w_}rEzgC|_*ynLCrqbEx(ql>hjTZXg&r%;% z_EF=p@VV0ZpgB|;rZ*6)@_HgxHIE3pn7lx&DiI6)2p#R4MOGQFmJ=R}l|U@eSF&UQ zmNKJSvUJf&N^RdEZv*rR#L{5^&CqCctimfTK(Ih82pYuN zn}v7;Ll`c-Wyo>=G{dLTn-mOta8W=;KdBKdGsfX}0te9hX@%zh(Mi z`sb&goF<^eSFi}2KfRlT6jh0`{31~1&U>h7JK<& z>eK+tLJ+1n@njHK3HZo@V%}Cw@vX|eM||jpRgKv+YJO?_>(elOnGUz@PO#Te(R5A#J|}u>Nb=3W=un+u~2jvhA^z z9{%v8N*icIQiJoED6F=V*tYj%7cV(i z6#%D8Hwp6wh&LIa{`L{c%?Q3#g8fPXWd|aNxI#5WTBuCr?|NsU2rK3kS%frr%4dj0 zY-#Y<$TIV=0)T-c;qz6z0zpT*&jk|tG*7+Q{$dQ<6}yv-ZtyGBpmAEBVIDD@TfoSk z=9Bv+=h$u$jbKbU>JC{$LlZ?h(Wx6F{rS8$%pe9}P!o6#^Hipzn-WtYl90mrIXR*b zGBh>j_4T5$-4apFRfUWqz1>35IZD9U92h`Q#}G(KQ6T{XSgo>ABy_-Ype?uH#=K*P z2fkBWATjXJS_}r27Xp&)xHS|;Mvo-#DI+Te@Azj&6qUd-BU}a-yK%I0I28ssR@n0E zkN}_`%{GijTN7e~1Qx;)Jir!mxy**rmfhZJ34RT51o1fTRUK!FC7>rM#lCcY!VhR5 z(v`q9eG(&xFuh7W7KG+x(A)YC|n>6R)s>Rpp4;lUxQsG4`!C)7As%2*E5)Qdp#Oz zs2`w^{8(Uh+!&mr5zJ`)P-MSHj#f@R)LfKzAQn3z_U6PhDqI?LI$ODO7pub**A2mn zwysxcWx58K$=@tsF6vL#A!s(rHbR*bV-N3AI{IA*nka7pkz^Fm0c4Kq1EI(AX`}U) zPrCtwVl|%rq*-)Wz!WAKVgaWQ1dOj)$HH8|NqUxLa94hyZC%6Wr8dW;rOh)vfTUbzo;RW;Q)NO~}>01XcjXb86p2vmg&4|?h7w^=>Q26rREG4J5UL{LiG z;TO!s2{s8I31Vkik_E!jbv%@euTdekrW`=fxGKS&TJ|iaXKV#_tF`2RWMH8 zP0;EcJ1hjx+~^G&9|_&HCiu?Tml8mXk4{d;D{kxv5(u9r$Ar z!~w;ayZ_5up+cW8D%W;5ebR6y$VghjDwFKixFphd)oy6k~l+J)q$x`c`muXM%9M#?M>Bnk2uZi z5SPL6cUDjS=33CBYMsAo#X-O$BuWu}f&rw4e+pq9ZPRlz2%v+%HV6>6(*qoh({5dY z045%@eHW!7SaoRibsGd2%5x>7gOYf2Jgq3Q3s^s`MS#uJ*eXa17dRIgof~Poklc-n zt0YaOE#ZdB?%AssS5GXio?KkLVsW)uT!j?cAtaJ?KgW-#wxq~eKRzg^1Oy+LMZN;x z?gwp94}jO%X7v?@_XvOEoZ{aq4A*a~_l&WilF!c4uN0`LN#v`fjquvbl&pcO;Gk5g zUlqh%)A~j1pA9cA-)z>8dsMba$ismi+eVE$kZXHb;!hf9v+!wG?YE@g9SNAAjNsz09geRSP_#cTSURVb56*FL%`|oFsmOm8-Z8rkzu=6IePD?UVX5B zbvTk~b`YfWzU70oA|;pbIuA-NJkcZ@Vv`YVq*PrWO?n zuQ+$~vhbZv@Fn;9 z^W43(jq{tZ>gF52bGG@S!Qi2<|E9*z!QjkU8~JyRCu|!PS#~u{{`T6GK&8Pz^#tE^ zEs<KkVnX;llkh8u^ev^wl2zs^O!4*SCAm zR}FvsOSnD_0K1s_G>yMdXm=1?pt{TBGN*YJnuDw(mH%EDkyP#j!^=&GFR=TI zs=5pFg#IyTsdWNZ_@b!p5@TjyEM;Mcfp=h8&KKNGl@C7|-`F1?Z>j&mIK z>x^z^oqA7um)^%hvHM`ScX8i7yLZk55tb9RmVdn~0Hx@gR^2r#ohP2cq$#PLnS8Red$Pg2jp80q8#Q5o%yDGd|a&Rhy^WL?IR z$EMyFj$ue(}VUmN1xqoUH>Z4xiUE zg05eF7SH5B&v@@P_BwZ?HA9_|{?@hKTg1+b3qQwcFoeF@kxls8`4g5lA{H4s z%ORp)kC67UwC(KiXSbchpcAX_6&P=np2ZqF=HX2S|hC7G|o!-O; zZ2QLLeG0Vns2f<|9{v3%GVF|GPa~)VyNh&_vXh8RFX6dwFH!71BxKy}CDLQN5_^fr zV1+r6??`R6YugUpA{dgv<*Z?7B=H?1?~44o%I25y?m`sha+aMptY8cr7|SkcAa~xF z6%3DT8|YHu>i*7~@e}U60hbvNJ8$UchS|0|Z%p^g*m>q%lrGqL({|EkWN^owH$>5? z8PuXx$q%&TAv|9En(IKC8w|n{v zMN>IbGIM{k{qlNHbe-EUB0o@yHeaNpXr7c6li1SVB|v5f`7K+NYS^5w2HnXo%=-g$W|Efr8i4WUD3dU+rjMqZ0g*VE}nXnmIW+W0dYvgOz0hB$1a;2hIKqMiWSjILvk2<{-gse{FVL83IB?DUd>%KatN@#4Ow>z)kA8>rOFkR@ ziZBD_MB(n;Caj7)JhRs9&aDmy-FHD5;`xWDuMnzu81IFNI|;pQ?*V^xV0_#bd&; z0mGAP+$_foxORVe-jArw&franFDaUBvz?-_*hWxH;x?bWodDdRH-+Ftx9y)tbZ5S9 zY~qQXMD8dyu^VtPLFgYPQ3;&i&ND&H@z`y*#o}y2z>|+(3%14VT2R;*yii2#REC-#gm&`UOTCYEhyVwJkand04;C8%Of!5OeAV%Jn?Aj8%^NSlHbTX*t6Q&cSg2;**ZJ-HJL8PX=^7Tdza+|)$C z>Y9AYsqeNbNem<(G1qp(2~b}zjSQq0yCx##7S5DIh7VDzASbZ285+)X4wO3RsE$vk zBB5{%8Ov!_%7JhQCI@6-LW0vXSc7;b&Z7yb8o9}JCskpbH4+uR&zOWqsAclb^PQFv zwpIsA-YW2L(?ssua87yMZEvyp%uUmsrPbvfGf71%u#nk@J{R)oAd&jVa6z!8J8f|O zCi>I26Xs0JJHVzrTwE5CYsW#EnQjJe>0QU^wlq`7mfw!9fSU$vKHZUUaT^yx_b)@}u0`|rEP6e& zxO)5IYVLCAhW1QtZ|EAFEPCZ|?iK=D?Z~Mk7i;@7?xP-W4w}K_KV9onf4!1u*JdQ2 zAPd*>_N<=U8UXHNRQV{Yx9~USt`Jm`;0U>wy%JN~V)3?s=>-xJ=Pmv0QbQIz0L%Q961I!ixZ>a!)KT8Ez!WTsa zzZX>S^_6>(o|&rnQq`5&-bI$tKlNmuw)yX<5b^(Aw>fr@c0_QmkWiM&8ybvz;u8g| zG|r1h+c%#4i2^_4VCo&%Z}>~t6@##)Nu;-;)H|p$h^eOb920(E?Wn`}tvg9087$H= z4JO~$TYw+0h$$eER|p>nh@4uhHJi`|}Hw1zl|(1*0jTf6q( zRJj6iY}3Gj5qD~3-)##-I+$yp&Sxp!?K&{`mir!fv_hPR6n$LqXoc_T56MZEGqf1n z!2;&y#fUlHpdA7`bK7~`xVXxe-uCM0_G)`j6CIq4+g%$?|L6F7zd#4(jQ=9HGGhql zHe%dDc$LI(iQyJdk(CDy?pV&)A!?wyWfiA=pi}J5-}~1H-m9qT-1lPl zG(N2M>DKH*6JXpHh!_Ncj(>jGZJS&u23dMs4ZJq$ z*{qFx0EImCb9hfFs~@AG;VaZnlGPEm#p%mPxe_tt$}!&#nLW-9a|RuohZ%HjBphBs z_j-mrn^1J%cLtS3EyhT-Z}^$kl0FHV!v7~)MME*EEO26RQLv##*X&!RlRvrEO1fCf z!Qrw2x2~1loeohCBwe!-G1f?Y4P&uAK4Zoz@le-1u9>@Nn8({zBeW7qZ612xfTvw? zm>)h|ZIx1vuA%=N2glmL4Z+v^$%&?3xLVIRU=h*-mPu>uDgnb?>i_2If{Q><2g9se z;kA$s`Z=*mS>T=(wkY}Lw%J}~E_ap|=eTAT;MQAb*SsDWqNN_DG|lQ9hYS}bt3ZdV z86?QT{yBxjDTs7|9Ad&Pc)f<<`C`pF8bko`i_ym-w}B*LAx>VyDSPJ&9@p@+Sa6~( z+$M=HKa_P9k3(WmeYZm}&L0p;)Q*|0opp0O#16u*MT|_OVm=XxGd8INuJ!OJE5-FT zdZyBC|6ToCdtRPUR1dUcCPc?$poO=v-!t>EaYud24A$eppg}!b5C&ii<_7VcqNl40 zJy!ayXjKh>ZU0gdcT1g-DV-%-<89W{0fX%|twHeMY($1T@bqJDVs?;YJVM)Du{vH8 zhI0t0t-15mU|~uLuwd_L!h+R}RiKbo0Ynbh!QvMd5rsR0X14BXWwoP(5NHzXz#=SM zogMOt5v6coS+97mTP$~w1D2^FB|?^ghOe|*Q=xV!;0E?-17?AS3m6<+uB(~QSUtz4 zGGnETe$zTPOGD?)=`;Es4;m~>P?TfZ=5x-WLxAQ{x%(3xoB-4mt!~u~< z&@;!0D^!ER6QKyj1bYj&n~$90cn}{)=7(H^C5w~Af3{J4O{_Wf*_ir9=XP6E1ai^C|td)2|faPJ;S5=uH{iW~&53 z4mU|*GR<39pKVqel9fWJjwR9w;Sjcb!D>f_z#MW}4qUfxAp*J1zYxjtZfRD&PRt)J zmH^ke_`!OoBa}685VYih+p){rHGpp9^D^Mh_30z5L7XZ`d1+9g9RJZpU{lEW9BZIr zu&Bzq+2vI1DdUt!S;QJpVJ^@MEz8b%J=Va&Op#MTvl)Q}HF6E8$dtp3?dauTNFA%3 zf`M8pAmMY(3bkyVvkYn_v*^5&cFr&=DtFbvJGs0Zhh4t>frv-a#+F@xxM!Giq`&}G zS*{Gh!2{aKpI%$&oJT1G;~(TFuca&x@1!bMcve%E$C3c!@G&n7^47E#0wzA-k@I#eR*KpK% z=NWV_zoDwa#qot>;z}9CPjZnqaq*K}+|xE+4m&L8e6qw($0GlR1YkM0b}%oeNrp z9mv-iNL5&iaKKsuxZeg!si(;i%g~-&!t1NbFn_UI|3D0f;(7SZ3iA#1qtJ)+C3aoxAt}`|W<1n^9Ja-eajQ z20s@k3k80nwsfabn0a-e=@_2)uHbw{nM2oB;}MuInT--cYdH2Mg$jwe7&fpqrJ}HW z4a5onJR7kCKs?LAh}g^K!w!7tsz5cJOjchv{9EuwDtWIb~y47*Txi&24-IP^Qi{pfdqy{S9>ZlUl-#DnE5hMf+*qTgvO zYqJ*PfY<#A`h@37RlOJ$%qSIJ*iNz|n7Ql-4tB~!Z8AuZ?*SABzOrX)5D*DOVhxfX zxSsIV;5cwf+QQrnrWqSU=GC|azn&2-NzJ1uN^!)rk&DNFB8y#}M^TC(8P7dI+@UB+ z&ups>_fw0Y!J4CJRUOE5U(jGBoW-oxd4mS++e?a)SjuYe2^vsM@BBdn>e}hC?#B*{ zDfFWSI&)y73^_EU%pnd|h6BBe7FY;xPqe^vKqWZE=CX;kL{6{*{ESzzz_{=t2seBhNZ3(4eQ>m3b5FG3*Y;^hh-hfE81i3q8q#NpR@p;aXqsS-^!GgxfG9Iu!_2UloXa z=k1~6qf z!f^_YO?6V?es**!S&Xhz))^hu`E!6>T%a615qoj;(6%FNz0zY#Ne04(me(fKK9z|| zwB#d;Fl;iU#QaWYH6-h3EXR*@hE}1h|4h(b;ER*7kqy{d8PBK;g^Za+Yk2*8t?_HA zwbRe9PiyS>%6VE7yw^X!zTVFp0|Jj3%Wp^ftNds?Rt>%}VBpmyAl&TcNay3r?GW}t z!6bnW3?Ji1Z{+@L-wQz8_I!fq4!un+*bj;7%E9e1Vo$4VM=X%;VAWYK5)+)&9iCGe z-|n)6v7_{bcKl^TfjvAo|E%}g>3`am7pvvZrBU8*cB`ZGtqVZ-A*lZ$B!J_2nD1_B zAAg{#hYwb-hTGag zIVN`-M)!KMIIgB3;bC-b+T%yA4m_VMHO4pHf zGLPAM0KlB*hRlx_lsV_vh~AAEXj9>H%J*q+c> z(`MlRUWysSg@7oJ+Js&}yZ5fpE})~|0&Optw%2(HkJ8lSksb2UMQ##3=5P2(&CT&| zR@qJUKW_iXk0vr=&6C@e2P&6CT(Y|8?J|MgK7Wab~=V4wm6p z@bTwAndfj{&i7B1?;ec#`{g^Zn18%{2WWtFFnRBeusw-M9{mbB&CocIs48Q;{t{-; z=0e8IJa9X9ON`&_#F*z#eV{d~GeIvf@i>%bW6b7A_<7?h7D0OzD^6ENPy!F(u2mCa z5hIsE2u3Wc zXjG2}iF;*Tu8w`5y$kz+|Hl2m7vO*WQwe+`;PX$H?*jZ&<$Dj{Kz={K9fcZoV~RB^ z<4Ga=4nb%B5N@1*UQHg}31;HS6i@J|^M)4K{5t){C*}Sm)9R>%5={7jfxV zM2fNqQt5;2(Q0W~fb%-+y>wttRqBz=h3b z>^i|?;c#cB8%aU%R_2OP6x+_Zc`|mMcUyB=r&zrYd6r(sM};~t*K0oi)qx5L%cXRR z_CXo6R`6v0+4qM?2=;Asg;_Z5AyVg!U(Ii(vraq7_x+gY$K-Yf4`a6ABG7@Wha8UV zL;g&Dl5{;p)%tYMK0n}9kPhr^rGuiZz0aLcZ+Z{VV0MmcaWLbpvWI?4#9(n8EW?f7 zWAm*FK%DN~ogW;boVs1=I$ZLx8hdM}taT|D65#o2r-Y{*6{o-{um$v36FqdNh9!x8 zyGSMlJ+>HR*Cd#55RE+I>w8GmMj& zpi2Jnb6($fS7g%-hV<2Vh!Y(QT+p(YUvxk|MD7=;|KO`&m}Dva&h>+5i%ql`d=1c) zMG*w+lwOs6j50r;F>rr`(n7<`Cz zB1Is53?yL;ZG`oKL51{(T2c{41B>t(?F35*l*BoMrfp*lu9SMmZ*f%3Xdbo%9V>yq zr>D-60<7Zs<$QeZ1F6n>_}FL4ccuBCmG43!4i}Rt!WCf3KYcxE@+SwEfrN+}@NykO z3BHX={yI<&OojXS0$Tl`vvnj1NvaG{v|b5oNC@)0AHjh~*34lRC*gpl|L^H=ckeLvlrax3#UmG6%E_2s)`er@@F z{xR=ahd>5Ka^%2~{J?(bJ(phIUwZj+zW-=%B=6~sVLOGSP7HFpqGRPT}nl?#a{oR<7 zOIT%nlWqRmM-R`x^U=d$E2sqKTC4-)84f;ao{vL*%*`SBTy^%?3kKUsplx&Cz>2jN z;QSEVLgt4xK7v3?k4uki`^cdSK62PcsD)KpnG#J{jBbn3yzx7K-^rz0HrP9^W zoVzz4<;+*s)Y+G zLn67XhxwSepFk?3vYKp%#oZ{_)Q-Q79E7P22UzPI>?H+YuGC7v!?pn|AEOz&3plk; zoqb1a`sUe|GXJ=Lns$$_;Q}$Lffb3Asj+VvGjOBamK~sQF<1Qv43mFZu~%68ABSzz zO?-XnvFRpjv>rpGST)C!N3@b->Nzlx|6=ewOST&L#|jk;bm+YeOXveJnZgpgtOd9? z4-$k(a%U^+MFFv|1MZ|Tz6v&|XK3u6)@nL#F0rvo^!c}F zo0(>_2&#kwI#$(vVk{g+_wn!?yPO>4`L$fd(Q`He2eHGd_a1RSnAmvyW5cd;Sy#vw zJ5OzN;3y|E)bS(rj@ZPcXTe)Xv#~CQC^Jhklzhnara|xrr-oOX`ykE%E(kEng_ z4X3Gjkn=?3X+Gc$sLXw==n`Ev;7aFu*t3r{v%pZM#^7l9&B#h9u7@!4kxU+8kKIAD zt!HYp%?2cN3@0|j+qs78p-WCq3|psbs}=*<^I#cZe;Bnyk`oAOj$GjnntQsv#lQ-54%Qv$p1Ol6G?A(udJ`bc+2e zcZstqw2?f<^;wtr2P$2Xen=OVM!51A|F=$?3lpH<`>Kn=iQ{er1r1FSnjgWrmwiwU z(+Eh6QjVb6&ombNgRd#XAjjk%M}ud}Bzbjx)~3a~wtZW_<0v``*vfp-)1D9#HK2ISY zu3Vs4P5$7t1PNu=JHm#joF7liJA1JARrn?%#?n9Qdi}d_y*~7_$@5y*Ya-KC<7b`v zNP6%N-XR*oR*`~4iz~FFmGm!!`r#L3ThWs>j#tX=Z3MBYYw>RMrTE%$&~ZzE}A=?V>@n zuk)0=Z#ZE|u_IvX3r;vh0*`;*fC4Nnw|iVSczLF>hHUUp-~f#8?XwWjh!O_b;gI%zi6UEP5q4sAN)C)W5k!XLB6&Uf5irYZdg=~ z!``e6r4m7A9rJqLaByAq0I^c!<*yoJ;1QnGZm~!9zAZ`}*8r?5_gq_83T-6#Ya+SZ zYRI(57MLS~RIOqwpJj}(FPgQYI+_#Uf!8J|WkAqCpDhh_-=Z_RGNq$w=nAPM_Tp1T z6Gp29C`uF-5=|q*Qw52>F4trMH{IP$_4ZlBMwEZK20^Q=fye-cCCK)HXZ$~Ke?E9& z^^Xz>_$5jtE{GZa0vGte%{mxA*XD-tcu}`~fajX}-MlvbuBxiOHPX6_>o$Y0tp3sb ze;EO5k@9)Ai!*9Ayl(x0mv7e;CJr=ehHt;~c>~(9aUb9|je;_~#Uqp7xQ3;U)g^#* zJV)C2tZSbwz8!rg%c%2$9SvXocIJEq>mP&GzrmaFbTjy;b#vX?1J?~-Yd?8Ebh*vI z3dm^w&|`krNr^LwVJ`>MuClcI%l9MgzXuLcPt7}0`Bt294w^@Dl z+u@`DIQ(f)p=>Q-mPMT1dZ2%7Ewi*Ink$FUoA{$!}aQfYn5+ z$q!!>*_^YeK6b)IsrA@Y5h1Fb%<-lc*{%Ra=;K>*>$t$kf=xt9nB?F@Go=Dv6a0fK zk?R%koV@262L>lZ6wnInX@13>Qx^E-;2(FHbeYk{@L;16Q3@l#PD^&mRkXqI9{y@I zU1q5|{2&NUrAr&DN@I}=I#_{1CV%y!@!pqS%-+`vy+F?YczpGqo?bk9^#xw~FE9Gh zNH`@a^twU4vuBXt@rC-sNXs2rIKtgeJ$}*CY9g%_l}Nn~pM{qA8oB;W{liW5#;aKu zqWN|D3K$xf<06*^FCBj)PrKy44+31BzfR@HiTr5tV;@7p-BMolcw`sEM>h>W>DU#6-eui<# zOyrhQ2Y&LcyVw~v#5}Rx$$vQTCH&+pCl9o0S=2h>s2077)HiaaTHiF?25(~tD7@J zgMLgJr?EBm8$YsclJBpFr=%Y4(1gsYIAe75hgV{zGdw#Z+zTe5tgS`EdYmnx9)O|n zpR>c=8S`gt^kwED0c^QVUvz9TFTQA59euG@y8eS%g2T411_<1^4cxvS2QMuhCzsk3 z%lVHyR+7kK`bX0>kKAVT;%wH8!gZwwm9d(gKHKzon-Qtm8*}#7iyta7lsc+_&A&#> z@n|^mqD|xWAvNt54|)aTIHL>O-wrX*Y;%hlL3OMTqXu!e=a5nypTT9p zj`RbDqICGJ7-CeKB1Z-*&sc<>c*gD6O@pPSA*h|+M#2L@h@+oJ41QQ_PdwM8#ac%MBtUlIk z(ZRmQ4~;_&t+!?3Yt12DU2Dx@KMpmAJ^i5>hzloTZBNkm@J?_umTJ8@f(#txr#WWL^%bX8pj9%n>}iv0OvA&@|$=M zZSZh>6Vc-5<#amxGMDgh!}s75U*@K8y>{iDOe~4rz#J9n3w3j(-TIr|H|Pul4}Ykc z?_yalO9qpeUnX2~rYpYHBhY{u@scb05pW8H2Yo0n6R<=NMZ0|Q_!rlzL-IXz)1kVl zt5qerOKj+$V7q!1I%GIBhlat#unq7HJfi>rJE~bAdX!QQC_k1(X|*#aceyZ1{9} ziHYp#y|Q=hVitoa;+7pd-`-=48DI|P6`N%29%<8%)tL}>_7wn$xJqDm z@NK38j>T=i&}_L^YQ7~MXj-?o=GF92)SMt!1YI@a`0i_QOepw)A}owP?kVl z*EO$sSlvsFV_oK`*a8Y3$T2KiBJ1F^Fmq}!&IjBjnr$&PrdC`6U`x0J2Dk+J#ngDm ze57GpG?40$a+0R%cP)gt%#>Fd0x*UBro#Yg&8QL@O(^AAH;A!HP*%nxAv*KJHhb8W zE}?=te9X1VSYiRQ)x)YF9AuopfpA>VusVpk0)_M9^wH?1-vRuSwz6zHee z3_T?V31S4j2GJ}6gTpYII`>!+p6}gALPmjEm5`m~aa6pcl&_JB?2<%5x6G!p5=NTD zNqqS~gq_CMRxJsl!V@4Lh6haeKr;}kB+kG9FyNhOYJ;el@zi?UcKL%-xB|f}=sxC) zP2>FHF8kgjV5{pKmdyF5*X)owX3#qkJPjnEsjy2-0#_oegbaO9c7R*YrP9lh{y=ea ztl)HIY9Glk6ldaj1#{hxSR`Xf+reN~h_P?m)G|+sqAEO{8YvW3+5%tJBb2I2x>Uo( zV3rc_>}(TS+?c?~whjP!c@hsDhWQdSd5Nz;g zym=@G3$f8@#pNO&&~~)ZKFBv~q2dA@K(`%q;J{j16@J*a^l?Ci`!OeR`&ID!d+(gu> zkji}eWyL_}C=YfrJc|7AeX8ZUbKja`;s`otWQl<%R6W7Jj!z}nRqIA7!+`OP?Kb$? zYUm&1_tYMq7=Y=yn3+`1%dCKvod?Q6PYh8UDxugs45tDX>41iT1V;+LI0~d`3gSo4 za-z8+c+l6i0`}!HfJ=*$qV}XSB$vTHfXUGA#f%R4#1ycAQWR;0b*Ci-GYMX;?iV3JUQ55)fBy>M_X7jP&bU_?)|ZfCn51)VUTK#Y(F zMeO$G8e%sl>0CqXo_DU_S%=4-1+DV*05|SJF87u|JC_j@)%;zs*dMzVl-DH~4EM*b zhzG9yv!`L zLZ$=Cb5~k{HBN1l1@G&pib_?)QkRK0rZAUKIOz5U`7iu0bempDw9=?gXH@2f@x9Lc zhI<)ZHGYlGC;A&L#wOHrBo0bGtrXWa2(zh#YpU0bFJ4@e*W85*h~$L&ZqMdKq&@+a^C;?ZuMkGyge%8F+_XvZvSLV++@u92wC;{zU#HiMUi zSo=)SHRE@&hvmtg*x_ zwGniEH6OSOt};*xuaXiO1p}ZC%AQf2aug>H7zOlXzV=fY1#QY*Sae5mqBjbll$fGo zzoTBBp3C&Rf3{cw^Y+W@HmWhgf+_yY&R!IV)Hlnlr!3`m3GIkXEef3Xp%e9vg~&0` zSsFMB#84xisr!yHGFzX#eXonG#KaN0CGo`RQ)Rw)(4W3;+nT5YvFRkQWviP2Exk@Bck zn)uplBlUgSSdBK)7+FOmF+MY`<(zR?fih%x<=~d-7I$Pra3gFJMn2naIB6Z%1;(Xp z7-TwwK|Q=1+t|Y8)V{MCSOJoLyTY&IL8g5-{xTmlU$XZgd}zx(N-*;4RSVk&R&dxY z%q5de*JQ+b%mSzB{2uWC0da-hjTL8!N3lsV+ZTp0e{ zOwJ+NU~h;dWEZzZ%!T3K%?kRUvZ(hUu8zfAdU8tY?ffCMhT9k8gXPZfsF=j6FX2+| zTZXxgksa)x*qZM42tX=o4mbVQlt zBQi6ZNZ?KmAiLIs5o|JerhnksFwdXcF-3Lq!JYB=<6C=<+7XgF7E#FqlV(w~*a}W= zpqO99#toRtTGWRbE)02aPYXT}iMifABNz-K>5uFo5}X|(lq?V**K;pf4?EdGZu-3% zoaf_o?n)*HMVkUC5TWO*C2xG+V%2K@tl4sG`PXl2tnxIV;y%>DSq$QQmkkY>oY#V| z+F9AE&+!!w3~x~MpGW?vJjW5Uu0trGrpuJA`k_24sE`c`D#X@RdFIDz5mcfl&{`k? zvuL)`zlr0+wHxvQx%cgBfkE4QUZW9DD~W}&*%pkzJQiV?%!A9Vu9Y`fx8dwOT~!zA zDsJxV?+R2psH;@I82S~?2v-7vbC3&sl38-&lo-x@b9uE{5v?vSQJk{8@JPfON34{ULCpI*yKCyNDXr&{z@SvTZt!F~ItHdO&0Sp$q8iYh#4RUiJh;p#W zYPTA}`h()21Cz*DEh!FvbZ=dH6u0M;6Zd`;ii7PD{IP$Ua*>eUo4G1%HqzRIJaJOQ zdCsATNo=f&#KtCzt9peGT*of;{v`al#Wys~oT2*~3cjr*zL}%b&2Xii;d)F?dB?mq zkadgWa~4Op>^VM%;7~iMA{&`m^vmlpGY>BZ-;628%=9mIRhBp02aYQ)@ePO&GnQ?K z2fTsnHmj;2ObPC6a{;vlPLZ9L7!a0X)4A3D z>Xh8>%VXmMr3;vwYCFd!v$!E$R2z^kp1QT7GqWjS5HS#W3x#lvfl({QkJF*7rMCjP zvp`zh|AmEs@pw@rr>b4jln};Lf`#A~=}zxdwfeh$sog1G9br9CA}&lE4rmi zD*eThqK_`BbRZH$R&YRM#YQ+tGN6}r>VZsPvy}|N0nw4=<>3QpqiKM^X)nJP0kfWD z2*-oa==>7^Bdq0CW62x9yh>)fzpH@u`MQdmJ4%M-t}1>X&{a<|1epgIb6HR3vW8?} z5J{=ItRWeybYCLa4_MZe3{P%Zdy-+XtkLhqE~}KvvML!^)+NbM%Y)Dcp|q&-PYyx5 z1}xkG029DI9c$b@3m|%uAwU}YpPoQ6Bt$88))5NPf`n*RvoA4|U# z)`Kn<_)z?Q26hg{9?5Ml_Hd0jw*~+L2LNowyR#naeac+@V?%S$8Hy#^`#gws(JwTo z@33s5OOJ0OP9&$94KrqRpc52Kr(OrICs*am7|76zu7f2bvc%3>hXnpK5__;Ju*x!S z6I3MBP`8v5(Q2R|twvaWxxu~hF`nPc(HKcuwR>ia%G94RLrB+eOc9UVl1l#+#KP)8-53??7jDJL<8U&;nSqT5*;6;KrCq1XAqOEm<&o6x2>fLI|O1*z)!N8kC8 zcgD5~sRFlta0LS{>|VPIWy~xt)ewTejCPBp##eF!pCnO$fBiM!t{R(Iz(^xm3rjHE zIY=&xjgg92auB0v>lSNXn`fZ&Yh)&r(N@k`7Cl4qa3|$@WKF<@yc5zq0;n zF*!&y2B3u}=)o-{lH^6>nF53`nEd;l^xQ}Afz|h&j6syF(7&kmSEcMgsn)*?%CKWw zP{yPJlBV1}HAte(?t$bQAQY!Fn{)I$2n5$&q;!fcNJM|G>l!c9F4=EO^JQ!YOt+|u zZ>!u=0DvK?$tO;e8&A7#E1_#;FX;%sWI-RC68Ie`BF&CWcz1bt-;PIbqs8E*DK$Sp zb2*D?LdKW6KCYO_72Ha?=!StZk?3Obp|V;-b3Dd>TGgYjU?`g}VJrM=sw>T0m>)ei zKT6m#v&unyGH?lxyo~eU-MM23WTfk>F0S|3`e2pJf&4+trSeB@k_H7D=T*Ax5E9l| zDf5{EDxz_Q4~{vw{JmxmLL!h)2s~c0B}^@)=_J zEK5=IxL(I~fb!z;*Oa{>(L%VQ+|C*d{*(qNoJP_DCi~>M_5q#!>dtulyT+!=%Ea8| zaY%YqeN_ZjC}ehK<8l+eRHDh6kgGj)zh{}owxm|rs~UtFqo5G9s4d;#!it9UW03iz z#;S^Hde=(~m@!dc4T!;ih;P`6NDx!xVK$s3CtdXqxUr%uPQ%j zEw8^S00P)_(T5Uh5oq0&WqHvNcMlEgYG9t{m4kXdIoFJMD}GU@qx}`*9JK9we>C5| zk4M`9Ku7mA^{XuXSpz0%*Zj3uTQr9#uDE-)S$PGi9Tj0K6hGMi8l#DC_lzyNkTH_n zKE&*2@ePdIO@C+rY-R-?y096*F=`H-&CN>CgjrR5+!Nz7C!^L410adOTsgQ5(X_e% z^XOQpZmceLgP!0ilhSR9Gho!94_!IXX$W1DO9QwCl_k7kidPOQABGfwZ=2S}<7ih= zWrY@oUT^4Q3%|nOUdIJM8gml<^P>@Wr{|#j+_j@tUhtP3)`D}?M4#p^C3<^bq~otJ zMl#pm_lD+@Z=x!P91&+XqdN-=;LGqO_xkhPy$sI%o|iP1RiC_Q5M|Z<9AsJbNhzxi zTtL8P^(Dt9kF{%;4GIohZr09DH;ieMxbhWS# zLef9{fkG5&DK$^pGV{a2WC$olg55%nzrPptylge}xgV)E-dFsZIy7`M_@Bpbln zTe+gPbYxnoz{cymSMnkh!(9)-=M=3rTsd6!#RNCGi1tQfNt*GMw*W zDMRq3Whsm$SPG0mJ`dfAVW<^hvvG8$^vmn_EBQbU?hq5JO zK|xu7oBahA{SQo!gRQ3nnsqme>Py4k?&LFmK?t!6GrW_3dtJFn#RveyN(bv1uq=W! zGy-l^Q-&hvk)Hd<9F&Z1W{3#@W^u7JE|PhuLG0q;hmhpM zGYB+AB9s}dAjIQSc$wUap#3}pJexxB#92NZTCf<2ridfS;||bL7zx+zG32e4WSTyr zomm1{{R6OLK+W$6Wz>Zs`vh!dH?~)qm4HH_*P&xBf3)^Q(tTr}bl>PpcW$%hed)d? zDN{=tP30W%LHx7{Qd5{ETOW!G+ z*!IZY*qRiRwBf<5Q6f~}6}xmzKRKe0#cg%`K><bT0Uc<{0+z7ldJmiiZqmNXNsk zR9_LXI36$dD2Y3{2W4oE+KNr~Z#oXtr_jIx<%XJ&p<-x*|3*B0#Y{B|`^tTqxl5cH zw8y%mP$n^1hq!nUR+?8cRr+64QLQOAX#qFOe$5inAxtpbpco4mcYXg_^nH~11q@^Z zN(m$tuf`?rI3_N+1XZYb!`NH$ln*!h2wo6|N6V(=;U0ZS_{mUKK7WTBeFXFUtt%7) zbszw~1m)89PlKDa5HFa1F|c{8q`(zl+8mq@2O#)5t>MkS003xZ#13~7j<|LoJ|T+&BXG%gr%)sHocT#Kq%<3 zG<>PL6#Ev7f(FEGkQY?YxQ}XNmfa|}&eS_zVen+3*;o2ug0B*u67gyjuThjW1W`lk z^JY_L{X?FhLZwRHA+{fMI`!dW5r<&H+>Jv}Rxk*j!S<(eXA^k5gpG@oA{t7z41# zxG5hak5m}&1wXdI6>_ZQnbmG?wB<(x9P07c^74~>u@^Gb$qkNot$kb7)h4!19FyN- zP596~6P@am$~hL-66=}iy)`Qw;Ns~^RzObQ0#SXOepuvc^4^R`c|F?ZsdOANRh@0D z@c8j6>wuIkszs>K(ByZ-Yj9|SMH69}&=DY6*LBta0!OV09Y$hK+ZK0q<=wBID>qOmTWE=G1G&7qD78kv+ z@FF80QrU_5@EoyPOI0oWw~LIgJ%_5e=dnbghgf>f6r`3zC$}O40SIJ6nHYbf)PJSLQXOd21cTpK!p*n(p1|OO|pyGma!U=?0K7*JS}-P z4Fm@atSU=eG)p_QRI19>+~mJfiKC`jSR+?js+x@~j~&ba4~GjXn=4pVvauK*azar$#$t+HaT{zfXjLU#tyVtoaBZ9dPfqV2MK}&(DO>fSCS7zD-4Cl1YJyZ zX+`O;TTxaW?&fVVAev2FG+vYHElu^-^(3CMIuRykvpK-*cyo09kNK)Sj+~o6_M54u z_#0dZGd(}982@jp^~QrktD8Me(XlWAnI(|8;>yAC^k5iRosF*>J%IMf_w{c;VPn3c z#rW-a62Y_Vnols#bjJG8~J^c@8BpPGT|I3w=~?MuDX=QwnTaJ5Eys2H|2U`FtD?)hg@uwqrdOLC4p z3@>vKvigdt2CKW=$y(rUoNvqhm10#L{v1)E;c!1wNyHhtRO<<}EL*Q#bix1lz>s%6 zejzG&k8DCerPF~lPFN)j`#@b)#kh*F%d6vm=0v#WEcm|_oHbh(SMrbzujCV+f#L4T zu_c~?k$-|D#{VN1CZE_zN@gK^D&?alA|!$DMwV(pIHHH)fO2qYe8WrObkdcZzEvO7QWN*{$bf4PMW$ zepw+CelG^LW4f!ZDM6@wKLaDA>rFt{8;?S%iwuXlY z)~F+*Lv%VKI`m?z%pMIJk_F(I4e3_BlxT6Wr(6>R&u!Z5>1Y%8QK!xAL>~jctm5t!`2l-FwHhY>l6b@ys|1|{6+W$o2w1RObXD{!(8$e0G2Stqj`g0vu&x@2ocs$ z?3Nl~(&}64EvYE62s(zUc$i6}qn9N$GEq(5fQC8X+VTF6tNTAFqq%s_(TiVyg`1n0 z@^>VzoTwkJG>$NawKI6;wN)5D&jG}!7_6u$s&z#;Y&R3K%DL-|2sj6TdGFzB4A=vW zU&}OFMdAMC)*pCkjgy00+J0cs^j)(~2f1f)^~~bx?d??yicY@k8h~|m`8-}eQQ*Go zn(SytDGdaP1ZLI6(^~l8KX|JB|B`CIc;@-$ z!kw(V*0HLq`ah`EvAET8r_ba(|7FXa9@@j5;8wb3o-}uQXu+Kx?r|pyp7nX({ZFm& za~*fOpBY7@Y4VcRZEgPT~$6gjPJ>Cr8iMZeSkl{upnAVUSzu+ zSGqmf$!+z{6L6*5dQ9c^y-ejy;Y#p0DILBGY471m-0Qf~J$oCtYd=@|^yi%`J@hPB zYK2?s5>Js@|GL#BonTF4h&J^W4bB?M`Ly;7RBoXk?ot&>xEtSoA zlPb*o_4wJUNeha%#;;b%`jMqyAL{>VS=WCN7^CrbcC2ee5n^8xO3!(WHx!gn^?ylBlX(V2&M5P5L1ODPvf4Zteq-M~8xsh?uR<(?LY7 zyiQ$mTy#FuA17B<&O6qjc1#3m8N40o94k@}U??@F(*(BJ|!yoiR5qfJJM;g*2|_LjzLxqS?C?+W1(dG*7c?1EHm z1Di~T@Xl=Gf*rVat_UCGZN;c4oJ8>wOH(>gJe;M7frFw!QQ#OouHU(f%$~n<#dsOJ z$@Ovy${5~+-z(n4~)6Q&5oeC#c>*lqNe57rhsP6)h3g<<<4L?R! zCTlhr>Hzp}|MU5CUxh=yvB_q(!D+$aur&Cw>9r%ox>?=G^V<|&TMgcL-k-rG8Z*Yv z@$&laeWW@})Ez-G37H0?Y7Gzx=bjC28TrN)+iR7`LnI3_C67bE=}P09j3OFm7Bs%O zdmmG7`?;6OW~h0@3htv@NMpNU)a&M2#epoeerpc>W^zoLZp>W2Z5GLd9NKUj%MzQ} zo5_7=r(3nv${U-Av1{^eMrh>LW-C`>E4I9?k0GUv00wwyXcjD#7+GU@1`D@)10~&L zQJH7&Tv<@D#Ms0}aec!AsT3B@+QE$2h}E0ikP=8UAe1`xuyAN27E!KV*Z?0iXZVF! zOUDaV*`fn{gXPea^C*lj-ZQmRN(ipWkt{q)&_$lQal?pgDF%*bOa8af^1oHMNss@@ zykTYHscbOvDU$`ph)J5>)PSI6PD6{%93;@`4RUR1kXA7453(IrkxR+obCArZ3{sx_ zO6MM-JNdxUAc=_f1_=fY3ZIq&<8WKh7zGecm#tkxO$2p@OFDeK8? z$ndhu5V5)7Njp4ds8K1xGBKoiq9-W6As>k<@*um+mN9Mx69lN6^#LM)2hM;q zU%DdTQUP^ovu)ax&?0~_;Hada!;n5Ohj|3Wpr51AAV?mXR*+K?MA)(tU?5@~4giVr zvn5iq7@|cvb2Kp%DTC{8z}33vJU#{9MFCO}8dRHq_PK$HAi~VT%9-b=TIhzOh*yH6 zSAY&f^W2ct9yUP>ittX?2+j_Pkv%RC=s#>D^LJLJ!1+&JxqGOt2KAUhN#UW!9t~w6 zj&|s^ZO#-pmPvroa!eA9fm!e(8x_rrHAmt;Pm5*3YCH~tygUdjz3>Q;eNnybImmt{3w4ZNqOQ({m-6l(e=Gf{B5eXpmctB9$l`({WEx!6KY$Hrqk zs_7d=O(#oiubR%KP}BL`$szMC)pW#FsOgz%5pyf^ycjk8(0SB!jzR0xbb5dg;nV@? z^Qq|wRiS{%ml7S&cA9ufPZ>s69~i4*SEe;!Sq?4h;%LDeoh}X!?`h4dk}eGzi|R7= zh3Ya|*g_ZI>ZuE;AzFuYv(UxIvQ~8YUaKr1Ru4z%@Hh>oE{<*uy3=@S3L9t&+g(5E z;-Tqc%RPt+ohmsth$?)jwko+QA*d@SmX4NH^4wr#jJuOoJT8%IY^$A z^I6i$TO(IBbV)0xb~vo3#s7s?4v&r|7FzjcUn{2u#{k?fS~+{OdRn<9d>2}I9?g^r z;1yu>lhDfh54T!*6gaBwxv!OHVjgUeyGf&PtYA#FH zIX{WQzvY_NBMlm}bL$|d^eyO@;KfaQozl0aQyPaHn(ZE^^x>Wcd~4G>rEmGWaZ1Bz zo^?vYh%#gtV&6ab%2Rnx|2btf^_*X{Q<`8yPyJnXN*8-!JU`Cgnqx@f)|7}*j1>yI`-pa?m@ADxcy`64pgZ}tih*DnIEq#vR zXF^H1rI*xSnEylJl)kLfgTpCZaz`j%Ko17s&*zlhTFxCQPU&^tIG%}RJ(|WH{noHr7=5vR)5iERLc1mXs$dh$Sdwv~eMq4sof4~2Fxg&Sr0=fvN zw4uUqN>jvVpQ?*b%u(Z%-Uz2MI&)9rhG)p>P&u<5)RXClTp5-~Q$q?_Qn z1wmoYPsQtt<2q2Y=k=u^L>uX5bUG@jFcn^3G``}-mvJ)R39s?-q;oNzd9UxXLlq7{ zEQjiQhk)At^3LC0ZSSb+5KLgFxBFWMQb36TD}_L7&} zL7au1k0g;`>s{U+Aq`*I9)q#miI7&d`eKB%q?>a|xx0s6D4_0KQm*8w?NTnRX1CA_ z6RHady;x3CF0mThrCc6a%H_JD`N4d$UCMoCp%+hW?Iri>g2Y^fNDG@}tGBtARgW+( zNwcx{vZ=dH@7OK`f4xVfKoca5t1b@rLL5XU0Hj2Aff5lajWP-fGyar4H~Hvmq#SWP z1=LnerEs-2Mw!U{0irRNQlhcEW0dShDwku7h69|M&rm>a&J4Y?Pwy2{NCycQksH$?)k#1eM5MCVmcq^P-J=w+ zzFYa2VG~r`36EM@2m`jZu#3gj>TrsUPzZmGf}(>8NZW|eI-aq5bxO5F5P{ty~CBAp55Kv-Phhd z(%wDV-hH9Hd!oJjT6=eA(bm`A-Phhd(%wDV-hH9Hd!oJjT6=eA+1A(I-Phhd(%wDV z-hH9Hd!oJjTHKBQ*|ShM2Aw3wdEB>>90%Kz91%TyZj^F&%#u<@CZdGdQzSV;ViFf= z6%Nps2s$T4+O)?n@l!s6!V?6p?>m9tMAe@od(;`3Njt({iA7l-DoDM_$mBr;e#7K1 zRN;t4twrXDZ-wB~hQz_tOL|D)H-`;Aql`?!Of`{_8L^K}c+f)_dY6F;erkMsMy6vt z+lyUq-QBr8T6ZzfWp?*l2IG6gd z%@vAPCV_NG$fX{!K`^qHzFq+w3z6UbPULqZgpj!qLWoc3a8U7#d{Rf_N~y?BI_#I;o0T0kEWr`mnExdt7swAc{Pp34&TuIgut3LUFnnNG=4BkP8BZDZbAuP*});rXigp`6FzK#}H;y zJO>c&R5q+Gkf1=&`NEPMEhrlfNZ&=-urxnuvLyRbHZZ_h*$68{CovMwT89j=N2I7Z z6hZ`>*;$a|ZH(Vd+1QaVB8q471b8%colfqqY?%Jem5p!~w#r7=b|PhCS)DYY7Rfxn zvaw)}A9q5qQ8qkvC>xL~D;vy7QZ_`lrFGbf4(>3;N@Gor7lN=g$xUkH3t!m?<{&Bz zhfVR=Qi>V>)6J4(Tb3O$Dc?eb_IxDnfE#JAAnWz>3&w0OSm>bwPL!k%Pn{^) zExg@A4*~5YN^T3uR;V@o6PxY?ro{H121p9YR_HdC1tm(7>-kI)B|rHr1ZMAG0&P?r z#9UzBHBk}?=aPjU!vAcyFfrw7)}M-hBs?XJMdUm9*70 z(NF%D#Vt%O8R)l#p1N{rmcY4V{pEeiyn@?k0 zFwp<#o^qL!l=9oyMEwi0#~jX3it{*fMSHcCQj{M=`j?&bzP>bO>&#dvIYgy7*5ojX zGDQ!Nfr6|rkxqIw8T&rgr1Q~k2b*B5JEXHo)G}(q{WP{-+=n~Y+6h|}>Qsw@Kwnz8 zudgGOIQ8$>eZC;KI$sc#fh|1Iq^{y?+4eXkiPMz4Ni7x{y_)qNo~_>c7S2I&<*|c5 z$0e~#`Ulx1JvOwhR?}%nK8uO17IZ4Wsy>R%^mV?{e)G6b_R_mJG3AKP=_;ZX>IL?kcvyrHzKAw~mbb-{R!M;aoV8;U z4x8l+?7Rn^gt9Kn3!WTL|1P67%*GG#q70d{uMfrOZp$Z%f9yZj$CK|3zX3%DH z<>kfUV{z^T$Z=J%E=!*<1!vHo97oEVVpnTHqdvun1!@*M6AB}>c|-Xmod8O-NdT#HwG2p|>m@t3#?5gab$)sa;N=9Vz2O%^m&do-d?5duG@nDe*a(VGBlwtvj{swMK{1&(2C~ zToZB@9GZ=Ea*T`(aSR9Am)6&^H`|Gj1ST-7gkcNCJFei3bd(Gey=lxsK;>8&TNJ$F zfcJ&>1OP7_VpKqMI_ICQd?%~gYi`(_X{J*LR&?;$8VfJQmltbAmJ<%cGW$jrCRfOA5-eAd~TZNjkA=nN1LR~GA)pm*i4vFD9FlaQ`V3A@S9mS z`NjsPCNaTSaF|P`Myomw%+t)zpJG60@YbOb>RTRtxFS=;aJQSIdT1)^o$Bhdzr6>a z*N9>=0H#?J)IYygqweWHhF5QX z#197mX?NNG3mb790JOaTX8^SYdr-|F+eK7F+jaI255T$P6BER_#~iIpnDjQ z3UC-uFAf9RUV!s}+5)+tzP$iP0yPC#^&(gpw2cLYvB0P5q*J_w1?^LP1@-dE;EYv& zs(j3K*10GMlt>ihb3sAW@E3eQ&|v%%MbV4H4coJaDaa|p;HWS^7RpjEw~;v1@GZ@1 z!DJnn&PLePuw5v8#70Tj=vXc~W_n3X3*XlRn@6&Y0g`;2m{z8xj}ffSW!=|$5kw}x zabi^fuAlgwJ@rfKy#C6cX|KM?*{eBWss2kIa)?*wA%N_Bs?jrAQQk1`b4NNQ5YSo8 zieM}zch-P5;KRAfG!4tu0*h_U%h(oeFut=W`gd4Z0XDtEd`UO3_7|7q{=~NelbL*C zVdkLq6Qo-@6(eJ&x=9awc&})#5DK`sCSTa$(~i zAgs^7*fx$|tbYH3lZhZGCT^Cch(uaC8O%~$V@N#M;1&^uyY*RyBT!xwAJ=YG0OtQpT=O89R&TOneV+ zGR;MtnBNZKro$`lxONp+K^$G0OmQ7wZQ$vgBTkElqd=6HxA zfEaGU@596~>;A~k}64zKQoThc+BX{z@_w(%x(@3tN-A)_q;sI zUYSj}|DALXBdUKh-78@qd+v%QPi=F{+2%Iei>ZQxh^?n)gM-Xz-s1KwMA^{M9tC0fm z#$HF&^4ju$U5{=O$(pkkr+b;(J^WKxa#3zjvCsGOW~=fc(&NiHC{#(1I(pttv}zxF zY!zC;RQJiVtncMbsK-6WsJzf%0G5_BggC9>LpD96c3RQ3W6h?iQ`v$7Tf>Cm`kq5V;oQxh~=3iv32!n|@bL7(n9Y7)Vi z1uyvEpA4YWn+^3~<$-GCJ+js6OGA=m^8d5qy8lv=qex8bb{ewz08G(d?{G`*w7DcJ z8wA$bq~m7{)SRvhTFJ5fw+N`}nb2}+^A$NMWi4>|wfXHldG3Mwa8V2|08_W&sc|o2 z8Q>NU9gD-yI-z5z*D9vyH$M#&?QMXe$kQOlB8Eb%jG^NPgrDHB2oACN&Hiic?(T?C z5?_Woml>g;!AE{=zKSOaWFv?o>$Jf`t~#}Q`2mm-a%E&(mRiHk_aNh2U051MkbpTJ zHrSU|_c!vq)Oo+n@w#vg?ISCVhX0ZrFyjPtAGtL@YFGZ+%PFolj@&>R_ELm6D(*_Sm3-G=5rmps=Z?Q$m ze$b1#>_-PWC)dEVqFdwT3?1&D)cW<`_40n&iz~PNif^Rkf6=FNiyD8i&;bMV!?j81 zlO&-xN=cACS!`f(SB;cZ+z~Q?%Ca}FKS`D-o)EjEC+t`X>A&R~dJ7t8Yqp%7&_J`O zx2X+ffi@B4B)}J(Lay_4;E-r8ewErm zY?m0x)T-mrTWhF}e=iW%L2wsglyrCUgEe&t0Ge$nJ8e}0g}Y3V+(l;RgAu|}b8@Mh z9m7xr6Eq3x0cyf8*hqo}Kog7^iTdvnRs_I1jZi>^bNe}J0V53EnsG~1rxgDcyM+Wu zxJXA@n)Qu8QRE}fp1XzkthyPkuF9}cCueo&pYt zTMNEXfDe2Sf{0Fo88Vs~a=R`IFN!(j1hS%r-wSHWC2?!sH=8-(w=&`S7TuV(G-54o zpeiUb_ud3hvj}zy3xS)^K6Pp*_Sb zKHlJ1_QJr0mcU7L*tKB5w6G-TsgMwxjAl-Y#yHFDReO7xPgwuM9OBm0fSdw{&{RR+ z^hqJDlPfiy0t8%63X3K8zm<$cv$U2#W%Sl)+WVA0OCz~=zK!ZGl zsH;!LzbtN6=MtF>6nPFZL@jtOwNM0AQxwD_qseK`&r>YO}v24mZ^$jFFOErWb`?uh)NkOqyp-c~VbPIS$!i+<*i}9c4 z#Q=Mazl$kI@rNVHyGsw408EFR>;qvFd4W`b123&nvZ{hpgJJzkfk5w@jc=9p0ZZuH zltBjlxJ8Z?EiL9lbMl6Rb@85#fZPMpWx2udt%9M*AzUlWSXPm0Nv2RngR-;1uqpyw zWdf809D}GFE{#&RIc+=B_5r)4jNaAgO=8;KVLc!JJrKG@Z%;Vy0F;@P!0t23v+?B@ zR!;o;xQkAHeiT5p#07CPwfBxb~S4BqDg(LfA9I&x@K zqB15*sdbrwfM>AM@b9IXM-VKPO?cHmCEG+2jpoC}QVB|-UcXyBA!J&8a(eu}5=O+Z z=|m%S&EWdQE#B4~>k-;~&D@Hm%7z0jGhaBGpvA?8{!1X55d$fY76lq!jF_fuPx?bH z1g+gNQ=H%EjR(ViAh_M)+x!~%H{fV_I4h0@k?W?2atc%vHpnLAv>@(&8&*otr|;&fL;jD!c-+4 zw)L=`KU%YlSBA^1zUz16oOx=CmLd$?ybyd1Re*&!{!-3)+Qo*ta$Hjn^017KLj0kW zX`)v^0p6%oLJF^|plUUJE;Sd;yJR=CfY+d~4U9I29-m=Y6^U+x0cA_&0n}HmiU!3)V`hGs#-{=;L#1<)_m!Vm-GjxlRxV9T#^8MKWUZtNBBPUP zbZWMGP(9KXR53HQR^oD%wKDAX7Q|{>D~m~Es`N518apMS2jj3)3Ky(-THNuZJ9b{k zyOL~`erny#K*8pzg&NWd7D&-M(WGi1+Q=mY`od- zuO9#BZb={eh~t{alEw?pb9|}kHZQy++&smEB2et{_}>&o*){$Z8y14$4i=CHZ6DanQ`}f3DW~;^!32&P7H3Fx^kIGP60Alv zOg!8&5ij;bKg=%)-Vsgfve+ms;lQPLyie-Lenkbq5?xVhOp1kEMd>jxN-quvm1pQQ z4gl4r;YS9afqV|=xw5<~PK`g9=YW-cpJ6eSH~ol6Dd z3-_8m1MC=Kr*nHqsc;2AIP2?UwZrFqdKIG8RbqptkcK4{B3blcCO9pB3D=-ir22Nd zmDQYBIZQ!dhbsh;*6C2{tzH0x5!}0RM2nUqu~y|P^27N04x9l!M`tj#JUKjlb3J*_ z$c8QdVoUozODqWR$~H;Z~+|j+4_TkE2ueA%Hu!Ff!hpU8WHvK~ zO0R@%ht8=88@{+POgAhT-{f&k+lWv0yyS{!_qEf1p~ms21OenPH^-j?a{;3zp8u~ZCJ5) zEMdN|VjK2yBvCYqYOEH7%o64YH(o;@7DjkmHF$W2!~Wsh2?DCYa#jkGo?~a(FY(rD z{GBwP%p3DILOo7xtYDZo>0kxPiiEn%{L#zl*k|KKwLvlWYbiiy8}zN1D^otG3h=s@ zcVp*)iMYg!2*QEuA-N(tYOU)%bH{%IHjC8Hczk5N@jFJO-@35T%z z^NYtp1uY*$E|yk!*RaTVGOFUnhbF&uynZ4x=Lczj`?yiS=0w1xuq=!EC$ad@e(r!F zuMn#!lPN1q;)8v)B2v=$HHs0^a4HwX%a#BFTQbprkByRTR!hA3&)t-**>MDeepdGK zf&?;$e99(1?KP|mt`#`_(j=GD31O}F*F+}kmw-1q@CIw@GS-3_P3)UIGIlY8O;#zw z>0u!jKy3)d1BYUUbwm~s9T3EPcq6|fv<_g7f4smdg_uPw+5qAU zowhV1>L9gPgemn{k_;wb=$@$0R^69K2XSGcwq*{BwG)8C0}E!fas*j%Hav)Rx)I$_ zBImEgGc115@)1CVl~TS9EMGDwK@$Wx5i$86C!*THDkTN?CqG+jAYv!>1vXXKyL5q7 z{9IHa(c9C6#5V{}+z%`lg(a7U(l1^S-UziC5WHLT`fQqZT+ApJSCJVYiE5=pHN*so zYJ`bMR70Zz*I>?5*0uo2ZD=MfMeH-%H{fpqFa?f*;UF{%CPPU$eaM z0-hov3}%Y@{eSF%8G%k>9O3b`ad~T8-Vv7@0@ZG0*`zsr{5b-FHe6(dLpE+fv`^0C zb7HBFFWiQuEc^?>(gp-i{=3~kpoIkpq%UbV^EBwMJV%3mZkMJM)R`%U?1nM0&P)pr zp7@ghf=CA|IIZfpbEofKSzJGd!IKRPB$G^jzHZ+ns@Dy;bNJZ&&$k*KeEe&RWa9-- z2jBno#Q}ZO(7`|Ye|94RS|r~xbnpv*u^R}!#pvLJe-%J@+H~+&pAH@TOtJu)uV9^M zWV}K$9xAzHJnTl2@z`uQWIP1xP2nQ~w@h&dxb!9FAvq)F6^POj^!THALrMK&psnXN zr<@ZEU}TlVk z!yoKeQ5v8}Gph*be0${-EWtQ5UBwbmVpoubkw3{?(|bKf)uVCHCq=SPwTB$VM>_ln z!)m#~Z$>C1Pv8Dc`>M(F!Lp=+)eO552|S4U>P7nmMCM)_I1W^j@a|NCzya=L=Cu(q z+M*>w3fv;S9F!YL1({*}kQ3SQQVh|j-XP6T1O4g~$iVm5rHLouE%*Dk0v2lfO|pOU zh_A6MTOXrI_n+aZN%!}dVp!Pt)5gcy9tB1872mu^yMObP{|2X0q>;=j1)=zR)qVZr zo^<}{lHf#-ma2X7Esz&=BTIM>MyODKsq+q5OMNhPS(oprp9KE-X+gB&7ii#@8+2Pu zgqt>fWJoR78qy1z)PSU)w-B2#|SZjj`N}v1Ju=(YfH*!MK zOK_cG2Ld<(H%&gaR%@P2yVP6yG_`N&dBnn6iKWKG0xTqIh%N^fma##c2MPEu9u{J9 zn0)zUy8t>cuz_Qf`6hY|3c(ZzfDPcmH&5?#zB1=9$$*LM8grPe1qxsMm6`0fdm}Jl z#ip_G-@OpxKmNBXoJU>lNq9XPgcS%D+xmB1aJD$%;sK&l4(Ue6VqNywj zGN-z~)|eRR&WO$KJ0vs#P3^ZkaX)3H9xqgbor3Ejv`(O(qhG zZ%skvaMGXDLR41pN>F(%8?0>!L0K{0R$ux5!tiBZ4!1IwR@vo)k zSG;U#v&pYVgsh^5uCj zVo+ix!SM`Gk@^dSwmF{5-^3h(Y+F+wy#XTQ(~+A&L1hEBrlq8Mp5|GHE)?@o(`(I5dGR!{m8dzIC`Eg<=a4tYi9UmlzFjD6k z*20KmdZ2a+gM~WHNyrR;ig%GgXN6eYEG&*&;s#Gk)~7i0NTdO^I<-PVi-iBWp?5tG ziI50uh0D?k$8Sjkgk~_r^(@1OPVBkH$00h>3YS#`L2#}WDk6e~f>xMU^dw89d~tnCi04u}j|D`a@A1u}5@_9vkue&e$IC33cH z08pH53>PO?u>mmzrINB}NoVv_l1>DiK-p0iwGC5wEH0dvs9)>QMB<=Ex`z+wav&~b z!OJp{i70rbDpY((_$HkgPJZvWW{qtn9N?mOtSIe1A_cfxrSQvYD+v`WYDbG=pwM)x zrh4UODl4~K-$D)4eMAkAx)1|YO_ttN6IF@}a!7g9w5~o4cCUuvq9UKanqyJTmECF> z2%v@q!!lLH#OGZGd0gNY5hpTRYGpk{_D!X&&1F43X=FWw29-qjN+m+-YtYw?&PvOA zUP8CXU zF}i53H7$)7M^oeICa1DFs#deEt#kDe+?H${L^#bLzE9;E?F}#=QwUcpUcT7kB|WNM zd>*;-fEK*|A!B}<+np+EeYF)RSF3MBpmZeHi|3Kik{$u7bmG1x8BUSgSNGq;t_V32 zS6=NqGd);oR|w=&ZGMJGDY{2TU&MNy(PJGiZ1c0;iW%FX21L zEnn;=TOO)bC0nArhZeSl;x1I@qjZ6HHAOs#%XSqkT_h>?UbA~dpEamuEtL>4k)I`A z9lCM4#z;YJ+MP?9t!xSEY3uaVen^^Y&CeZ@=D6-L%Gl#U$5RxF4h634ULqksO+ErFPPaC^TqT6tvc9m5bmn2oD zOZp@KUeCBjFsPdu?QBUA`5cCPdMdiTM5%qDXt)o16Ev%XvJ3IZqubZavC+rKtznT+ z7-K7$CJsWSU!;g&+u9S5Mx10GAr`YeQkAsMUK*;>{^~L~3``Le0tFMB=J)Z<>5Hh; z`{!Wa%CL7q<6=t?@gf4uP&;(uE`{UJ5HUy?wTgv@%F>EzmnAh|l~Y!f^y{!jYcp@~K24zT>WvDF z%S5jsnTj*ip$n4q*Gw$wQ6W{P6?Lr;c~Oh-QZz)}=Yp1dO9TcFHn?+!EC`!uIVdFf zu*5DZEk`f5?n7%jU5NQ_^rZd{J;~CoJC}7JC)1Pw81u%wM?KFUq%!(0WN#of2qpDm zL&|K(dM?|w90;{QYhjkLmN4WfN+*(G_Dfl?G||2~mR|96eg46b`O`!5r`7!FWd0Nx z(F(?p#fYT0H#tGxmgm@Q>J+>HW?$RYFO+qh%gu0AK~x9*OkRvvUoMLo)Wj#Q}~tw?}qvy5sSW2KZ=3d zi+pf9u2w@uiNSUpS4jzxisEe$SILq^ML~;!DJlMZJrh|;3XD}00UOy$Zo{tQYTT$O z83SDA<@kbB6xRmTl;cVO_Kr)-zMp<9rd;}kuZK4=Cx~v$Rd}O7?@mcAPmK^J*q_T5!+TL9i4!-x9J3W z65v^pjd8n95H2)v)5zf-%?1+D%}3yp+|Q{a60D47EAGFxSppgP41}X!gGMLl%l>Ph zUD$uaeb>6s`aA8vp%VZEEgnvJb?#2UI$UYqT!GADIoix^u%i6jQaQL|&u#gwZAK2# z7=Ao3giY&^me?RCMuoq0D`ar+Lm`s&xUwQgQ|FH1QlRezG*fPb()t$-C-Ywd~$Z#JNSON8tWo_pOKJME&tGWWLfs+?*{724?>`Z zi(a*N{wlBbKFCDLjGadb{enHv>BPxSQHU&z0Mc)H-)*c`L1jp?&qf(gZa!_W{$1lI=jf2#;11pB0HAgQQU5A@7^ zb5*^|i*;NCTn;0Va;b1o-=-S~1iR49Ui=}pzFRvhSZ4R=5r+n|-QDaZA7T%=J!k!h zrY~nR!NGm7ucF*4(hNIZSJb}J)V&7vV;aWH|X&~NJ2(%`FYtA~r7bX)B zh#i138r|yIYJ3llkIOzZBfIDn>Umn zzXK>UK#*>ZKd{4i1Rjom<)mPjWly~JzI$ii_T$a3`g{2B+kUQ#nOTd3!ru%1j$tgn z;~;oVReU$lw9`8F(W-x=ICF^+B|Q`6D%`he0WHfRV!%S^A@pWhiZtZ@T+}Bm1XWc6 zNu+$h60}|8+qUzHR8tBzs_8EEiHRvk4Dq(8l*?cXb>7TTc z5Ze`E=3|y^t6Y$JfC)I$7$lK^LKf&bcNIiKw6VpNkB2h2cBC5bC{s5QO?d_tDM>UM zDiEEcC0k)`lW1&i9nCGTQfyC?(FGN;M1xU40A^aGStK9-dBho94Re8Ihd6^xFy3kg zZI6Y9fdQ4}7>P4TH7f++5Q=l#>>T0@MPzY>Xzqx^p__|wIAMT9`dl90)ytO}XEFfY zfGIID;Cb;R6Nl-;eWZ$OYm^#YSStg$Ljp=BecZN}Tc&=v&6!H?1!xRiD@^BrMYAlX zl&C6LKb(BF#>P&cnc0riV!kD*1rbwHi(I;F3sOIvgAfy|L0mNzG2Pnd-v!B>L=G>EEc2Ga4 z1sS~Rhe&d4bMpCDt2lmyZPgx)%DQ6Ch1FTj>ulu`g6Gu1R?6`&%Z|)IS3GIuBDTX| znI!Q<4lMfFQ}Qi;)@OTfLu^>RT|W>wUS3wSeYZ&lx&2oCq8YY*o$bGEc3E}V?YGV* z*RA$WWTdA=)0@aJ+!iRBL4we+;d|152rbo;w~ND5qvZ};j6q}MfcmnWXJ~`C2(i8 zcI%1!E(uw;SF40JM$FuPn;V!Ue8rMsu$6c1ec%BUB8xK{Zhi0olgDR@Y?YExD zB&R%7E&1tXw*x68C@{bx&FJ8*jOsPebL<@&k-y=KJ-5%+^yzI?Qv_=#fCT^R?DZA~ zz}aPTmC7ViFu`oDhqw%RJj0^YO3KfSg;q21%|K=DP*PX0^o*zAYvkxw+4w&R4&Fqr zJ{80NPoGwCarp1Z3P^I8S#616S)}WcbUm7`SElQ+bbSX#UT=x_KJ6CKyPb;6UGe_N zVLOmz#38I2<_!I4&OiI|SQ45dV}Wt|v9Gn?bRTa``%=~U32vG^MbV4F;*AeaS;B zPVRykut0`OOMYjh#WT1-14H}tFACE?!P3ATcwhM_(H0l-Fdbfe_9omT!9;VQYbzbL4(?}a%t|q*bFolFBM}AJmZ~L- zg@8{17r`^*9+2Ap2@iRpr+0k*KQ3fy9I3$QbES8DK|8f5z2mO`cj2Q`d{mqxz2lFb zJYRapk1ew=8(;4d(ZXari}VL6H|1`I=u7DjAh}42zC7~J$;lPH` z2BS)oo>x6(V-51F~OH$c<_N7|A3fMSD@AG#KC#lj_sQW}ipB}h^F+`rmb zTUnjpaLr42qLve5X{{pALRr0#T&V!D099XRs&CEAhXw{xwSa+QQ2(}OK12*Ee*54- zVr=lqi;$DFYv#kxpF5{PAtTL3ugYoAxlB8A8Y1}0k$zw=;uV_C_C&Ci$Z1$nP6LON zbbd9mLN}*j6#)c{9eOcabDpphG##)gfU5-wH}U=U32UAE6|moNZdfZ)38Yi*6mG%V zu*He|2^o1)d@_AzGaJxBt5i|Z-8i?!oQ75mxWJr-WD}xasYy$i$!kat1>Sv&ISrSg zud;ov;@6-XksSsw1Q0XPQys@;$^_l=DJDMPXby9NS+JF2gy3M6!Bz3mCw)<9O=Pr)7%~;83R0J zDl8Dzz2_mU4mg73VX%Yp%_y)DBfs{+z8!Eyq}yIrA0Zh*e42qmYo9yU43j`a_$m&zVY|SAc!TZC&PXjdPrHJgM-liwQsBE%rdS-18RFkjd0m=?iG7QHs9VDbS0C!k z8z^tn69>MlrU-l=BIr`zLJd@UL^ZEB{JsUz z_&%zez;{=}K*T5`@SQ3q(^8Ny=|Kn<-iJV7{{^Wdtw4Z^yxAH8sH4I?%}#wuARwk$ zB_S%UK%fx#MS);wfdER&Rj_BR%>@Enn_&+CqTY@gqD~ETcnNzF4HBVvVM&f1fj|vf z9B2dIU$k4`yC)geVsz15Gn7u?JM7H_K@s!&#Bwz2`=IwSKoeh^0^gaz6!^X;z`Q7f zbOYbdEqpw~!1qan9}~U1GFvm-n^<`wJl`SaQ+B0vj>F!HDK9oW;k(Hk3UVe3WN#DM zI}90QDbgk{YXybGKM{Ep^NY|&zP*&YJ!*EgCzLydiU*(VDt9nv%UV+I<}%2mODuknWk)q!%gg^_FEsFb}kOXH}gU?#6YTH6cD`)uEwrKaowDztLqGl5?#_9PI=@eQQwYVVb55lS9}w`{=*j_Sr8VO3 z+79v86gW)59mpxRRRC-u74~5SORP zs!J2aoDSU@5!G%-r2NeB8J;tOe1>o^@!#~!K|UGek)_D_)J>eS>TTkb2ixP6)!y?N zr)-PNdE=Cu;Yf3m_2+zX$}*POneWgY?{c**>bU_Ju3l;*%PT^RF2yM;HX8BiS;Z+U zY3?cGlnIx5Va6#BC8&BSvF6Edqbq;rJK5@zg(ohD=!}QH?B~nph*h*E-`Ra@cPC`{h0Y(7GmI#lp<;59wE#%k1!f3y@rkph+w6NTgD!|r?m&~=KH_+pt0q=nGnJ+ z8hfyOd`&3CMF$O@Dg{t`Com!bV2yP?{`;A%Nr<+^zp$2 zUlchf#fM|Gr;|x@e87NOrwNiTqGSEo?=Q}mt9tV@5XoBdhJQpcIu{_C`#7hf{z&dB zNsl96wIp9P9iH9RE@}V3G?&Zj{OvFRlCzq2urZKxfI~?so@M>$|G6fwsX~%T0QsMf z#~oEqCck-nJjD!#`;pjyeEh=&Cr0MC(IK)z=#bivlk-sRmB1{mkYxiNj*ic8wWzbWS zIX`%{b~3}fD~|vR5O<++=yd=$2~JL=h`g0-j9aRVeK~sLKQ4>%w(?dX$UXAl2CjT) z)}ve5YyS~>yA0YtK6`!qDE%gWq`dI2y%xaJVXuu_THrJ680@ zKUH!xbs#-Cs+wodwOq_=MV6Bdr-U~?=duE9dz`c2G)OMm@-_!C3J#}bb_sp-DQ>MD zmAQ93B>1&A%i=s*A0#Y~rtGnJ{?BC>ks?@kJl;2tDmRu`j8s!gf=Vd z*rFMsUAep*Y6E%jAvRhy>^w&2i~qWQ*}D;Hpd%sF=nV=7iUQKi?Ma$Zb|xz>q!~rw zK=6@fI5{PnfGZRX6fPKvy6wsHG#7FjZ_6Etx+g{YSkUuC-9H_&*L0yR6gZAu=Y)#d zq0LCL?otcqs3o*G)&n;+_1{=)h-K5+jD#g;n9Gc0OH)Up5#f##lMNdH{f=M>?UF84`zXTf&^)=?OvdP8~KEZ_6;;g;HP# zsk$l0SACZRtezpYtJ|uj`LV(eZFdYvo#Tsjks>RcRJ}pFO^)wd8NI%Y9N%7>8GFg>0 z9^W07NRV`mrLzoCjd%@rFhSh@GCApmVr&W=Y+K1@K zulZAA__QZ35gm{(?zKzHl6KB5z+(fDEbJR~4B8Sw9kPksSl{u6+2|nf7~V*Rw000e zuFy&4EQy$7cJJXeWqN)MgfsVBp$8J9rI?9^Dw~KhGk~XfXM?4eQn(oq>qxjENtaS= z`pzdv)+&lun5SFQLRhAjJ4L1Zsi+`Sgi5vmA;c}@Ku_W zx<5EYs8dKr3!lM_v(j2Hd=kQ{agfdb0YLzI4jZi{YQ;&hid-M1j8`-{BjV^0mU3x}xenqI4e51@xS3U z4P@-KDK<%^)MBW_;&npwpW(h+v8kfxOW*nKEGUCWA=}Ok}Y6B{KL2yB@;1ZF?f5 z!?}Lr?TN^}5D||Zj5z63dlEe@jRyX^sL_OdcK^=Zhv4Z_YS;&+F>*_(X?7pF5T)kN zFDW&bl$uLQ&GVwv6=o5_FZDmLq_Vsqq~D>lDB z&>rX86r29;icQjAy}MVV*u+*}RuE=`P;C0%^K((LLF+;{A@yyp*f^eUL2Q>|6HmR9 zNK$Nce-IUDNjses64E7wV;|V5*bK>{lft9i`gbX3G87wXIK%#5q1aGnuGm=pQcsJG zs;4;F0TyIbFzhav0=(_+Q2S&_E9)c8>L>mrVPY&1N-B%qL1dU5oN;5o2X zLV3rS_c6va$_JfC4Yaz3BU$R2RZr|C;eng=lt8*eXB+IMvw6?<9Xea5Xrr@@sIstI zRF?p2EhAK2o<>tyEAiQ||D8veQ=}(-7OiRv70wUsYwY8gHNhxBOVq4u0JVcw% z-L{gt*)(H~&&~MG%|PkTsry~-)DZ}+;*B~kZRn)~^CR2{@4jj&-%COcprT#(S*r)fOH{61knu{N~?ZEX8~y)FtPAGCRBgieu)V+oca2aw`P~y zKKf1#KDBg3CeXuIn?qmgmpM>M;ZX-_7^*nr|4?RD$-Z zvVlk$5DA|VZnRHq5Fe$f;hW+NHrUB8Y|NIK1$Ja|brHnHg9uMk?;OT8#?Ik0rb=X$ z=}O5C0g_G)=+$cJa#o!JTnb)D#HAN&n;hn$+{{2&BCpuVF05OO;=)F?Qm%xgg(0eTZ@Hx4#rj$O1DmJ)}fEssxowbYN^XCsa~#q7&V$y_$G z&x6N)H@4DL1a&Ha*+K<85zOpfhsD(Qez_?)X$pe*uIUlUXyY@_cZDmG&Uwm_JmJ31kFYg^w=~uw6nvG$%A+^1 zF8M4yiol{rio7@v=#P3hvvTsj6D+<*A3XQy0qKi;bvOL8cZEWes0i9vs#}gfn?7Pt`%cF4zj;>P_LQu%)P{`WwFHbESbJIg+`-<#k!jUe@$1gi9IY@|zvOn&{ z#BZ>zD5EIdm&sq(bOgI8mm5IKXn^Me-Kl<6lzy{siQK4IMzee51 zQb?hyg+LYcW0?@AH~FoNSw6WFM5E$aVL&*D)(xaibek!UWPdLZoi7d<(P1dt4?o%de6?x>BOJ>IoDEQ~Tp2>>A8@5E1ZNC~HGh?Ky{ z>OcS36Q`8vCEzV`$R}+jv}yL-?nokdza@3-GpP}RdVN44ce({$F^;%uENlp+tCBOQ!Au%A#&U=oqWK`jU zk$3~DAy}=tR%T=~WG_WP&ubz^B=aa-S(61_qoF4JJY-glRd8GT~U_|ZX z7rTE)XYgPzKsq5Ejl7Toja`qr`ce6|P+@5(Jc00fsC7z1(V6$uqMjVxDUBB!&3{$V z7;Nt>EkaFx#H;_PNizZ~lEc+LgIdyCniJ%$2UF>f?^0U8>6FI8|LIivfn7=ilul^_ z=O{7vbe(8whLE&~5um_2Fdpx3p+tB1{{iN3$HPN<_)wbEMhrYZO#Y^4|DU~(dMk|G zJ`oD?-vx(Pgu^>gd!onxK4&uPCzF^CL#=gKnKqG*HGMsAp{DQP7kbQF*rR&}4dN0=+QvM`_^CRim{ptEpx_&BMKa;MXOV`iqDl^EQR~C<@XODBO zzmjiGKDHsH=kB7p`!shSd5xa^di#vU_m|KK_3LY3E@PE+jVogO#hC2%;!8QII#~R; zSWvl9h++2XyK^5iPt;eY_+OXk%^;8j@rnqQ+6GmDcjU1oTL9I>n$voaAA|+uP55Zrfx;|v7TNY^#djSV1De%zC?@P!N?gH&=N+i_ZaFEqV&lL&i?&_~PzeUM+`S|BL z8-L*iF)WfZHlK1dOysBY&GCO6V;=JP-bsiI!l}{s6vbW5lzPbdXIv(;y)r*(8fK$v zZ%}2z7^}Td;M~LP(3C+++5Y2}d83El3%%V4P3^$K@;X!!tAu;&bcK85YHp!_zm{ zllRo$pUjkHbT6NG4`ULYFJ);Xc`$ilR&ekT^!hsAX#9;NY=@n@Es_X_(6F`rr z{IZE~i0)Nj=yBE+V49pgA!7(HD~8KPj+o?qmerB4Hhu};pQwRkXe}Y>9#8xW+mNU- zxqYuHuBDGG?vuRbA-JuwXukE?Pf%8}+5XrVxu8u>Ri7=Z0Qb5a=FySO**HB?4Odbh zfy4pdzJ?$l^C`N040uxz1=~A#u~!L-!7Mh><(~bQSJG>A1m;2}@E8_6E?3UW z_q6#j5A{i>yu^&d?4Jdg=(}J*2$Sxy9_q5J0vwfCl``;i2Mv08vELw9Ye!}!GhHyk zNb^ERult}8)@@M_JkypeeGZcEtaE4^Mm}Qr*c=#sQMoRgU4^qUsd_I}oufw>H-wD2 zX6}K%ztN{mkh+c06N3QlGBNEwsQ_?tH>^TEBye{erJc%7e}xJFXR)J1t+8UFqq>_( z@YWIT(NX4J9X+BdkF_1uez$bAUq?rkzut5dDDsUuDhmr8J(4pM3dv2RC>M#Ca2ljRnkl(%ruRLW4Tm2;0JCJeS3!WRHF{H>@4dfp&p?_yj-X z6F`JR3VT#*3HbIj-Dvg{L<|4XT4hGadp(VV1w+dXgGh2~OO2p;d@~)Iro)BP z+=Xy6n2R(!`KZ|>i5sQJ=n(U4Yw}|!q|Jb1I{%X}KXEOL>V>%;z)-xkW(M3-FJP5? z%U_;_(1~fu$CbgNSe=N??C{A;T2oCb7sdp>t>r7%kQaub_PgS^2#;W8?XxP=HldT> zIhl?B_d&iQ!zA`8Qc@*=0-+-3B-Vf!mX4@mZlK^bf8jkP{%43>D@=~HaAmz)89^fq`3wg~SLKD*&nNXiX z&l2^?MST<>0r@lPC{sU}Z@Z`syY+xRfU*Y*r9#vPZK=DcB}Qp=phBsGe52W9Ce%-; z50GcL=UCK-8tPM;yr{p?QB$9JM~m1K+8q`0+R#{t`lLzr^qcyuPZ^9mDt!{_QzYus zkV$nEs9qVLV(nH(p+4@Y*xGK?XT705WuiXtFsO;B4~V7-=>>*zgSIp$LNt?z(1U^V zyhfN{2GVnxO*=nrf9o!(U)8bHEUB->QN-<<8rFYl4&Sb+uZYOenPyFW<`>g~VLD(r znw*=skk4{s6nbY&@AYxnAD2UMVg9s;jx4}e%Wz{WEiPSHth!tmrQY5yH6CI(H|Jgl zF!1-j9tlXSxR&p;;?lVbvMQz$x=3b#x>(@wR}A6pJmd1tsFoF3rvbW|>tjV?5)eJZ z9;X#4BE=H>Z=ida+vY3s&su4Kv<9Je_`_KaseBJA!ZL28fu_7B`Ve#(5Hb`2bU!rO z%Q3Np&%Aby4o&ygi|gm;a6_8*Wg&6PsopwAhLiKBK)2yjoDTrT^wa_N!RR8?Ncbi( zqk~XY_a}%W&%yi3d`K?3_<}Mw#2ci76t_e+rU*q*2UEa+{v%hj`7P7|$)LBV5n!!M zwFgj9V3tlkxW<+pdZ&a+I6kMD($~=&4~F9x>_85aI+$N`&@PJh!~}sY$Oq!BWJj?i z$H0j@xg*pCS7b6=REA@uc(T(D7-vFq!<*ey63>L^|opVu9vt{A>F;zScTXVvw17{eI8N z*_6j`n4Sl`%04k(oBy!5~`>q^v2lxUG5+!DgD!H=t z@N8t$6)xS=(btEejiWOacYpV0=Iy;bIr(X}0^B(5OQFU<+Syo1S)_lWu)PiXFEeR1 zZZfmj3|$Huu-8y*dc{}%6R(jNA(*=*ix^<4hYPJ$Bc(VM-6^CPrEO^_S9V(Do)(#U zn?zHaatyEvnr8WBTgAOX6PiankwkW!4JzCV&-G$2j(0zy{rruR^7NfL)(ORkfxiXq z%CVr$sFgQ%tjgVkd5egp`rM7ugH{m!;?I5Y=h)i;B~NfG>+(kZFleAY(HziKY{qQ- zoxGwJ>A^twicI=vUX7&(uL5Sk#o6kI4K1XD@rnMevvKrq#EITYmeUcuNIl9jaT2gq zn|$6YYduZps{S=J2t$;f)Zc77hFnlhb4^auT7aiBH}sde*#$C~O}9bjsGQxx2!kbc z;HcGv%8ts|uOeDrSa8 z^l8(GNWg+!Pu)K(u?wMlD*;T;jtU9flT(td$!2GHmn$_g>q0YMl+uq%cAPL2-*Ih;uWL_U8M^@iUL6kK1fqZN7Z(duK4M_@d z4lTm7`Z!QR0%qykQZvhSnv+gB;;WBX{mHAL=0;~Q*m?sKX!M`xwr0Ihx6O^iFM!R$ zU5a;fd*PkB4PaBZHS9v&?hLzVx}A$KR6hJeJ!WK&CvxHKX*KVb`ndzmVj_JXPI}phDk~(;)SI!KEBgoV%)36q! zn&JNywpcYL{Y0vXa^<$aQo}MH()PA@Bnd}1kkr0jh;kKx5Qu9$lb<-D)r15m0sf;W zFd6!=n|6G`Ys`jLh`JLGs#a)3xyP0mWZc0i4w zF-vOOC81Af@2p2^jeb&rG&Gh2Vh)`T^-RBV{Sv!pBJVvRw5Unr&)a&CNvTnk462TJ zVx9)^OP~DYQ+|RSnhIrR?p~39ut=i|W4)i+#>&hkQ#{`fXfK&G zX9dA0Y)nYECjT14zm+$Z9U8$p(!vbmszjGrsS|!>*z?f{lV1hVS?k#O5_z)4BI*)% zQ~^XajXg!{sQ{>k9;*f?pOZxxj569bASdHnmSEf585dS+^_uVuAKPc+Ta;>4rt~d~g$X#WKgU#;=D#XW9aNo3BI{s4*9E|oE>C{y#IzVQ zZLL5#zhP3O&G=xJV*H!EyuXVu&)7QBeClktGQzbkuf*h0PzxmsO{x%0x*>ku6qoOd z%bj^#5i0S}14o8oS-OPs?xqy5aU-o`qfpuU|Niv7YbXUwDByB1C*PB}<1!`LA^1WN z1yB(FoRZMAPv85&A-Vw>LPW^){r#H}7W_TjmlHN+c@5~Z0bDsPKrsi|K^>mOpnMdl zG(C0HK`CC_n%YbrYBfVNTu#V~{z@)6A!ZkXHIp{iETCZuyh@q{>mgVPMMJYFIt;eh z#$X#x6%^&a;+#1ZjnSeRY|E)iCdx6X=h01~lkOd?ncB=P#~+>)t9SI2 z*i3K&JN6VC@tBYZ)P%zHxQvj)>|Ke0K&Abg6D_PYi4Na2yi_y)QPHJMGqE zrbpne#7G@n*ggR#h1hyAE3a9VowOwRtbpd(GOWuHIsE{>&6L*XG_i~YkxN_^Ekvlo zra0K>-X-N1t3~}=Nc_loL?6`h5g6my(!Lsl@zMsMS~Zt1X5hTyonML{Cw;?48JtsQ zoTql5k+#^pM2R!+(hI~n?US!NwZRT>dZ)BIgeP<+#Vd>BE=S}G@&Hnz#&B4vc#UwD z4xt7LD=)GQ1aXCXKrB8qn55VNwNE(;e*y!au? z&wJPgNpu3bIWwOl&rFa+t)V+O z^h89X^jnqCTz!cn{SIKi!Jjkc(noIWvA3)Y8dxSiG;uM)hCkr*&G|>6>4b zA#G@SjWLE5%V%=`#x6@uXh_a#y$m*jg<2#W$~<2;``)r@fWZ8#STf9IUhQMOaKiE) zWyDWZq^S0VmW^cR)r6mM?|1n{a)UBgly?%ldUu-9+S<+d~KXQu9pgy$j2sqF3ZBbwA4PGm~ zWE<*hizKAcL;!l{AT_|imJw66`II)Kkp0MnY+0IDshgVGL<;Hao$wqoVoeegDY_cs zX7U7&hX~pye6Hr9bl0rHWY~GhMCyDo7vj8P*{>(luFCoFPmXb;K#ZJZ) zUl}CK#z+!?@h#oLJTt{*qo4LX#ic;So@Qd=tyQl-H|cqbTce&l8#d~VX<1hH)^{QL z#7}MEO8c1ds8-~P{ne_a&YT^ihfn}1uuv^d>eZY@%<53x3vh{Cku$t#gIoMHhf^8r za5C#MF6?Ja1Pi!3WhM`rufmMr%2-1N9~MQenvgjX_{e3|3RwUjvmEMufKO*Y8hj!1 z$Py~Rv=-5Gf=@^t;B&itZty8n9PsrT@UfWZ;G?9#2j&ZWpx*-cOkBN5+bG0faCM?^ zhXMXCJa)t6dT1rjKP-zum@HGwBEx@qiH%+hDwAoSEnRK0&yY@ef`Py9CxR2g1TwgP zs#6)vmkm)={%GtPEvFph1AN4x`qLZW0)0-OgTyZ{E4d92I#a|kj!&7Oh%~0Jx*yG! zbT1{19nab4{I<8-mM6>xXR2kXUIo>nyfwN@8o~AMQ9FV=bN_DpPkdCj|3po7`~S{@ zI_oEJQam@^w{0YKpRpiED8LJaW%A>)tiIGwCih8|RZ3XQNfvj&tcp9Lv|)sL>mVB6 z0lN`_R8Y*cfp#(e%**qA@-aPuk4eXE`;_BTamw*VTsp^PgW3uT<6vo#%PQU<5RJKY z3aFDbhrXi-ES?hH!e8RFru*tV&T}=Dnc7v^e6ammSpj8udZ7v)EMg@-Qdo=-72xSZ z1yV%A*wOYFln4ZdGzp~*Z&x*UU>{)tO9EL!NRy^CoR+n>US5`1usI$deylKz6pwUZwg+k}hb=yga1hxtp(BDlDvhVuVyWu*K_AP@+$(B>N013_#$aRI z>AfhvwP2Fh^kGjQvTuOCS#e?>WuYY{d)*0YIvfC?!3LZ#2N?9zG)IJJvA`H9Oa&Y@ zB8sHCAU9fH+ z)-}aBlUZTnTHpG?AAsV)Yu+>+w&t*HrhB9@K?&9iG+QRLeo}6z(-!Y&QyZiHx2N?P zLW62uOEYALGc$x=L+U{Sm_bEvHx_X-;9I;zJRXrl3{6n_qEA#$S&p@ljEX~^XI zB<<9Ou#?=!gYw;Q8wXj7$g+CSlpBZw1R%;>`j|?_5xB#Z+5?Q{HGYkB8jl_HBrAF< z?kTA6*!~1nkG8-h55B=vS{EAdZG)VeYOe$UW-L-gS`tW+ULb5gxd%K<>wqCa2^AE= z%OkU%9i4J)q`X5pm!6ht0>9qS7p}JH>g_}=X^`OnBJD~)zQOK z)iQNS17yK@%sPPMWVK3_s4~mds`N&LF609-k|Z^RTrF;o)F8X~uob!pA`}9nHx5nt zhs`(Sm@`Dn)p6uBtF>)bWRLNKc)(O4MoCTNBC27MqM%A!lF!iGzAD^?S0Noda>+vNB{&~ux+ zd?d=G@tx78YIE6pgQiVg6npHwq{7f{i&VbyozEvjMZrLENCOH10 zOL2fyqVuPO0xZdx{}8-S8gUHH89sB?MZ$p{f;Y?Q7Ln>|unP_hj01xP2eh+lD|j0s z;DFmr%hnm{k;&Dz1We^?vy3 z+kLlAa3(+bqiy3qc0wPDqce=tVihz7jo}m%^GWfIVvCoMIso0g76}5A_*33wdIYHj z4_CTFlaX^}nd3AT^@zn^)iO(gH7qQFQUzinK8RqMvd|f*lvY0LUY#-RNi0my5FEt6 zXRASD8upz^xc{KD+0B|5Z!8z1Zk}p=ht#n>sp09~inxgL7|ybFcZf0S#$tM}_sKkcqFP^8EDbV1>P! zwzrtr#k4ObMfcR(#F_*FvwBN4+QK5Hl7b9$-b{zNTjNqjBx9-6O>qwuZ5^2lnL%hj|SkFHAx|jNt$Ab8Z#JX=>TRb69Lu>>`*WYDhqs>W*WwVNjFplt046? zR73*~ej+@KrqZy|+%{;k8kQ(pS}YoI8{mi_wNdQ@$w2u-Avsn-Q9MH|RIBobKy>v+ z#mK{zy#|_MZ8^~tN9mwPNcG&I%nw_>0(WJhw90(woaQS@M1Ug&ZcqrAK~v1y($dA4s)&k=@L`5ZFy-OIldabyjpn!rZg^gKjhODP3Cq1 zsu7=Z(R9;wRSi(NYzSDmtJutaFW2LnQ)IfDq?74BCJjXz^2=;Mn#}Z0rYeZf@4CCL z$iC`+L;jwyP3BBTM7qOjtY)hNzn3C2z!9 zRbUI7u@s2z3rqQpyp}WvdGOMjfF7`|njpW%KMrA~8*%=Xl#W!>`Q`kb?c;XU)Z}!h zS$Z@xhK@391xT+*G0})-t3fgnflG>qd%bN8Ftn+**{* zD=N0MixE}OF<$3Ts2RpV6=%A?k9odBY6|isQdd`p$Za-4hZu$q!5nEskdADMB7yC+ zTRM_RDZS>>08*sLUySty#8CviqoYJDIbZanetNS3Qgb;;r}}5Si#_wIsN6oL;eP$s z2#17i2}!EW`ufw9<^T?DOGDMy91NS&a6I{$SF4r{wYfqCQ}EGvC7#cVq(u-P+@rbA7fAV{lr@g?v^bZ8(tl&e}96`|FQYy&Th!zG9SxmH7!q=RfjSP8O? zmZm}Lh0UG@3A9?p0&Ne_7IV-R3ADmLKr5R7M$sLh1qclxNH7(>X!TE$%>mF{|D*%g zhJC{@Wx4{U@@@*I^pT*51XJ)Dp-?NjXyDUW8d+xn)8c8tG%Xw?&9rm^rUJELO0|M1 zibSI@7>d_1#spIo2E(+G7rPV~p-K=QX(L9$2u1b8K@-h;J;=+?qzgOfR??toUmjc! zA!`7!mR((Zrji&pThXjL`v(q8->8D^hbSga3P!t7kD5eDIz`nP9?N{NA|ntMd!vym zhY83hzQRM`fDEdOXsws2`pi9b1=Sc${_xf6;0Pk50<^7Bp0^aO&tai1o6re=C0n?e zq^&t0h@P>SLa+f_&xpO4CQ-P9O{^#nv-3!7rG{^wnXMoqyRxcxv+#(B3l;>T_G#0* zpfbRS2LgoPCh(;A3c)^L+h55V!FZ8jx5i><8Xs!rrnHkAxS$8Y6;>g5L5_9}MbaMexDKIBT zd)OuoHZUb1$70{(+BiUSVL{g6IL`&_B)L5+*oFP?`GDQ5?1VLmokJRlr$T!V!Uuv( z6+;Fx+sRYs+nqd*r?jo@E2x+7U3G0=L0U7cAWNXzSCChlSCEX&V!lYVlqbem3-aJ} zRZB_#%@>tA%!*(I%omj;OPPJa0n@!M4&(_3Qo{AQaG?Jz){i8V76;%_Y5l-|Bnu{q z<$UW00fY(~9DwF%9n9B{y?7lb=+5@hv70sTNsMVoAa0n~BJ0i)Es}Ohe5gW-6vyMI}`izNz+BqEx z=2Ql)ra3}%Q+$w?C(T*Xu{f7WN9T`dHJiLN=`H_!5`6Y3Mr-&iJwb#$0v}Mv8v7n* zK6RoOOx(Fy6@6#LBxw&Sj%ad&{#=iKps13*z)*;vyBr@^o0A_i8|FI!Lvklp#2aCL zH^|IEi;Y)NqMy1Vc1a!-RinIdxn2ZCNuSdd7C}+U@uFY}>DK1wRD(n=*Oys02S<4z zz!&}mgA>`5NfLT0H7&TiCP>3osa8EE2~&!uE9$&&Ye9V$V+Qcs!SJH3=(<3)6Cu)Wy)b z!X<2#1ukC5IsprNCjv8`FHYwYI~lkY4r6xUrwlO@3b1ehC+amdZ>nwifE$%ft??@z zx2R|~v;~y{ONKUyU?8EVvV$<5m9QN~Eq>0>^KI7W8Ns0$s0m2)?Fs?X%)H2)Di#K7 zqkAz?HQ-^`sx1dz7#`c!%Jfn5eAm_*v!Zai$^#{&hkESTRkS|>avjisLd`hzkXZ#e z0Y1+T$gSt48tRWkB#EQ@wJ5w!=7OWiJW3@Il~PpkZU{|VrpeNo3!UfoVn|*F6ad;* znxv$%5)$V`SqrTcbup+JQm)Zi)NH@dPRSfhm%OK@qC5z{peB znW81@1xw8Wm#ZUjVyKR%$eOYSt2c5nj-(u*ThFJ+8|WgNh+IK&6oJ^CBG}Y?iY&xv z?IaYS;s!S0qtJ~xh;?oAI@wq;FPIax_7*2F=lAvrMDvOXj5QH*vp$|vqv94Fgw%wX zEi(1RUnb2nLV*(p!qna6KzW+^B5?tzH)}}~iXUDm$>D>dwjd_YWC4W^sD{KKM{mZY zM1kkBSdTRKD83co;5|#{%LOcR!3sKSa!G^Gcibrhk?3_pN#?~n&2SOmWjq$8@ z#E`l?Peq29Tb)xexzkPSHL2;bPlqI5;CFNOh!ew!1w!U(1P56s=wY4IKBcj5cGm2h z4GRou88ni}Abqo(<}9C_-J%p$#-!ROz@YDH1b!g(Dsw?s8y`C8YKBad^P4r4M`AQY6NWtQlK8OYM#_1B{R`t;d=25i|;157_ zNj^j(Bu+c~6@(ZljTahulgw?nt~j=oZh~f&(;aG=59$g0Ssi*ul&cUW55W-!lJ2i}osN9g86G+&4?Fs}em6W}MA`QTRE6e9q8MqOzewdcoT=D+cG7$`~ zznOM#Q*zN=WtHiAXS&XJb7SClmubg0_NFWEE60C!DJ@>9^G2%bQd&IgUZRn+Ca?u5 z-uF^k{H3&bsQ0C`c;rGn7?;xGQz$=NxSHljBbUZ&P|DP`9&m(6_Nm9!FrTqC9 zME?B0%sI-dNV(4a!x`8OcS##VSm5x%#ZX!n)7z2`SEaLyQB13$GBsHk%5Xu#U1x{n zSx?&v6W5BgullsvjQ-w>-%{uyyPAAaS|46FS}j{O6(iHx}P?;Pnp3o)SKm52>9=zbW4=|;Ls5huK{DVG8%dQUpP ze5mNo0D?#rT!D(BqnIPw!%XMni$gpO>B=VZIbV#_mFK6QM{T_{os#>pd+$HzoH=v${MnOCFf#FVpHs9! zgQk`uqLSH@A%uumtpD%p`?*?eFP%Y3LP&Y9Kn6%Dwf;e|7Iv{G*`^-#20KN6v5S??*-p`-)tYNWdz(ogw$da;3sZyX6ag1zu6X%a;T8GIkRZJ|5(C;e!;Xn}6ZU=(g#IYJuXMVLn zM%jLbvep<&inuz6UcDdWK#()3Qr0ZEd=^yag3IkSse9aG3y)NwpD*qrl>C7pYrvj> zXW>$3Q5gI*O)K)$8PFFU2y$x&lo{ozaV;b0%3k2o0Sj6?E?N>j7)(y;CE1BiB1nmE zIuhhU2ssjjv90mzIugV&7H@59cxzh&Yg-X^D$6B)3ZpQ6gP5n=X>6ncpXNoaqijr_tY)B3h z#06JwR;=|dm4qB1i+m}qGJ0o$m0poKrIp@BONc9=S+|9uUMa*e)XNj*eJQMC=S#um zYGTC!NGyY*Lx*%~YDk89&5AQzj{`f_X16N3gzX;4!d5O49ct($U%4KpJA+~@i%#|! zThUsYlAZ1lj{|AFxVb$@rJ^ z^TuU>XO{wGgx4t-?}&9Wz~WDkdIARuu%111h$owVYu35aaAp_NPf4^2Q31=$O}|Kt zD6eArk$B5A25~tUt7g1=NJzZV9TIAwG!%z~u&64T3m+^Nt1H1Rc#1%@{tq*o@sR{(9!fM3vE*fnzS5hn+^n$IG)Qf1ylQX z#1xiek_$%Vt>7(`ZF5l9e439zpr}m+fDi{K_RmQyQ3lAnXOG&iAr7yWvL~c1hE-IzMT)LEP2fOEd(BBbtr~ zK@kXr#V#j(6Nh}3C=>&AXNf{_MSh$5WM_$_U%Fk4WW~^d&k{AIJQ4ut&Jr>G?FN$y zU@JQ<+ktqJhTLsmfMG`zrV!{cm$MOa2Ty*fC!J;fn}4aj;#6l?1*W+Ow_D4-sD@S- z+Z4r7IS!}&Nl0KdQ1$cw5d#RJ)Z2wN71j$*NA==PEGc$WOL1}kT!f}&rF(gOHz@6R ztcNx1@+^@?#KzHh3w+tuv-2{`L#&0b0&I;ecA{XOo^MiFAzlw(vEjC>?nT_BweUQuo5QD)FNZ`7GY%329B8%DF8N&9nq}RBD9t6 zSt3g6+zagJR6Zb&jf!nY9HACzX@b*n#%ZVx5A`fjDAa~f2IqDj?^zpQ z&Jt<0q5Vp_mS>3+-2#hYzG-6dSt7Oq;KH3H@?@e$WDcPsSS4HvE-{6SLq5B5rS~d>G%^}VuiFh z^j)%2ttKp9E&5*2VJc+{@K|52Y{AK;;|gO1$yv(;7F4+*BWFi<~50%#dSn%Sv=GTLjDV! z(jtleX`NW1F^hbPWOSJ?B9*TMA}2&S?1=K27_% zksssJdPiDe-uSe_me`qvshrN^6!96Iba*-;nUwnU zNCUtYfTdCCuMRc>OpYEAY$Fw;&Im9#=zCLFwXrW>Vg#6q$`N2I%;ov)N$l(&xy;TP z(EDWA_APtN#LgOk&318NuUMyMd#UToP0lP?qqy(GMc)tTt`F{=WjwDU8xosI*h0qL zNUH-**yunFnY4(Alku~>Uqg3baRjF+kDgf{2$72w=4J797T4c=oqqbZR8cikP z&e86gPGQLdmLryDg9te>o9AebqZ zKr|7HMbq&@n6D8f6FJO7LlG+yvpfg5!A*Jx_Z%oB%1w>cm3dR(@rYTmq2FRdzd7ul zZS-mGqvWpD;qoGe5-!P5pdO6Q3XWu8`dI5eR`afmp|oO|d*Nd!7pFYeU1l7Y1GU}Q zNu@sTqvb)1#Zv+lypkW8=tOewGjv6V@ZqBd&2;CJf;$(Pf-GFFAM^NH*qsg30_3Lg zXvhzTd)Yi>m^pD!c_&8odImLM(db~d!hzO3ru4j5>;4S zfp_Q0e(j?MeF1s3Wua349ffMJZtTb#jB&t44JxPslPQ)d^R*B20Tj-S=-*Mea2G1X zz?r$sQra7p0497=@S$$hpt6tCfDJ=X7{Cr^+^yn9cc@W=a4wCeg)92HClEE*Gp#;q z5MnQ*1{owojrVOsCn0K3rhDQDfN_baL6<(d8(f8{(ny^94;0K~QG+8|U&?K4L3&eo zKYi37jnXd}IN5Q#WBqb^Skzz>@*A<)oyyb)v9cunPF&O=Nhqc?g$IcmG>TJ(5f=y5 zJ;LtJq}yxscSnpriKsz`WKd?5XK@}ZBOI{yq8l}+dzy5dR->HM%QZTlYpKl?HRyeA zcuI6cS5bpRy5Tg0b@iOlO@t4ltKO(VhI;(d9nW9Q2wg2foA0}!{V{J_JC8XQ*Gm}W)MhRhML7g2-FE@6vpT4q!P zfS8bRf`#PUv8cfzVPa9#ps^KmgUM!oh*5*|;=*wVOqC0sVjl*nWi^h6EXR4y4cD87d>Rmue)HE7ILF+w^N4*^Kya&kjagYa{*IGFS+ zT)9QmARK>T)L@qa4i+_7P=F7KF#d^hWYcf)Tt(ENPr!kH&rQF;gG_DuyHSHS{pf5i z@{e@{W7Ui=6*WjE7)I(+i?aw_)F7<1Ep`vi?9(gX+mKQt+P8I?z*{R(gBGI-H(L}nsAr%=EVnc4NU~4?yrA@P zV{TzFvjpwi5+Z0H(KF(uyHB;j94jQo6|l${lIYqg`g$2Ph*TFZ^m(%7dCJvCZ`5GD z8#Ty!uOt>gkvgAQh5< zcDt!42$mG1h#C}TBu!Mj=!WdaJ&)wdN;HOOt(m_<$bp$vJBr7Z)R#pzimtP(#OmnFxv!Y3Uy2o(vJ zF7-G|LQ#W!_^3f68^?z#QG>kiMhz~VdYr%@qD|mg-2{5w)Z@kVpFVooxxOt-2M4my zh)QWaD_ohM@S#45At{;*qXvx+p{NTdWR_hRH3+B&jvBPMR*nt9n+Wg71~<_IAC_$- zY!7PIz3zpc>n~^VnpY_KZ#sZ_VE*`695u+fD$78Qz5wXms6k_wk2`A6HVZCl&@NHr zw{}ZV(F356uidCYoy~F?$OlU~%SKFVxHZiX%BVpouFY8VYGr4@T*{uLA$LW0DY~cJ zJ}Au3$i3B7+$D{irsQ5qRha6SW;&gvwn(ZkOy!?uKDCYU^xkr_E?$6~tG5Yna8ZLU z19>G+r0>7jR1g;=*vJ(|V+CxB`%m4nY5|S|wapm~vj3CrSz0=`qZA5=c0LtO=yy3H zP%V6FOK&n&fSM&2HAuu9 zil_oKD}gyJB`i|pSJ)FZmAB2Y7`Y&Y2U|q9AeZUr>mYw)ZidUB&J&W z-V=@*H1$L+EQ}gtpMwp---NJq)F9ittW`$1b=gsIGplDuT%y6m`KZBy9a+D+v$evG zFmM!6gJCynkdlgUYgM8KNpT($0(bGEsKNT8sKI5lStfUnd_bn?kElVGxY+6AjT&^} z>Q5wU5S&8S9;Z>EY8Fw0tO-sp0n_8dsS@;oL|F|)_x`+!MbW|H#aR4K0|~w$YS81< zq6U>V-f-%*XU~Dls6jLkZZ42XN;V45^At5$vhp%&(8L*`$r64|7d5EW*49cy$Z~4Q z0JexJiyEYfO4Q(>E5rd+2Gdhi!0^~)i#&ZMq##~fgD9ewfgF|9r7Gg1j2aZo5V`n= z#j7?HH3)zQj2c{`R5xl++L(!K2cFEYjw4#A8+`~O=^At?Z6JqQw<2^MPoi*zQ@E%> z7-l8Zm)cYrBw|{5p43=Y??w&6mwPjibN-??1G$bSK}o{B0g6U=Xc@>gb4v5qGUP%Y ztmgJ$HbMh)_Oyc1Ys;A7HfAa9gWgTEDrO@jQnjWASn zmA2~qxC3+s$9t@-RVsh)M1$!lnc ze5`#YnW=WjP8}r#;~@5_+7vJ)YDr0;bJ4PVI%?lEnvF2a@=}vY9m|H?w9_hd3hf6r zR+I`>8?7k)JSrAs;;wfNMb$O(ZraK0Pz^L0DZ?Upni>%4pj7F&NB3YhN*+qO?pBOd z1Av4m)R>Jl9lC;cwIVWFS1WQZTY3Sd9BPsaeek9@(SqPjk#?b+8=Z%b8N7K-xFB@X zCdtQBMmg4eO&TGZQa`&Pe}%peS9W{nYMdm7$)y7kF{w*}gf4Q^%<=TZ5&;dKqAX(A z2#VxJZ#+v zNjc&Sy*1S6gWL` zRxWsV875PKwc0TttAEtAb1}FSk|7r%Os8L1N2gIIEYdyR&&eJzBLPWXb9c7{Fx>%6 zVsPRIcUGR`4lAGI5h}}zSr_GZRi4jQo`10N9Ck~2jo9N;q64rBfl3xipc0ZVrItox zG6%@cwT{Z4XY~GjL+{Ux_R~gjYPX3O^%h#&Ep%+R&{5r|tGiFRMx_{LaT5nH4RoJ! zcX!8S_6(IAjpHQj)?kTLuUbs5q5M4HgUow}U7<;-eG^uw`5zs>)glj1Cy2KaZ7bFCh0MiUd3*s$838?PW-s~k0 zQ(IlnD1qfVpRGs?mzUT}1j)xp#y)L?th{93(s_xEyd2m4xY7Rg4N@pev{6^|XWwjR zXx}guNx?=GKrnXblZzIui45G+a&X1L=(hVnHLkI4; z6kdK6m27-#kBio_iw;%AL8{0Pt6G4;dCDIeh9vouIP7;kyG`=yk9k z^tm80tZ*70&(2ctkTq^j8K?eJ9foT5P3o}wrwy0J975q*5wK)=%W)xZcD{0E)$pYT zWAkoVz!_$Qh7eW*Ca|}a-M0F2x1-6Pfq~8hGP|nSKx3$p5uNQw=#D5n{wNz0HbDoT zDG7ur$+E)#jHB}D9vDW=e8>=x>FQpkgl&&H`gI!|dr+(1l5>AQ+2Cu+Etnx!h%BRO zrkF&AeDFw2<_UsO&?uoAiYV<<+-36>3X(J*P1aY2ToTrh5phetudHvPd0wZlQmSdC zEK7zZDg=IOn#~x|4e3=}J}25(Ic1;~zp-*!sAhiOwf9|XbIW?;3gqjv+VvCh1+QkZ zHIi-&ey8~wo@ohY^eBrB$jzFBZ+4U(k>Of?8|g|n6IHLsKAvHvcGe-D0F?+8v1ArE z77aYbMfj63FY9~FV5rHY2dqwK1)D;Ax+sJJu8~m>pa>29lL;i2{5#@8t8~F{fq=9vJC2df0+2}-Y4jpvJQuOT z3m$8n+>`+jn&#fYKGzS{X4#KZlMP94!yW33^=W;>m(enSW>%mZ533 z7n%d3S*;59Mvg$LEb(TEDTNUsZ+fPbg?#`B{A`Mm9h6u#iFKRApod~tA7Bz$u}q=_ znOm$z)(arK&*+TaIhm(BqU5ANw}Xwv$=^m; zxgB4Nc);>C9-}p@)sD0zS__h|+yfnkmi#$10;SG2z%#oMUa*Wl#-lW225g zga$WPbKG%aM~OQ!xpcrj3$Cq@poq0e{FhNoq|O0F^H;R&ct0z58xhYKT=}+{=t?N4 zEGU`3Q~@&Pxt1giA(UvR<{h^3&9oFNi-?jpj$rS| z2s{59*mu}X)5O!QWc4C=0}rQIJEA1#b5|FBI)-lce<>!+r^#!EXuF)^F3QG5$VQArN3TYx=a1a#^6-cjWS2x~Br=&9gn7R^Hkn? ztXf`9I?E4?yWs5n>D!V^LfbOt-}xhF3fA3T51`p z+wtNN0(4>bXgaGbvj9VOx~TsCQsC64)qE7Nuxv#N`WSE@Y(({ImCC*_JSus2KPfnh z6wUEbfo2ocNPp5!9cKFqAv@1R7DiC<#2?5}zk~m8SK~l@Lhv*`K6Gt*bPzm0Si$#M zzQ=q=bI7mo-z8Tr^WO#P1*Q#3pv39!NwO)fkKtweul~dSJZe zn4qRxUP6OX0--LE8|5FpHqGnlqZ5lje)nRKM*!!JYtv%_z!#Xec<=@0jsCm9ltY$} zgM*2B4$@w?sNa7dRN0v1P|_qyhcop26vC(sT|ezGid+4(!wAh9j4W7YyJ7mL7I7Vo z_orh0lF}U&`h_8B<{UC3+YT5jLSl&{!Np@^tjV_{7^>5MU`Y9~CXuX27U^R&i?mUH z#3?#@vW6G-_JK{EJ4}t zzH)6Ur~{Zd;@)vLv5LQ_u!_$v%_<}sJyTmwsyFqBR550awz9h7PT-BC@R{ij z!XapfV$fneW|&AnSqC7;>%ApG%_MDOhsS{ccZEAv4GL<@EaoY#-RbxruRA1HaV>2# zxu!`T8;9#Ux+c6=sp~_D_|<%%%t@1wE}2fHw@!dgL5G|cM?_u)YXWit`tdm!h>d)T z^ci(84PYb)oZ4n8h7%(7i5rCSOZk6Sh&&I}zWXB%~UeNKjW{oYK+6%*HI;i%?oAZk!?Jeui2{~RSwGP5bZ<}zW0SS2& zGe(Ev!ZVLn2r+2+)$_HbLQ$WX6Dlv^|6N*WgR+16h?CBm^S}3?YtFyuziTP}d1ZK? zstnJh_Q6nG(&uOiQdh^RPkcZi_0I0o>F$TVi>L7Y`xP+0Qvu`aPY8^aB^XuA%nOWO z%yj7bgV(uLBFw!1I=4!MnfLnd!pz(KcM;zX|6RoQ*qCwb*fR{2W7|X}_r;ud4=;$u z9S_Jl(s-QI;npxHfVs{RXBU!cmLQ7UhLv4CysRCf%u~#$gbmwB7(fWpAKBOua^Np@ zQK*)-4OWiZ72GC3jcY@pcP@@vjHMP}^q~Qw*I2c-0RXsaca-A2kLfVA8xcTTqQsEe zQn=N<=c%d+ibgpL5RVIF3kbnXD3B~Vtk?-?y2ySWY|?H&Jyb(Cl|i1$8`H+ zHx6fpG-LtSDp8g#QhxBijNh_1#0pGHHreI{=pCTkm~N+!hZ7r?#^I&{QnT8cH}Hak zO`Fyjxs`=x@ePyHY$t&)z~nZ8cbb-kvQyn@O24$7CT7IyPP3t%CNVv>(}bQL*G|(6 z`B15ZBRfqC6@7v`&0-6=0nEi0Xz&he9hQaLe}T7H+X_P)yC9jTgtk~Z9_4K_ewudA zwbvy797la_FUQsKs7`1(s1R%TSYuYZ0DIn%wt!*GYoH0YBGb=m!-TAp307}>782W) ztA*9H!0|1Sk6KWf)o}4VbjQeJ7=$kR2A^(wj^-7w?D5#Q=E9Y=R|j=jOUx(9S07w? zEcD0)TBBAcRy2W+GW8j(>RPZ2n;m`=z93T-Lxs&cm^nQ+mHzd(O2~3xFB!0^Md@FR zGd;3U`4?s_i;I4D#8KT%G`^1|Y=i~g z$<0B1Q4%(Oh9FFN`#wX!w(L?sL@0L{iNL#D^M-e^Cp13r&LN_1EBGinO~X5neQVTw z=SjL`L>*;K!@JhA%oDOz6?6mEm?{{IMsJ+f3OP+Z7O`$Wl~IEw&6zhNLf4r6WN)hF z6v2dPCS0C4&s>MR9VoRn#4HwYuj@1z1p00PUTomOlS=TIHXCx3esSE$$2@28* zfSItem*}qf0Y_nu+->hr7DGr_3(n}3tO%Hh2xcMZNkl!JMH|MFcWBQR(R#iPZwyN$ zwAY;&N3155$!Jrl9mL~;RdyXOmidZ`Yhl09D)CTfK;I&`z)oY74ij(dlg#DIF$%S1&m6Y@I+9nbo3k@pP{- zwGfDiLp3Neb$$nT|4-;xze;oxart`wbUAvgatj}1r5@^*jAE>XMYRrGT-~tAUD(Y* zDsbRRtd0^XPzk2KyiYqK4tKDX$uIpVX=Kv(*`Ct`(iL?^t5b%2FflA~Mk!11z+1Aq z{O@&VK9{eLSNN%Ug8!nfWq{ej?zLz7tcKagNkgsQ+DP&hBBDI@crC7OY>DwCTf+9u zO!=y0%#)M8X3f@dFMxE`VBMc!xvw?txNl5z(p+`_Wqm_^0bvfcdV>rAvU%rm`rx?q z41QfvpGxnVV6A<7e1MqZrC#6dU*o;^uOF(`{(@fr%lH7X#Y?^Z3;!C0&A+g;0wCqx^@A0oO1}ydZuiR@>xnb7Kgw2%ZX@gY6T0UDhpA@R%XFlru2KR?iX!`(1 zks=C0bRcJ@1|*_dihX#(t{H*4qrW8qb)@qpkVc$mP&QwT_9~4)5KA^v(7B^R1fr(T z7Bw8>W%%IaR5)AUQIkLb0=^xpZe$KMW#1Mhjoh9}F zm0!i2-TN~;vilg>h0#ytIzzO_Cc_bzTc_Nb)BH)tKyb69#co-Em*Fwdjhb#b&f2eH zao*VT>(1)0VsiiHFbe+mFbeJm1@BnyGKjI&L6H)D44^O6+y+d+p>b2P1o0C~Wu76M z9FmzI1hJVyfsUdJOO(<~TM20|XdrAQly%6ti)r*+T{OuRoU}v6F6js|*P~~kU&)hV zx;d`l0UaySKCjP%BD0L`N9aI?-MU`;jiRC=sE>MNaI$!$87&Q~ZF?-tN}6jO+mI^K zKN8--$^#4zz`BDleAB2jXxF(e@LwaH-S2rBzMjf$q7`PoAWgoGt)eC)yG z+U}^jz8lCqVsHD~xovOMskMidNE;p6@=sRLM1Eg%CMgA+E-!TR;0$z$Go})2(>ixS z32I~&HN-JEH7cA~_9ytp-e9Ud3WpKc1XS>$D@9-~B+@!)Cz?Oa3LiC&xeGGx$RX53 z$5J&|?0DR5qZ8EvOs0J%i)+`&{V@$B`L%WFv_1!n&`GQV)o{r$5wc;kNeYGjb0GmX z($ZMjacDZGI?J-b^v(%1QhpDJ0-2YoHYNnd#v^(XJvOpse3vDWEJ8A&z)iCOxF-W9 z+$#M$jOZ$^t-r|5@l73^ruEjpWN&T z(_2Y=V}_%1L8_$=8PYJgJ*lPJDoD{FSou>+@3M@I=du_$$}Nx?5S1*lq95p_xDEW* z{OM+WxHFl!Dr#jW$pzLXexA-uXuM)L7LU9@-s)5E^Kw*tD3L0-sZ&T(q-_I6qIU&-R zjq5hLg(L{%V=e$Q>-Rmq)`5Q728_!k%=G&D&aeoU3#VS6CB|yb&4#yaZcCLN$ul(( zXaT++C>-ps%@Q{gbT?36Vy|*<2CunFK ze$ngsN_&ZcPoKtlwNoPL1yP_IY8ZO~9Lt~9z9r@Y4u;lWUHf7@(6Z=M{0HU=7qMmy z;#H6aOF)MnwMtz_0ux?RS*m?KVD=pVOgOIKE1+dVC-6MC5=S~y*S?y$0o$SAE?Jav z-c-PZhALnhpVB0-S_78ta%NC`+@%1qWv$#AtD`r#b$CV_?|`XFCyy@zKBS) zdMF)`B|)4@%)vYiOpY`r&TQj^fO;u&ED1ez>$A6gtmQjTR|^3cBWX%tHM1eLmWa$7 z)7u0)|EO%_ahyIY%j;NP4%zZr>xq6X#LeR!{@VU&{yA9RrV=WX-xY zHpCiRQFgwC-Z6}q14EWM0)@!UN1_4+Z=*kNe2TKsMGTOu(5E`fvjL*Y1sU`=pwt9L zJm$o(y}ig3F2v+o#JiD^6MW`b2WB?`7A>#i@Nqfs;^5XA2zO1mbQ3}*x5{jdp4}Q45ZWn+ zs1o&83u|oowJ>(a_l2?Pp(qTZ1h6nx&;ToIrT7m_#KBjzE&qInr>2ct%Qj5;cOWmB zvOvl-vMrCcDUc1aG$}{XW+z}^&Y2Gb#Q@+9o_6)>#ZN*Vv-ni6;-EBv@Iu8=aH`ZX z9yk@}+>V)&Vy@;`Q^Y8Zb9aQ6o)j zR~eHCaJt$KG&ML3&^fJ4r)9*rxr5wmWy6b@68(q=SfaA3Rz~ufQY&NN%qv=%qf5cI zgjNQVG={>Rmg2I;P7B2gJm`@&0_J4QL!@drt#vBlF=(n+U!IBXGt!E6`b*Wz9M)d- z>NCwj*hg&8UyGDi(vG;YUb3PO)r%Y6E3$zGr3BMWSM`#tK+~Y7vQ-3wBn~PzQpc%Y zT(NH1ZfV?!g?_}sK-te0u`Fq}?IC16*{HVBJH#|7jm_k_UFuFJuXQeW)4-Hkw@ugT z{PpDS1@)}vt_C*XHL!Gn2IhvaKm+U3bP$qfSHhGYy9%aP!aSIkJGlh2RKi%C0jqR`Nfh@s4W(t*momT1 z&l}vbJA*pg?N^v7VH|EMl`!buYf6|C zXiDVal4d$Rh&_!^Y;=pJWU`LW%0iP8Ch3n>RVrcZHn|0SVbiQN(=(-nahO<2STn=% zL0;Qq=~UabJmW#BgweTE!W`q(z`C0^?jF}552S=GTg2~1wCF~%!9!5OOvCOfVP2P4 zcq1^o3D5?Trw&ImA^FY~>?U;0a@=Dlu zM#51aw)vB^i6J93SqHa?F<8GX?3%{@9c*GVKf^Y$FUuwdHKkMS6_6K9`^lTeM7Tdb z(^%q7V_VU1WxXg&V>57$rA=dKW42CA2BWbpVHi^eRffy`--}oht57N!Dc$|SNWVIh zQUyDmHd&a#V;IJe{L)FxFlM$T_Tft!#un;lTMuj)bGwY1cEnQB_*t7u-+v-X52>EF zY5Iur?ngUVXnA@i>H*8?)Y9@K<%$Qd?WD$nkBeT5#E_Bu+$=@!832b>%hPKq?krEQ z%m$q033J7d(elJ1>sp=$mbN?*@xlP5#r~gSd2+1ziP@dHrY5yg7|YDujwQ{T$DV}U z$!p;(1t~m*)yWi|{)*M<_0H;a`j5}*w6=qS(X~3QaaO0Jmb5wzEVMdtr3;%wX)2A1 z!Xv%KS)JB+g=Ys=T$3APCRgC9CYMD+wmqO^8hYkaxyzeugQs4 zlqgUVhYwi6PuApAXgk_hddHsG$Ksy>lasU^Wo}2?5x+BS$M&NuC47+89c&nnX?emX z>vjcZd6Kr%l9fU;gOtVfgAJ(SUBC%~L!)YWLXaw7Vri(YI0B{Z0&9n6XXpdF=Y2oIk$oAHQw&D)tTsJ zcXG}?6r`3EB(rSPL|?URd9P+;3KRCVC!yddIk^lwT?J>*D>zld&LJu|{$4Al=uizi zg==krg0s-D!>u7^*g2?<^MoxXejByOyXFi#<&GF9O1UG(lv{Y$yu}1F&qBja)w^b_ z>&Y5+TqA{HN1O7lVdp>r0P@dP>`yGlUNEH*crB+e>zIA9Khq*{*Sx*nnRQNY52Fjk z@CMzXbr7?TB^9sw*81dILvb&CYo_IpsN9=%)^PnI`gT?gJ?CVExlR})|=j@t6I(qr{(lzgXB9`lckn3*wu3Ad zKE*n5$C0>tMfLf9jaU}q9n9~Bj_GpDpb1H%!h0;C;}o9`1c~^ON!vxia^H^}kz2RW zDV?jXvp*L)5fj4gT^S!bV42muXt-B{0+DyuWb+XlHHR8c?yr?bpZjYEwfgW{CO1i( zdnsA9`@{)$0eGkLcRhZ!NOu7VyqP-CLzQ;VB_2Ip7tsP(Mk;BD80J<>-GXUaBrX|- z?KN#v-x)UQOGVx8?+NB1kO)2B1sbYeVMZZKl=->zF+lo5>D)NHq;5DR=uFPwdOs3= zy>f*ll0{NCaDPZ$Wlk2V8#rsQE;=Ben6sQOao_)f}yyeHKZsjrttO}60kb86@ zm}^BRDxF;mY(HKrlD*VvMPHVjWRf1J{rL2vT{W41x{A?tHK!SUPzSCUMB(u|kZQDx z&=k{+^rfO4@sp4zqt)wbehtue*9z3rL7^b|;G^q|(7lF4OFb&mic&v&!Ya}M!;h9! zk<80k2GjR*#~l=eHvV`cv!A$XWVibXsRxP7{*P-wy85tc^dC+&deR1@BVArhyTkUQ!}cR@W-m-$hwVp!Y*`0Yk`CLCx^|(($JY+qj{;1T2Rgp?mr8qY zS(iGbUebXqK5AIWm57PpaqZ4^-w_F?lnaNmaLUj~B1lao^z|EegbI+J%goZU&=U*8 z6+Ctfu*?VatT6P~2!v#32vwTwQrPR$FHNNHT;EpDmO3m*I^CiwCVZ-MgT&QIz8VJk zy}vh-|IWwK^cneg`1a0^5&O!&Abn=~4-@HECep7?qUu`gSuBhxv#2x;nq*lW891H`w>T*iTqK%d}|`V`r~Q(?TP%)`FL<5{}vzLnaJOLLz@2giTtB{{L4i8&_ud#BERwz zR{l@;p#0zSLHQr@LHR$p(aPV(2j#yzk$!I?SzF>D09`?T`Ns%)8OR_0J&S?FvXReh z&-VxEvn&9L49yEGT;VEQ_PzsEc1J2unr;=k*dKTMIBr27pK}#%EGpDoP_u5e+27Z#_M8RP?sC=kbgPliwp)B* zFZa9RcXx|h==5Xt@(-@s{oQIky>toQ&ESKs_`b4u`s`xv78d=kEBZ)Lw0Yp7dv_UK z+yj}KKpaR3g0+Q|tS+(AU2*xtR?ybn8o%O}E_nAvtR}ZW4_(9g6ugsyWJA-E zKX{A#CFuCBvtin1AqFb^ZF@UlI8Yb3!Fa4Z|CUK zk*AC$PvM7Imr3Wg_pe9;l3utz4^AcraFGL^T%vZf9!xturW5Dq%G(#DGn1tF<=pcl zc2f%yA^qWm#9#Ux9wo5&$YsVPFq$rk=YJTI6EdcL7AdoAl;8YC8_EfRDh!>BGd+?m zILZ`Kt>l#X5I?vKiAu$ry#FO64vq5`NBec*pRB+U85C&5H7!p-M5-yU+By@ETjX)NJWrU%h&2fpz6cPDt)`~4KKr2yXV742 zsqUBvMiT17j%X?ea~qkb4-HDN;D3)4AdXGjT!hI+sG*x3lxA#I1xkb(Dp{!crY)bo zU?wUARl>Ct;PFlvH)Ya`)+gTxl_Vo#%5=g`|6D5BAdhv~B9+Ni->!K;GYcu`0nKWv zjzEq#oAXjoSOx(G(o7iAi}cUI?yz+jE0_!u8e%En4>rYet|{_HE0}m^`!0`7DQ^B*bXvzs@WW@ zjHwC8@we713_z6V>zRX^LApA^W+-5ii$iX(&0BUt(Pq=lYy?~?WC;b`^ny^Hk|)y8 zk^URY7P$G;Z&hvA1_%}@DBzeNgmw#Tdm4z`O@GWvrT%nswj-)a|Bj?;EY2*lXbtO{ z2?Uvys%6&-=r~ZFyVTp3krL=J&(o>R=80~F8 zyPz{BCP^3u@CYbJ8%9M4A&uM9he#v+{gbj_O5c(rnKd}mCF_CMO`sphSn`W3P`l^J z1(nhah>_AubdN5%Shy(F+{w&g{+9{}@}0CJp;i#t{l*!WNicbizj-2YBvw$baiZkq zRR-g6s4le)Q3h3)C)5UP6TNy(qaTuRIR2voQG$NPXv>J{kM1fyuJV8E&uHwp8a!|?0FOKL1!@e^0mj@m!9(5d)@?gm76xq zKmFzVALa4^5%dk8`P6>CBGrS*M=yHzub#Z(-`358`*>@94^sNsaWLDdU0HOIzljcO z*TQt1p02 zFCC0PGr%RtbjrDZ^N3XcZ}OfG-nQk}pZ{wo&QE@K`iq~*Z~e2|X6Gl@eB~J*n|k%g zI=)_h>upDzar>|B=Ie>C(tn@f>0GBa??DZKbMES_J~!(h>{~ZWU%J|mKEm|vC)pw~ zj5+*KAlYuIHr>P>s|gz33?c0wKDl-tq{`TZPggt$-sobxiU<4F&(rQ@>)X%)6q}Dv zkqslTNee;JqkU2;n0PW$jAU07TTWdgm0LSeFO`70{dS8%w9_GS ze>}Z~Ni87)mBHNA!wIwp1Khv=QQe3-x$>CFSN^d5KiBc>!kL+`X||7??0fy_FFy0b z?kf6Q6@P%qj#Y=cwOftS&$@WGDgg*YF?;naf2@ zKisIGpR1G44n^tq=sYe`+6514u(M|=P3J3R2_j}O4>pZ;hV#?UKy_{R=U@M#vN?2o z7)ydStK$a*VoX7*!&x%j)-tKbo{rSr2_5ckMFb6RFf74jIFN^bVW4jfPM}&q!#C9r}! zHmMU;UH}h%XpL;E7(@XaOu#>xpXHRgV9?DVF8cz$UITtq;1N%<0q~S{9$=(-CHZqX z1l=CV*Xn~?mh-1hbM~vLt0OdgSDVh-qXH&Vq$AZgb`n;l;b(6w%%J?C-i+%19^kW1 zobRmYWne3=WoV9pB{y|e6a{aGXN~rj9Icep>Q(Y2gqO{OM!uplP={spoDINUNZwI^ zuxH);`+Xhv?%WUKvO(0RgMZMM0m*e~?=OmpW5NFpz=MlMp3Mluk`!z`vi=!A4g1|HX zZ*drBA&NVeYf)D&@9!finN-CV^^jDScT-KMZ%h4VTd7b94?RJ6JdJ0|rC3vS*-#XO zh}^})QwQ;8My1x4ejVyHc_~FogB&W><(MAV`_^TnAkqe@_>gj>ywD8sE-9ahosToz zIsoZTf##{Edo*%KSEjKPiE0$vO$jc%B92=P548KCL0eyhjaU_klQgxWN2W;x&K@BT zO*f3pDhv_3k_0ECbeH8=9HhPPGn&?#hdZ){ZS82iz%_@bC|<62){|z+k6G(Bi}MgZ zLJLoNm2T7!``F;H(E>4}`AU|j5qGg@aw8guvl#Z{UV)wHrlPS!Sb>gb0FM^hffp;- zWm0q-lIG9m!7idN1PLV2FCBno##`Y@q!mdm6Z_fBBKyI;x6agLFVnMioAV!j`X~h>)$&Jf_+9s+Zz8bElT3RX zmo~H;)nf7&-t;R+zVXI;Zk_EkHJmVG*5%Fe)3#f@nqL%;J+rR4&P|I)m;olZt zAGzVK8F$R2ik7w8&-=S&2HT&%_wt?Z9{Sb-*vhAZEv}-)Y8q_5%mUcTr>c3r8&!>2 z`nsUNl~#p9D1K@ML;Ds$;iinIYOA&oLr*m*e3=DMluuRjyaGc%`1IF*hqTDZ7x2s^ zUwr51-?H|!R4u>X!|2x4jeg|JHwvC zu6PNB9zRCD3yFvw73wRtG<_Er8`fR72RnARO)#;7q`>M*SI1nyM9_~5vc8WK!wI$i+#9k;dV3jYd1T_Q=G-V`Bk5vJjLx{D4J>4MQ3qV zYz*0NN#4xQu|b_@QT~Uw zU-doPMpiJN{(tL*1rZ4)9N7?r?AW&Eh|TeP*JJ4!ii0qc!Bh$~f10XU{hf#D@2$Ve z44aiAR~e3r2UaFwW=fy(^|}fzydVn?WI=f?bG5*{!gg5yVP1LVm=tW96Yj#UjL|sa z>8x*Or@qBXq*tt`MBl`TSO=obw$Kqs7r57}u3`)B>7H>h-Pa#nJscOmK>?ltmw| zH1}{REPN$NN8i=H-C1xo2$lOL1jBFu5`OcfNMQ^_Kz1gM-_S zddXY=XdR_r{^EOPe{JyN`g&$|>Sw-i(oOoh|Ki|1N1gI1eH}EGaUWmdC83eOcDOa{ zXhxawRxFcy-24H$y#c#>-IFvxiCqePC5r4d4%x@=gdAMo~ zw<>TxpO+tee<;44IrXncPN-#e|=&DZY(gRer$i*L~3Ye7A*LEAT4D3CVXNA!84?*if6tp zJAJHTn)tFL>^;DM=vV{lnWG9BmSeZ8u>g(zGU{Z(2Q=MR+NAaK+aaD?p$#B0qXu(Q zEClx4S7+olrF-w3S${iv^=4NkuQoZ!+I zef{|t%y+->^mYIG@TZ=B=7&P8h{ZQIc&iQ|Zq6QOb?beg9HRCE0otNj?RENOa`LPH z&%?<_UwI;G0L;c0+{>(%tm__FjjjAwY3a|+7iNE{v_rF1OYDG|?2F#U*Q1UyB{pHf zDV<2u=(GiGQ^(kdY4?S#!sZ`&$aqK3$?yKn{K9j2kEu@@Ba>Fo-}Uh!EuDJ)gYP^C zMxTdHh0;1cq#(7$iURpHAAe`hq`)7ys&)K7aY9OTHjh8|F{@qc?2* zD}8{R~hOj_kZL!zVykrd`VB^U(|nJ~6GDiv?VUEgj(Zzh-eLE2`mfVjv;}^$ zB*n8RLQjal|DsuX9GH8v^IsjPIE_B6{eM1l#iw^zi~>&{xaEhKdwe(Ly6dh$bz_bH zsv5`E(0$eYMouYb-rnam*%^qlwo@r)^a z!5P;Kyz%uJQ%GeoP4~|Tk}--FpP&;>^z11u*fT4x9cDvpIguioZAdAw|4?^2g|ue9 zjB>My5VlU~=Mkyc*Q7zUY4Er#h!t7h=ouksGtZ%eUG+g<#>d(`A(s{nV)LToSGUh zvavRo#dFN2{hPdO#a(RAe!xcUH(!1`-pE()W+k6$y!At@WRPx zxpjW>j*s7X>mUA0`$S4#|I6>6dD#W;Ve3bEK%SqQolX8Aj$`v6n+`G_2{l};X(p_i zsYX(Y*O}F#R&2ia?+-3Mt<>(NcQO&TG&%im(@|e1*QwO!?5Y`s7 zAOwScvv!gDW8Q24#fa#fM17`ZP{h=(SKk(Ue@9Dql z(=gzVo%_zGeP;9B;;*$I{64Dl-)UB?3Cd0W@`+zv_T5iW?kn+K?|bux&#Bz&F7Lee zs8hbEub10IeuJ;G{4Z-TGp6Gv*Et8j+q%@9$wNeozy3l9b>EI}d~e+=xk8^AB^)}1}?|J_0r5B899O18i?|b)8uGiPq@vA@IdCOb$_4$9(*zx^0 zzfWKP+oLiq+`ev>M!Rj@``>nNJ?mLNgt))8bKmoZZ(gRNr>E~Y^534hR$o_Ky!X*N zzw{b?y?n=+-}>4!FVfdX^G}TZ>D)*7YX4?CeY5OUgo_-GK&1QhO|U8qD(Y#TKjG}v zbF&)4>|CdD6%QpS&JD$P|pQ8V_RYbz`oKR2uYc2>Js7vpN;YrE$h$=&t1c;%iYc;)XM zgjaUi%mHmQW^aM9R_!#{ZFmBIC^@XN+&KPQKBU-HJuLU_A)$AQ?>ZF+1Pn*fVsK_# zG5T?ACjwWIz8-&EbUgfqFCA@AAt5e0;6Z-H&LgPB_C(+8qI1l6K{~fyag>xF7zsE4 z&zCp2iaM~sY+GQRPJ~D>DaJ2J&Q#1-K;slX)!p+Rr1LWJxQ~3-?h(48C-;RuSeYJx z4$os()q1nz*{itR$tq`|-2!gl2WDstlV8W&1|U3hWnZd|6gmZe$q6`8b5M`6;oL3| z-1!qenwFaXHl}jf^#sk@H6yvxm3v^WwTC6S^s^vlQ^9;-x_G%a*p+}v^ zu=>PZ>;zG|z6c#B#Wl&tz*PE+>HhVsdb{UdZESKoYsvvjzIJn-o~7w8=Z!sLM^S4 zCz}uuST(71r^zg3$8pxuob)ao6~tgc2nCkitbRe_TuMh@4&1jY>F= zn89yzefgRoNPk5Ak^vpZoaC`1~%P1NogF)*2FQ*wc=SPNhbAE7m{Fpf3BuaJ`wJL`Opr#$loG z#si`d-tm?7?HY}J(giXz&u%A`K9}h54rIC!MhXR{A6ifHY`&&zkCd(rQ;D2D7AJq^ zIo+(>=b~HPvwripQ%%M@Pcm~dqbEOxsfot9Nx>VoVZ-?c@NVlnK#lLuyJhNmgkc8@cwvv7cz6dOK z#4AiD%3@+F3>amHs5L2z6e2hmBI7>U6JZlzJ3cyYQd^7Ss9*wYWWiW+p726lVMJ37 zI|U;Sc{J$QY!B;P#0#Nss@4cyrlBy;#Bv;CRWob!XDzWb#$Y5{Sls#8*8J(`B=3!a zh)M`5wAJ9H+)6o~-*by3fnPJM=I!kGc5{thYy|OC+7>&b{IC=Qb*Md|pk_S$YH)}{ zzc40(T^iUGzYNCDmvPGNiBB$^5M@0`h}Q$#^1W8CXr*j6k6lAE93^B&u&`TXE7g~6 z%l9o=y==YPUX9JK&Om4|2`3=(P{$=`MNo&yvYyt+!$0P1x!Pc4Ty6|{+ z$g-Bhj+Ma-tu{VtH)Gumz1%5eQl0mAhKg#thg;pPEF@h^cddt0z@E1oC(ZprlCq!; zwD6Bg8LSC5<$+yvhDGYXxG11Tp5Y)A)iAGa3J%zZrYfLku~d`N#X;Gc~Fygcj6pU)jJd>l^>btVIp+u|P<7uEqI zHrd%QYf6~bjC+1IY$QyKQJZ-usobd@Ad1HIFH4RI0^I?mhWOdkGPc``vk-eV_-y9S zkavn-@whly=zarI&dDDVZk|M@*w2s#@@H_I0rjn^fX9$EH=YF<>m&X6 zdj1ZQYZfrp1Y!F930~_54Y;T?3+HXpR(_N$(oIScEeJ(8qgV#($k+mT?(5Y0v|bEaB_0xzw=i$AJDD=AT1Ebr{W+_w-BI6{jjK?Dfub< zt6EVbqML#IS+b!!XaydFURLyc;~hH8?X^oa9cVS_;<*7>(wxFTXpON^e+CZd51) z$EEOW*lP)0Onv?w;h1ls4ay-gst$;Ev6~6d6A`7>Ga!4`GoY5J6?ko8w3ApqST~-# z#8~Qipddoaaeb=DNt0=S+vI3!iXo_&hn|z%kYbZq+wFupYSQ!DIpgwzmket2Y_rPU zX{Xv7sA>QLQp&0Fc@uLr^U0W-`EqG`+zZ#@${iO$#TUZf-1+N1 zc-V+Sv5t<>$Hz4);{WI^AD^ae95(4E);stH^m0Tq(l0d#(m!dCG^;7A^R1gZqnMg) zf>`*(8MKG;J0DCT6geZoECwHaK64&*YW2s;;>UIDdk3CU{CZ&+3>;DX zKmZM_D1N8~f;U}rRta!gxzdR%ypFh+yvYUM`NIH3G2q0)kNn@9-nhOkK7~$>2Tm_e zvttmoZ5qZDoP&^9?Q&h6^pCs6@j}uF8NWm|V6ufhx1||W(asSrLWEeR?1=L_M=RaG*_ z(%^M;wLQ=)IWe)fA6*aY3<%z4FeDMq!f82T$wN(0ynbfnA+%dkIX`I&HyLC!1_v(% z2l_eaqzj=kK$2*vE(%woQ+H>$&JuNoxp@k=MF{K5;JpGOLAn$@(jWa-dk|kdTip!O zUziOt)!_gUJyd=+L*k-R<_7&`nWFPDi@sc6* z+}+i~x``E5yBt3*^+H;f0_tLc?Xo?#5f;^1@C^}=Ymh^(h+2;GD9}mIS}z-{^)$d( zjlWq*;*V>FO3}6uDMyxNuOokRHn4H5lPYE5AmVM1BMOQGGc*U;2x%ul6=9eRn5XRN zYK9s;N#Qhs@Famf;L2r1Ifp_fNHa79j-+Fup$sJ}18MbuVLRgxrWmA`>!{Tvx@0B| z59Wnnk*Z-oI}s10DrF$b#G5eFf=eUqsqX}<&0(1QnW^p=pqbQOj^ut(vwwQ1Wo)V5 z=xg>v^9uw{pluOUE=%6xSI0(h_N8C_Qtzw0Zz?}m2fVaA7<3kzR9L$1^TGo*`-=yD zwKh0ltzYPUwHO#YBN#nR!Do{<60-(=2`EovZGm{G2WFQAJ0(4KJ!I0*cBK@Vs9oB@ z()7z7<;N^l3*VeOe;rK3J*6*3AjrQlPCK%KrmuwK5PA=(RJ!G5Yo=45XkBpr&axrt ztse+qt-VDXVzBP(1OrzAX2KSp0FQEadYpPX0ge<)wzv)IOk~>@U;zUb2F}{A$+&{W z!1k$b`oXCApNxPd7Dtr- z>)QPLYtxUdPd@*Ycp1j&usAT(&+>qC%E-gU^VZHvlPY=0QJC;5d~NNO`JRVNeDEm$ z^g~==o+DYPbS+JePSpL`i{#$-?RZ^IRzcQA`#mXn@UJ#WlpTQth(YMX$ z3IE8~OmRdGstRX2;bh?d`xU^je5q@=tRTeNxJG?#>8!{xtHI^qS>PGuY5R0zC(Ur5c5PlaUNC5(Xhlgwz5gEs{Cl zoS6`4pk@Qx*vW9NX)9;BU84G|&rO1|;|3k%k(}znbSoR)oDH5K1kzN8Xd&t03tBcq zzC1^&!Klc6R1OEZZ^Id)&JklY;52o)xtcoao{gYhQB`{$Mv978WHGDvN6TNGi!tBV|EPNvc z2nEwM5|+CPHs72bi^N>nO_e$QB^5Aw);Ax}K{{eoX#swZC!9l^mHC7|S^BG=;g^MAianC69?o zA*ZK$ozLK6vLukSH$>Cbw|;hsTIc*5!Z*;egLi%6vrTQqG%A~lqDs0Tj@ zy_8@uRqC4+aKOG@H%ZQA!g>G3G4;ilX^6#l344n5XcJS{Kn}8xOxVmZKDZO8hkT4% z^T7t%V+TA9m3ZQfacOw5@Dtm7%DCkRJH%iOUCl#Bm(8(&1*4+uVfoyJ060_+ZiGbE zCESDA=l_0)@Ds-3Wy7G%aI7&Wv$Q=)@?}#w)=x(vBM{n%ra_z7#HNNajl3T+urh9E zv`B{;r36RbO#5lFUKnJYC(L*b?}CU~g`N*)F|a)7cp7p zmXC2NPW?bkj?g$<;51)`cJZH#Mma<@2(OS` zSEy2#Jm7kQ1U@7Ewb+afi>gEeJ=IC)sW#{g5{Puyk!DFPrnc<2e}s zM79WWLbCCo4R!=kqWpJ^#zj5gzbwKJTu=mZ@>q*VkmHlaSwNLDJs40Tuo>X>)1qQm z1Aw7Gkl)WHOh(<|Hmkyi#YM?Ff@U~OL+R(oXYoO4A`tW;ALMlkP&t>!?80}-X&Pp= zSB&YCEg^34)02-CTfB&#kB-mk)U>J8*K@Pl>qG}ZdK+F~Ge>LMf&6smnS&y&H$Ze1 zB~9qSDoS<68m+}N9Ws;{I!+`Q%biM5yPcs@tGh%&Gs3pro# zRx?;Mx*45VBUovw(1^K&aEPrG_OxyZD-Zi&Y989yrsBL5c;eBf_(Wn826#&lM zH`y%c#0)~odz(>%T`*28j`}tXjZtnFn8-PRs>H9Y?5uCuXDKd_*ipXAfA5dkTWa0u z&^{lhYipw#&P)_B)y84dJ?rs+r~m&1TqX}mS}@LJMftO*bsQ~VZ?ZiH?EIJwN{MmM z=7@=8lpzhWV$=*Azm|Uo`v^8Fn#i`msA;^GH;@sJqb+K{Na4}i@>=G&+=!GXIwlJ{ zgld!&NgsLy6b#q`oXoe%Vpg^V$XT4ziUt6u#nAhnj+C6hE6eb{8`cNG%=Y)DmrQ02 zx#H0gFjS!$x)EwLW^u0BSpP?5tiWnxmC@q~jMcJ(fKQu_+E6VB!$`lt<621)Kefdwnv&cwzrp;y77%!eUK6G^~rZ>M5JpLH4y2F3#wyf;!RNiKEmYmFDQf^BO)k5 z&&2@vm1x(mAKb9W3;91pxnN`2M0kFC*&#!XuC-QB8O@1qVw!BMm`=>6fB~hwO#CXz|2CLLS?SAPpsYVMGd$5pb|)wu^?56X z43!$5Sfyv0fvhimJKR8vVtJO9Qf$wU)`^|lDq~?~o#k3zt2^xBj*ii$kB*PG_iU<0 z^vj;FOn=KW7JG&_K%EVW^t28ek^?=x%V8q`i3@|i=R8aj*iFWS{R-P z&%=}?%q^J1rRJ|^?jr2aV548^i7h-nIh`jl422jy7+d84wVX<#nTQ3+E%M^W`Wfq| z%6p6sYsw6(NU9XbI_X5yEaf(NaH@NZQ{g{KKf9in7746B+kcrJ>a}rJ1s*%&QtCob zI2h=Kces0O^-Kh?q)OSKr!4y-jj56E1G%*#$4Wr9uivrBIE!|`-?_4`F5_@fYiuIA zBumem$j&E474m!f`d6_A6dQ7f>6Hyd^PpiHSzN|fYR{s{A01y4PHgu&({8Wja7xUT zxXemiu|$cZ;~BZGsDG)Mc;oz^!s+X>VNoEK6jTz>NT)G3l4T=Z3f#YYyU0M#gf#wx zS}vM|p*8Z$Twz`cIPGb1GlpZ17Oz2F9aB}v<$ShH6uBbCl*#91F_2);+e(e?o3`Ne zciTt;$FZ4Rcm70nUT4`(*`CxS#ttmzS#!mjgR>_gLe1Ms7V{=K9@b z4{s)+R5oghEuW31(B`m_jPWtThw8gJxHZ(!tkt>3{$6H(uZUdTeU1iZ?BS)Z(cd-d z+ZS@IiMkJm9ZjvEg<8ARW(cFUR{!wM^iwwKy`Ekk5WRHEGA!bu3d-E$`n<>J?tWHE z!Ani|1q1H=9(#Z1qW4F~yL>MD!>7BGazjp#4Ld=$E0!d?V^ut` zz2Uq(mwnd7X=#Bev(pG^&ZIOLlb6y1@kbSD<;1R&R!*r!zDn2?(#jP$#iW&d64gSe zswS;?Zt5!I=}IdKBF3Da#X?)IHL<*qR%NRTS`;OlutOa$q!oT~*2HnqWy+m|Kepvq zs@SUgrvYs+XMZd2$Mi)bHe5uVoGZq9NAc$Xk1*am$+^x)#nefrmOnBq?*p02cB%t3 zbciv(47=iheB~JR2MN>UaJ7^r=M&aZI+P|_lzCESQ+5YY-oHVoRtG7|%?JY;iyH+( z5HH2dhCC$Tj*lHsm`CX*d1 z43V6Y5U?FKkm5OgQpl;lR)3DypG<#Ni-qu+y4F`}Hi*@J%k|mvS5z4R^|kPPqf!*8 z^alMoeUZ?y7%SeKrq{1s{94+Ajb)?j&E(DUUMUA^$Se9xwDM-g z-e0=tJ@yON-?{2<5CjkAP0@0deBGG=R-ghJ*pC~v;^u);6nsFK8=nMpL9r0j&UQbO zFqHSp9@d`?$#mPFk7&2w4}DwP;(&etVs~t6_2EKe%I~@t=9%FQBAd`Q>=N;!Bv`Rx(FN5^{&DkLi3@3Fu4 zdk7^Hby-gS02^DFhyxF~r=okT=$-Z#;mDJlT{X!)k1JUOm_R?PK*#plcoU7W){~q_ zOlFM<`VCuW{5Ex)l`Y#c!HM!hQo>~u7Sl2-gL8+0J^4pU>~&giF}JCj4b@cHwZ#r$ z1vQr+UZOLRvB^|NtWdhal=9iKxlK-D!8SK+?j#B=1FC5#iSPaBIts@cI%E2bHrzyP z6MnkG)&Gz}@yk>n(f^KfG~lz#f3 zpxBBnOO|X&b{sx_D79k@WZFOrq@7N2Qy_uXL;k=Y!c1sP%alSx{{;dK@030W{5x%c zl!lp1^8fzU+UMMR^)N{XWJ~9<_t|^xwbx#+z4zKWdptBy-k9CF6)Isf0Ly0beS(&t zepj#%zYME_lWuvlqPR#dF)oC-$aQXc@%w-K&$V0n`IH3{__CSoqsByWNb_F&m7JQM zXQxYy3T_;#V)nhvJq&#tbKvI=-J`j}ZSwz}i}VWf;cLqIa8}B$c}O$h6Rk?KCik|_XwHoDE0X(guyhymR7wW~AW^0?`;y9zGxz0n{(7D zW4c<*u(D$@0~l%>ksQPf(PDlJ`4n>?*O(G9wvESlBYn`zcw_};H6FQVP-^|dN=XIH z@LRv4W>}kTgOa8jD`NCN9zNx()E%0z*_Zidl`REds=0jq71%M=6NgUBt5W^Z=Pj2NJuzU z$EG2}!}aF*4NL;$1c_^NtK!+3lAf=X9bXF_UmG}2Do8J0VZ#OLD_y}GCn)i7@$V)J zkALU?`C6<+C>A29w;+ZlgM#eZ%L^cwYL%w*xogZM1_Ium%PL&zeiqXa2+|UQbiiIe zjX5V#zM`1_T`F+ge-6N_!k#G$JHy*h*ypS{PnFI2bj=GpQ}e>=zc%n{QB?pka2Gy* zbubs%=q@u6oVUun-^zTTEc0Y2b3T+wN46DE0d=ZXfDYE9Q_BJ#4h5W|0JO^D>o#}C zz;c|ltSEVA=t~Bdq3EfUBw24J9?mS0+XwHb0|w84L6IPqk&yx@G8~(kPpOYssgI^2 z1)~c{B=zn;l@6Sj46=e^^#txLtpx1eCoPE1XYE=7x6wQMiH^zsO7in$je$3>tZGDX zFHzn7R^0<>se?ZXn|jv>vl&LcC$+;42LiHj4bYN*K9!(64}O4rWbn=GQzr)$Xqk^D zu;CL(^xdq5_!{c!IX9viw~SRa{#jHDje~yKwUqj7P$Ztmnm;#Z`HUNp=c*C3?o(Pe zo6Xmfnys2Nqsk`9H0QxwnfXE^(F_v=teFlAO6fCt zZKb3z8kLgD$Rwwi!h8^4GSY0vP3q1^RV}rEf0c&{W;&orgIsyg<5{(9f`dm6r6cB> z<$YOjGZ|FFUId}3CWg}DiTD5VSD48b$4@_I`ch5Ho+DLoI`3ko=%Jy@4wLYTD`DFs z-~X5Q-23_8{>1VyaiTSL;}9Qg{X=+N1ix!R%Y11?89rrFtENmspZaW= z%Al?7Ghs}w*i4v#RckI+l+6_wfw1-3iiwM9EuDp0kWnSxmixS&y2|OBf+@^b^4;&F z$Uvw?W0Q6ci5|^+%DCsMZ@Ho`zm9#Gu!Wf7-Vel(Y z#G8!#D~3Km9jfQm@36<-BTW_D z%$Rq7yOf?@(9|}MV;HR4YeB>EC3BepY&;>?PK8AVd~AcsbjbRoLMDCci+_J5qx+SB z8v4XaGep!45pkYq7ke6W6eH5zm3MAe1Vtz6=A=sj`K?Jjj(d2 zaSS<(FJueI5dFr*t0s&30YX+6zyA+51B?c@WUK3W&)f{HMm}BhuhQxv*4~|N_MW7v zE3kqPM<5{*p;^O)h7-qM0VObThr)TA@Yvo0=pTO8_R1@H`FLy_NLfn^;!lOI55>o} z*!o+M^HQS>)`Z!An<;teL--46Xji*9RT+mF#%Q}_d`EkdND;&IW6kv5JXIceu5BW* zK`S|It83Ei%Sqf7!W_U=kv8`cQzTv2)a2?O zWTAPZzZR$>=)nL3DdTU)EOc7l}lF`a85l%+RA(Pw~YK9(J%rTLm3vaZ#(an>7Y z4w+eE6;h$OX4@Y?Vi9Y@37~Jx{^p#d|7dDcysTWSgL~fJcmbVm)iA3ZIrdZU19m46 z!Rw!fBLqK0zW2qV1Ka@6^w_TlEo+)prQqz3BmF^^cPC1C{X-MW|}%l zZ+n=RveiV5Lx*g;tnauCV6uUk8Yq`zwAdPJJ;X+6$j{{iWz@*S-1^IgOA-h;Wnff5 zaTDa2JU{p;=rg1o?srWx0v3!}=MP}vYkeG8OvC0}KudgIE^HVukxh-yFndi_+am38 zOS+*XmfN@|AhT$Va?3a**XiapI)B*k6k^rR*P!&-q;tnU+}aF2fj5cV@*GwWkFf+AnS#lbPLE4UNVV zwl>wNrbWf*8I>w0R4r1^+o{+y8^x0so?%pMZ#$_N>dfPhHCLm+0G^1e>1t>*^#P-G zoqSx570ofYjx``=Yc;f!WTRx2tsX4zXtZrPX15~in)rb_YjGTBb$d$feNAU=YM}xh zjys0MQ}{gccn%*8n|0|%D#v3JH9k3PV{s@lp!J7_9e!*7Dy=41k=M0=i529UxHu>8 zCxmrZH_|05z78>c(PrN3Oir-i>mEDcP5kSsC?~@|(lLtN$YbG`*Bf(-ze|Hm9~tSg zZ2d1qBn!SrsU#hoiE4TRgWx5NNY_b8gUso5 z0BVk2+m9*gdSx^4#KlZ$aUhYt_31M+);oW5qngra|E zm=hL(-;R8sf!36+(84Zj(28%RIaX4yx&aFVnzq&7q7y*|VAO*?+yc%aLo4o95>B>u zW7ODBa8sldvWSC~SOhJ6+EyLhF=j+kS2s;enZJ%SdHizayZ4QQyF$hAE7>o3??6$b zdZpr&Cgv>(uEL^^Ed(GT=)$c93e&oN(Uf-Q*r8aEU@sjf+G9bvDLXwRwe_uOMrff@ zdqNipo8mBZt)K|(t4+IL5=L^;09MM$GUR6w7 zdd14FhF_YS=9gIow z4G(vw$89{G{E0rM1b=sUK;JOQD|IuR?#Y7t-y2>&wAE*aA?U=-g*aOi2{S0Fl8D}o zS7&blD0^vf08iR%dQ?38!>fz?`K?rZ7RR3y{Snhj_C!<=Sm}l=yPB^_ZOP@T-C?PPB78@5w%#9HjM^J4$i|$mgO~-`7-O-4i%|d^gX$3J) zZDm^3zcUQyDx*!oX+jDB68BrovjyN$u^e%I2hv%H1|GBLjAZ#jf-$Gq0_q#vu^Ki0 z&?7ni;c)iXALL8%$UL= z+-zbuMg@eXQr&9)**26w_iYI&$|_tqSatD03XqVuH~05Q$uY#7@AjIu5BCIn ztqi6F*gYYRlA0r5qU<$g?X#^jR|M^%AQeT}^q*i2wHeNx@A)w=mtt9A2}>Q6V%{>QGK zYPMQ;>>07ulqo?WOF#zNr-rrJYOL3CTa6nFsM4N6wwh%~3z&s6n61_$jg+ZcXab;_ z2Xb2t$DCT21Y3=DSxlg}{!ukn8%|J7{F>E<+Wj>&b!|S!KmAEtE596VBXk07D^y8I z+ey%MT2nHj?Zjz2Faje{`?|S8V`I7RwU`h7$LN6d0BmWF_24^GD|qDtWyPXVB0g!O z#54A|H5PYurCs`oyV95+uKXn2@SZ<0L%By?3d*p?SoWUdcQR`X8a^+CyDEO^8x4i8 zWAS^zBqjo`fi~1vq?5(3KBp~k7xD<{>-PpyZSELF{ruw8H^>R!YJ7xAF!bDR#W%GQ zU^fzC51d?UXLJ-jdm{K%D8l3dF#aG0v zk`G`~w5HR~lVI=pm~*oUEsBY0rP~zPP7^{ zUnx<;;LFdEgM#|WA=AcMqXmr0Nfw0+kfz_IkZ$J6(1mG5-qn$`im8&CBdDc-^733P zKOLjH0x;_X%q+3srfnbqfU!jL1{k~UY9s0370A0_*A}wHND{|3^a!LF-i<#Q&Kojx z6a6w*Zbpuz6vsr)@{AEdn4#-*7(}MsT|myr*KL23R5y0C+QN-BOr9>fSlLYd?aI#q ztN5y+1*3=zlS^Tq62XvqE!1nZpx@p!Lw4`-1G=Bu-+E&D+WbKg8xz@4K4M=fhqB_BB^3Zcwbs$7t+pE};HqtO# z8U9^hMf<%$TCg7K-gyd-13Z=Wz>58rnNW#fGZRv*!Iy zV&W|s@1VoQ8F3`2&lI`=lK=q`GY=>~w-~;_4KLk+;csP*iTzj+sku0yE%X-Aeq>6A z`zJI<=#)j0_<>4^ylkt&VjqxjGp00h6I4r8jZm6w%N=Ej?+FrfSmx|CW~q`uVuWVD zoW}K*7r!LvJhBF{)+~mCd|QXw>d@M@B?`cBhmwdpfeOmfBPnvJ6gkG{wf*#}#sdS? z8;p3W<1QhidX@oZDwz>7bKO`xnm1@lLxH#yq1dC2o6!eCL6Y^YTbf*-=TNC+^Bcmq+0rD)ceB6}3v`A5q=yN&cJl`vX!&h8OAQCSufz$;xWol18 zxHgOsA;V2lSUl?rF)c;T3IhNosrycw|IRt5E3g0iNZ@|Ai{u_cL2@T?tIoFS>G4FkYB9%O2imN=wM3a-m#jV^|BDr@9E!ZkQrk?fTUrYs1qq`ww> zX|uPWpFrWIE{juM%MQqXPJ|E!NExjiBRe8a`*m{o5Mw!>k@XZ26Ow4|0L(^h^gX@# za(F|oGtrENrfcfUYb_Vic$Ddz;jzabzrbU**}~OHdK#$mDMBj)aXMIh?D%R}$UPot z+f_G#t%0m@3#`UHOfh3|@BZvcnm{$ocnCCj(NT1R$Y&63Mle{cpk$q;9KCKG#%q>+ zLrmP)gg{ZHT}vOI#iDiYsOn^d0zR2H=f;kKa*gKK30*F26qe9pEC0e?NX2)X_}=lmS0DS5H3Y%EQvhYGDsui1^Ea7hzrO_C6J7L8v5T&U51JH zp_#fD0$a<@=ld_1rEfRc#U#rAXx$=Z%Dhr8IuLA(ClO~6W$za9gbn4Tsb{`n>q(G; z?3aYX)OcF~zt3U2(bVN|EAX{=yf_PZ@#4%4Bw3|cF$E})By%ZRxMf$OM>nLN!r`5bf2CvYi7 zIO6F#DZ_Ltt!Pn>p@P(EhFO_xR<$+1EDSH0U)K4r;!8!>WYIM-7gRm!*BmUf%!y{1 zj^-g^1*MOLFT^Nqq}N&g0^8fGy-w2j%;$0p=g&foVX| zl?KR}5Zn9j8*yI=zvjjn?FK(l(+Sg{6W)G{YeTe~;FVUdFrfLt{F=zS9O%9>(k2RB z8RlE#kSJ2Np#)v!vAEgYIrFLkp*K4c(AeLs%iEl@# z6x;Z#VIFPgI^}l3?Op_;)E3D52fRWNKeP|+UGsd&ehCu|Q6xxL;~A9gShIM7i?VT0 zs60K9k7Kk;V5m6wNCW~N68zz|agXsKqBzQYv*64$kN=qLBm5<5QnzF;(REl!Q2_rO z_loo_HfV@6rwY5!^WGW%gdO4j330v=f&(3&;glC!V0Djbkun%=rR01UqOXM6+adws zC|>|S&iMA=-`1v@2mkJ};NQ}7*)i#@%UAZx`!9D7q7~$^vBA2F25bDQp#uU-cxxdc z2Q4@BClD}^)-gSbdF0)I1u^bJsffTk#zI0(aXKh9K~{*$l~I!N$VPDxlQjNzm^@lO z0JF0NZ!xBsAk;gSWXiH^ea@t6D<>K=CR4q5j3c)Hj?la!TRPS{*t)G>Jxrw`+_*Yd zG1UES5A@K1!#?+u88#Q<&~zkGDE4_fD*MItR`%0ic<~aL6GO)QbmaA*XKBDN`V6_y zdjL0ZtgC6gVGi7dLca?K zrrGe-+57|)TQBYtkrcJYmxu9tD)b!CL-g`NFuGZ^qp6o%v#&Nh6)%Dz3q;zU!fzWE zJWyh2&vBzoHvX}tHm+KP2Tv{HOFu~>007+q03!#a-$!l;2n=^K_NWf|-D3)rgu>yw-|K_|xUVI{z^mav>6Bo%>ob^9>4Yq`y=4S9dCMpXyyug3^h zsKIUNITz9|@()$U#gLnU!{1y;a(OLgWu#>)M|ZY9g3U_Ngfh&0%J)GJ;%1>sV1aZA z5(0Z&U6TDs>J~h+YUo&e{jU>uTZ4B2XO8mN18-|6FHU1$Aal+d*q*jH+O@ICNStx; z*B*OY!*X$&2jA8hT%2~9h1izZU!1ny)94gWTL2OAl=$1)*n|3O++0i3xwCd4al%(i zg_HCBA&M!-RIhYKGl{C`>GT(JvNC|0(hbZrAsbEPBUxT8;nQ48mN0b>ZqygcIQ(BX(QTnl1+vLvpGwsMu%hEukRZ{oGVy}})`$WP^} z`7vhi6s<^4HgK3}rHMAdlbHJ+iH&a#p4cUBhcTes zX3KGqk0xdHG1znuTuq@V`I@nUWM_nQ8^1!45VrXz{}Gm%d>>ro`$+ZjGH`@;el)hoe3&&laH-R0X1+y)isnuXuXeIYakU(1KxW*ihSl*p?w^-CO{2Ui zni!zjA{w8+Wq_+UDTlRzwD{afJRTIHSq-Qy zp==uHyac_aR3!~d!UyX)=)g0wh31+6Kt_{E#0vhxS5qA*n~e@tdf+y zM@5B91eNCtjOakrE6T+z$$q!y-eF-4vL>(}Tv2P{XzXOu7NcR32SFd*$VHAiuQ zVU(J)krKhIRnd5Mm@C68Y5cv=U#{l$sDNU<1_P)@0OB1-XaKAbV%|6)SI)*iXihw& zR0X5A_|%bA5I6pDxUg9W`?RjdST-~RR$4QQ&Tx8)EwN~a+R9E@sxlfB{;3Ld1|dZk zc}Z>QlHS`fkXD)gG@ir7ZjHpFu4<{&=adY*1b9kNx@QH5y^i$-+LMBg2Ar@~LaXCs zt4y?`0>wHs{T~8O)#S3wvU%ODPte6n?B!ijp;Ql^u83u4BpBgmzaIO3foJK8E@+8f zV3^av-N~RxRCaiT28dn;e}QmBx=crjk&;Xh+Y(l^%Vc#-Zf%cOE_;%VCJP;pC#1%T zRQnA{&4`T&;wmU{L{O!`7;OfR5JQlf%_5IVh^5PQH@-pe9FBm~0+H+GGln}Bua!%O zVaPg`+$aD&X<{HomBM5OjV>Zm&Yolt>syQk zhNP3Z^c9)xV;NLEW+kQ20=OG8z7t48os*CpVP>6kXAvI zvQ~u*J&3=Du10CqX_!PhR$|@s5*UkF8$8>V77#=tXbaq@hqj`_y6(3~nI8%af{ zX7)}<3v=jdMhbcwN1gfvjF_j(cqwQ^>7jqLx?Yi5rFT&!0yUu-=Ev8Ilg6acqb^R? zYI$ro;eWfgu&e|2$T)Vrw2ow@Kr}u$jz_x`)*CMjGZJ~#N7r$0ngZ65?r|chSw(Wd)|XL@SPHyJk1#=h z+cks4)eG`z-0GrX+%0ONe^4FRGesb2T$Du8&XB=SuQ*$%g>uE*?5UY*(;?i5z>T_{bXo~KY+O`8nul_tC)HE|v*v}T2Bc@ts* z5JgRzvs(d66G3S|3&8hj<3YC%l^(^Tj*1>aeayemy^q_qbIfcNJqL>U7gKvaFvAPH z8CkD#e(<658*j``#O^YuuOedr#5e~AiRvrh#-CxtpZqFE!veR5%h#jXo7~pKe+wlsIU<~E z3bJvf_P?>osLC5<14^>#!6^GP4H8y6#yYK96T7#f+{!6pKhd#f-Spe}`7=Z>yp2r( z(supnt&&iQzDPU1=xWPzTi*U;U+~RwDD+=&t-EBdm7hP*WcqqAb2HGl!RALJYL*>8*A&a2m?E6>g$!v5UXj z#vblg8R`hi-;!r-B(SaH$qPu@thSk(^ee%tB8&6*JkY91p{ zyAE38C)IZJI&7RCgfJH&rs$&)LN^j#i&6mOVuvjEu}lqzsEum)w(k&U2b3u4ImRA- zjml$GcMSu5C_Z^yn9>4AiUJoW#bC>!BT_vE59q>+ZUO$wF(a7)x`92cDUub~RYVG@ z--SGe1T`gX(DN}aYLSN?3o4rk2!&HASTcXsIwpaH2itj98+wc2OHUrBJC8Y*+&eXr#?zsD~&m*>;+2f{{5VP2j@poV9Vn zgZ4t=Qb&pV?U)n$EJA-S_ zvc!OB{@}jT&h>ScnYl<8Fn`EH%>U$oBAnS0BxnAC>4S9>17QT!P7c82AeuJj|EsB; zW%%t_Qk4fh^aZ$S7pG^gIo#4L9zPQu)~Sc_(5p{A1jR;Mxa9*xooOFE%p$3wJ%P~{ z_Xxw-Cawaiae7jdEt?5i#tQrk+gSO)IE7j&nT6$0WO(v|t$uYQ^B^R=*ga#P>i&gp z%6IYk(o2s6Fi{v#mGzE;FzEardojWxWSA9FDhjMR=(s!{#XT=Z#zWB7p{IN3GMun* z5%ndFRr~1%nF9P_m3B1(ng;*!NJ%tMy`ddgfF{5JE-Up`>N+6gj-v2Tpo*D~Xa7@v zRI{7h)`Bx~-eTEY@46PRI9zt=*I$a>PPf&Du0CNrqINR0c7Fb9qw?w-PFj2iIqROY zK2+tb2)-uqXarBMLGSg4c8b&01n5pK^Z`d>%CEEDc!o?sDwPpfq4?h7zUM()IR>Oy z1|tmdP=N;L(s3o=#RkELvZQ`=mBKDO9Tb*?VPY6MuI(N}mr9(hhU#0a_!>o0S*7>u zd5_P!Q-~i!p0;FKv!6Dw`L~Zk*n{ndsEEb}Fw<~9wZyBY949!=M6ue?L~SaZV@+5G zhJPN#r7Y7sWL4R2qvrOfMQV&KYCwhQP{okzLTWi2+MgCFG-z0e5L0T}D>9`?}J4CLT~q5?EQS{iUAAgY^6R+WQL zL~7H{a4?-=%=v#yY7~+&Z&!Dn(A}1%Mu}*^JJ9tFrqu9|mLDK@}Lz;E`^N*M%fgGJbbmm^I=DASNoULR?zh?Ef?`z>>WE%E1WlaQc9T25F| zKJ7cwJfBY$UcPp`xsnf=*~t63awMu1(0$DMvgbIUsM2#N;ZTz;D7f%&7%LPeB#K6o z41%EkBh9rGtAdhztwD!e;R%{R{!;d;{5)O*9xA;up|8lO;S2r(4z=6orpX9e>!(8$ z$_+I`KmNrz`5Fr1C~^C{M#U*R@Tqa=4qjg)KNr2>-27E-_0ji%KBHrHSd%M45+vEs zH78B6i%&oZ*SI(w;sMQx2XspwU>0bI+@K7hPLyiW0vfha!d|^2Qjs+8CmjebV5{%T zAyk*TDOJVvrDm6DOyZ4F`NI<$m2&i${$vS$t3cBb+?A$9=`Pow2C5x;80w+^td)Aq z)ub8hRBdd@ue{4O3Tz3uS|%QH^lkic?C|%fGYfG%lqknN16GLY$i^U-W}mn!N_Ks4 zto22kE@20s)MZ$pZ57p&dL3g@sv;E8S+Zg#l0*|+7vathSMtS=+0k;6(ABj(l6Y&= zuVU<4j%G~2=xlQ-WvgAnd}tm3Ou|5oL4s*(Rv`Mr{NXsGlSumCt8?}Fu=hGUgR>LyF9=s#uN^MV7U@1Bi9AP zBg}ThS{$Pgu}d1Tuzs1WBC$tfdWkFNB0%C*-ysN)16pF15~oAI=PWG^S~A|NPmxH$-5EDRDC&i!n8^@I9Sz1yR{o2M_xzS=;2?6~6d^&JPf8A^+P$emD}@uMgP= zJF~-@LiQ^|cGwfyFAmxBj_hULwU!rwf$_>zUUI}C>eXA{?z?%17Wj^d<%=_z;btAj({c z?=J86GJ#fQqB^ogQbnn~UQAUij(KlvCva*m1qI+jL6WB6Lui16RvDneX>SQ5hmz>r zn|SDZL3V%}s8J_|UQ^*ms+b}E5bcnD(T}Rdi#|TKTwg~mfr=Z)n+-rie=>aJJOEKc z`U?jJk{kcJrQ@5IaVOk2ACGV3HLoWa)Y3EYldG=vd zf>d?86d$>|0?y2FN(-2e;4e^7uXF^|10ugW*otpUDcUtDq7kW)GrP^m zN~K9#vFga@c#BMjCp75zv)SxTWTX z5gjh=&&Cel5EC@f0HXzx7 z#NHr>2jg}(lx~H|;h7Kh%PHeoWJt%dDGfatYWVCsx zmXank6n~%2c~od&&WS_^L%g!S61a?grni|o$AegCoUhRo#G1FRle0;k9iWo*Mx(4) zx4F~OeD!f8S)xFT=1MHfe8++^-?4m|M75*~4p~WU8j_+s!Trx@Qjtm3$QL<9Z6Xx4 z*QDUOww&3Yq^LsXxxzogaw*zcYI>s5W0gbj%>31KP1&56K3V!_m;;E0u13qnwzlpF zy&4VO6BNQ8+4;5V@l14xH7sZoWnuI4gNHTk9LmP2bFbXj`1eF&$CR61!P!xM;Oa{I zldCK7Pzg*42vbS{h!^C9R6*qn{12ML4LKjDK?rQRMepRlNsAx*rulE=6hN0Ov2smW z#*dFk0XCNkglFn6dJ5B#lq9X(y)Ai^tpqlk z>^5CJyRr@L7`S5ve}X!6oW&7k@m-aT9~h{%l{|qxBg;P?JinY0*9iIx7Cq z{3mCKG&KLC3P`{f%=e94oJ6bx5@{8O@VYjBawLK%0VOK_Af9S=^LMhT!|SlVespzl z<~|E$pfB~T)$UpxDfhnSdwHehRZMCRL`5Bhu{owIz>`O7C=xO~!UmQT89`J{`NPx`Lq zlU~1k(uU=eu3A3n>gAI@!BH2>p`Hly%cil}Y}vHWo+a%M&XV@jS<=wem#yzhXG#0Y zSyq_K9it;`hetFT$( zj>)7Jy-8&dhlJO{$Zgx5lgT8i8kshG1TviD3Uvfe)%1r7(0S3!+$F|gc_0Q70+iII zSp8+!vSL)~2Tn=n`8H2!k^u2xny(2Ga#+a{5-!x(-$4ngyYUkvXqI-=dFfHI*FQBf zAdqb>T9nnJ#+##Twpv0YAr*wASui zqkw{`kJtjsH@IOjvC<*ZAd;?9~(+5CX>w7~jtk&8UEz|S3#3|3qC z%DC}!BOwMzTY|z_mVp+6+6Fn=;=m>w2l`UAi4c@hgCbYF08s1a5+G~*qE5)6!akd_ z6!OBT`7UY)GlD=}Wi+g?X6jXsIcUq@7~t8rS3^+bY0D?2I&K{RAzoaZ-B@auZttjM z(?`*0$z;l@`k`^Zw9CWVU6vGU1tVJqT;oI+c9lCltEzhRY^v%8AcgE=MJkAsLG_sy z2eLAL$Z+>bcYXJIp8!2ezAQSo8rGeh9 z3;F4NFs$~2<9Bb=r@N>-p(}(-@g8WF`az}!O9IU{Q+<(&#Tt@{$(Uq7VB3J8Lvqt| zAvL9wVY6&d)LLLK18yyNuNS;8{pRN{6&cgi#-k&UJ~zeuCq92E3UK4sMYZ_Md7NY* z7WaQP((C`~uX&oB;32zIncnkR3kb<=~0(O zykRr{{JU50zBG=$$5N8r@VnyE?{Oxvx#Fk_!4TO>pcrOh6C*-0I|f9W@prO+&lZfT z8gQ>awZ@qdyprWeIarN9m6eoygw@-Qip2Ryh}n}goR8E+JT>Pdb>}0A_=x-+2!Z#D zff&2MhC}QGj^ULCj`3~Q_Ul}5G#&&)jSr19J~86r<3PpZ5O|3ZUgM`>T?Q1no2d+A z+e42ufS29Bk;&~^St-O=3zgES#z#lYWD|ks=>$aeAquc3)@Fl^N;dyRT%n`e(ZSvc zHq*`SE-Plemo~yN#eM*raT7$^ZT%&TbH@q^*cM(O22i(F(SA@SBUlpvnwMuHiZBnL z43-2V2CL`oa#@QVZ)~$yu6Tvkqnnd|L`;As}G=kx(i0Nd||5dm{aT-ZW zuQ>DgY8qmf55dG6pBc%%uOb0&Q$m|qy=??V_ozmd4d?)43ob#nHmn$-%@5NO>BtcK zx&h5WI=4#*E@jbcN`3p4a?an=FfBRfJ)MY8imywVgNzbJbzui3YN(nT7TBkrf8G?a zdgB+)#z6m*F;H<5W(1_gQ!W0dpVjL&t?QAFv8sA=_Q+_s5HK(3%IKOMY!iE=StlKu zC(p0KZ@aP!G#*u(hL>tHZ`aR`P+~dwfZRuzx5*A_619n;sz<6Z>P?mQUa{E!7qmHx z&A!?~nlo5K+F~Y~vlRgVUm=_Q`Gsuu7dqMOe^URo*sOeT5Gt^7HD;>}5ckVuw$hm( zNyAt-x{Ug#gWtN7683ZNaO1NhCH0j`B-{gZpQk^}brK>Q|F1S{#w~{^$W{g-WS2c} znOV>9X0dX1T+u}gHiZyM6@o-#^Ac$j8lptiLBF5S(h*(&YOV2yBiS|j67=#Vz{_@6 z!F&a*=t z%H@k|!3T!6nShPIV*Hh8!Pa`c(Srz~r>RWZYbXs6+EGl5sDRHySD2dY-jA}^ECuQp zP$j%=Z|F?!1VM)Y&@IwhMp4!k6<@r6wYeOMhks0#5j8V$26#J;zU*+a26dJrQS<*3}QrINH)i&7;GbuFIz@zur8 zymxiuYa{GV)dEAsL@#PHMO19?C~XCla;5l&q9>r=X{SCTUmYxuk=T#$Nyvd-UltqjhT-irL&^-s#Fv{jp3xos-qdD2WVnrXITGdXq zs=X9L0D;9XFxG14<#!mwD37}b)IpMpYP0y3x1z?4MzSEG`8c+Kc+Kjydf`w5A$GHd zQkl0c=i1c`%B5sl^tzQv6iaKM$^Pxd`qpnRYTaJeDp6Q*vBBMCkTMr?DpVPFd~RA5 zw`-*x1ti7U|3zmCHI1;LQBApsHeiOY)z{MTWQZ&)r#McU}3PBL4+dCzCJ24HLSA9-NiIXnQY$i$Zh z(X+paN0DXX_AJst3+FGIxAYgp55s(2dy)GnU>O)4kUrJLXm6V44eNYoUu23^~|j<1?I55Q27*;`zG*pa`uqL?b9c`Cvdj#oB$u1dS=(;*8jsdD6!W zBCEWjMfXp(09Bfx0TyfaqgL}q-zuieioL?v0q~k-2%rfdL!J7WKDA03|Ac{P&i3%3 zKoSFb&fBx}N8fD!$nk9-1+HapN-*if_B2DI?5}o)hF?_-jc_(%DL4|uQs`S}Xv)0= zLnAm=J`mFjLbrE>h1kFbFPR${z1I3u*wD@+<8f*Ba18w>gV3GUfofyz61}J%v;IEeuPbbqSm0pP z&k+tVEQFoY4JG0M;qX^Jj=m>W$TKkq8b_7?3q=RM)o)#QN%ThRy-T7E#!ax^Bn)T` z40nK4SIlSGsFWh55P-bQ2_V`Usz$iPUD4tYoj%82H=P&9o{%C%aeSTjiR@F16d=&> zVZfQ>LY7>tTd=CGvT98R(OyJP$)_1@;?uG!B{yn~7c@g)d>RLrH5*_M<`x_ZRGG{6KPjTPRYB80o91NmFe+1+6tNF_?-fgIkuK9X&!%7P3}dfEVGY4J5Ke%^d*Lf+0ACi(30f`Erie-w1Mhw zZ6bchg^@q-1QkqJ5&2gw%zi2%fKk~U^|tPyZ=Sbl0dVA$)5o|3sW4ANm8PQg1mDyU zBJiXSA>1?Y0Q|Xp*d%Z+1KRR8vzdV&pn|rf1}P-3xC&u)G`zYhDkqDqU;`NnN12n9 zF$O`;5cd)4DdmpFy|bWd1bg*fdCne217t%J-a+-$$(Upnx`(gQ0!eLmjIOgr;kPND zHe_$Eh;%8ev{4mYqjRJXj@&Sg<8>iEv+S>4?~$A_=li6FE&UIiJZI~;KIb!JU97=p z=5}{+Q1Lo4LhD$tZ6?bjqL_d!91sd%iRP*lrnQRshtJno#8y?*Dv%07d+>v&#q$rb z8cLdZLFr)RNM5^ZpKEo+(^j7%>qAiHSVIfZIIvQaB2^F}9`TbJJA#etO&^h2YsFIz ztj>PVBXcN%hdDH8BB(?KSeck%PT6ku6}WJCgA=A`@Df*y&Q9-P1948=eJW7Xk^@DJ z82A>PM9=uPlLgO;k?-ld5xt!)H*URTgOj`1HNBw8m|Nat^e;8p@a8QKjn))zZk3?J zYyQyMP8MbZ7>3X^J+Y!73c4hR;Tc!&mkcZDYB(OqyuFmsm;CvUY&dc zex>@s*98x#_Y0A}dNP$JI3pq3Dh2h$laBZvsb82)dC$bZ1%BlzoBsu;D#S)j#E6?t znq86(LSZnSM$=p<+^ylxT<=v*O~UX`t5>v$1AJ@s3gbIWgENqJGfoMm1NFhM271ZC zO<%KaQ~cJQfkG|P9Z5blBLtb=+@qktX|V=cFzF;YO$maEHHLCRqmRL9bZ!PN=T=6T zismc^OnoLhA{*XdwhqCpEdWdL_)&BJF$^KagZt>(fOnm;RlJrWZ5O;xq7q)m=zIRf zYuNE(lpPV=6|$Ot`nC2(lsyMdl45?Iz@FrRwn6c#17aT>KQ<#zPCO^!i8u8C!+VC) zWQo#B4-4oVNE>=s+ny3W-Eu!@R2* zoAE?1Q3mXm*Xl|lbBQGs-=+ZxTWK&4gGsCy9dsi=(v68MRo>NS1OxFjszINxV1#qN z4(*6w0*h%g31goeI(Cf#RF3^bW7~LvkNu8k#(sEKIHi&{2jtfg>7|2`ziqAD1eB5! zwuVTCI5(!_&>Sf+cE2XG-bb%8%{4GJ5H1|S=@rxq-IswZ64fx}rmvZbM7fiG=uEVO zMB7*khciQK9VE(kfkd4MokUSQ;p0_Cs&@ho6fF|%auOv%ZpD2yso&x`pukyG18^#y zVO#{p0zVV?5mR$k2H^!(W*pyGJCp>|asL2J$8JC(g2&jRJ`tr0(^g2yItc?0RDbTQ zN#}^`UOM)Kg^DVkp)7t~iv$$opk|MRauGRxXPnp%{1Qc9in4(R=i!g)TZ(b^k}t zABdnw(5CjX$Z8Ne7tWX^*~~IM-9tJWLRhOOIoVl}X)!@Ccxa3vNg@;CmAJQ=Ywqze zYCxer$vxP03tx2EDGS}3`@2f8g`)sAOAbJE9^cdt|I0aJ1(m-`)jxeD6-nXA2fA5rbm zZd3!~lm1Cj_ITnhsrUq>cQJ}z@l_yt)KEkYxzflkPDx%x1fl7DXmz@SXmwf^XL+hn zYGp;1)hbPkI}V+8CLq`X-%u}rQJ_@)V6sxVmad;Jbmq&*wFQbLW^)$apc#nN5+9A; zh5i==S})m=ccHiZFb^yZ!7p)gwn3~hRSjb#XQVY`;8Rc=5*VCnI~ZP!Z3`0_cwItz z67Z4)ya)x|6B79nKm@{<9%cHwFlqhNhB1)Xi7binG)M#~{n8-qBQbM+ZB#G(2&ggO zRgiryTEI(@=l5syna1xf)6HG&= z($LX5;jadFYtM>#r4@ZdA8pwAOmGkz=7i%iQ`|tgu&D`m(=x1B!-NggybJF&x?s=; zTLu;F>mlg>n!JaS>9TdON|A&GSORnwyx}5Ltq2KkiZ&3EXRRS(jZJ}hf?Qi6fk}UH zMqMB?1CE1nG2t}HS`Lw_19{`U1douJRaz#Rh70B;l3O;Kxu?u7LHdi8=01&1?`EKf z5@nY#vW0kR@{8f06*;d}7EJ)R6ktjv#$=@cQ-z|7wkg0hELA$HO#ucv6kye#rzpU< zE9R&;ZdZUI5=MX`s3%N@%g0;xi`EsCN;z7o+T^wtodcfg)+UgorZEQqnniJu>B!WC zj!Y~B>B!U%8Tsgf=5Uvh%G^(>P=Sbn&;Ihlu8y-C=_YTz^Ox)vr(wgtImstLdoDH zqnIR4sCa~yvee8jYguYqDZCV+(po1}hTR+kwZl?_77PYL{Z|y4bW$ib;bICk>IPI1 zj!`HIDOMa+K%rQgqEISsQ7E@YpO32N4V$ZR2d&_x^p`eUkMHr3q0Z^c)bdAQ3P^ zOUz&mW6%gdgA}2qxeRMFS})yi9-&2AZ$zm2UMiKrD)7Vj@oo?eoyOteM!(Ny%}Ev`Zp1NwG7Y zs5l!E6W65XoJn1vl<}*q>iRKR# z2l?y5G^Or!$lrtepbUke#st@ik7QmqerO|t22%ozw}}NeS!9dTPR&lL9gB+P8`T^XBeQIzqEtdf zBCOpH7hG>%>ZX1SLoWc(#bcMOL!g&@2L+9Z8xdW*2S~MqPt#Vb<-wKXEZ7-pA-^b5B$1CZ%9HstD1lR5v&Cz`4Wb`43z z;RnESQcYR4#fKI!>Kjd-bttaX3M@fJmI`)l-6P*H{cTgrG|GYC`-RTGmbh)RCC&MV z7E69p*a=rEHHfxp@)3Lr`)Et%m4Z+tc`N|A$!4uIm%EDfFK$VOF79r1F(Dn=ySR-G z7E*t2pnk3UqR*%*kOf1ymmvTFF-x8&+0XWcwLzvag9=%y3@uZf8AGNx%dnRFTMpC+ z{}UV!qT*#s(Lnc1N-^hj!pEIDbE>lO6)z%|md*r+PqD3EGGgHgVhL)B76}BMxlA;=a&ixz;srXhfzqy~=u0OSfPgL+ z5=$5iojP;0w9ZW1oLFq!i&!WVn&AB!_%y@Q5?J8bsWYpqib`kJaEFN|L1%^~29^Tj zB*|DQijO*aX$@JUL+mZMa*U;_%vMPi3A{`V*-Dfu9eARc^nwzpC4d+7P$Mcf#L{o=@Wc@fxY&aCQO2B!c-W1z~@>XlNF^e($C^(pQoU%#a>`@foN2T@r#|igNsV=0w?YTHnFvEp28el ziR-}vNx}`rwZuGy#$BjDU{T7rQ5 z1yi%cj^WIODABl#Z1}`*T8C-&tWc|9cAAmL8uQ^I2q`j%A0#M`=J`U5o^+s z2X!1n7m;wAHJ#JaRYp8i%*HYfAR!JFggE+*X7|tr4tIEI6%g>(R=QL6gA$G(QLAt? zQc_(fZgiDdQAcqCYbIW*U-wWSRoMYuQW?P{M z_g1xkBnm$ds;yKYmzr58dCG1;6bnTA%lcZGs+Qd*4aL^rw_`->Yxt>g(6ZyFPHmb3 zLwTUvIkc-@_D>ICTTc&dIB^{A<+wX3%-Q$)qbQC*+y;LRmF4xfuFF2y#o{bGo?{;M zx?V1t6)PWNBscCEakf-z{Kt(F3Kr5*QI%L8W})Y`7`z)&GbWtVar(6JBO~IZ#HeJL zb6G3xm7QAuk=F!Jr^mUt-l$IkCayV_dqnuA)+Uzt;cY8Sd9&L>#y3|0n1!|Hb7j^u zSmfL}%`(_&I##fnAXvULJ?NG04Ttc6zF62I2%d_dt&XiT|Lsiq#ja+rg-3s0=L@Ka zACL>RTb7@n_&~KwefRd}it0O7vxT#x%HzV?!+s7Z+%asDpN`kP%yo4;DJlwdc39}R zjRTyhkAv-4`~xz>aZbRZjp87&)z*u>tvPLF@@NHw3JM7_s{z$pJ!LEb4MfcF&`OJ3*fd>fK7cy#p&$L{+^H3p+%h6M!onfXf`&Rnv)y4uuO8jarRU7AW{I5-#Ht z$7x~chiIFTIw3RXlYLMgvvk^#ZUBoH8wwdh6|t^WArHKF6LcUT4&zyht6 zA2Db(9xP~Ro_J8(c{@|{_tG@Ql~6yF4EjIuKxbY6j@JN#l)l?6`Y-7fz@lRuG7^}@ zvyW4D^q_G^aids|AFeow-m8qqUt&>NLSxUo1QRLlIS7WZ(DjI_4`KU(L=W}H)u(ME zddu*Gf(wB7_A__+S!Ke|s$(#VzyA2YL*)~o`-%_!<_BI%cUyev$14a=HG!#O4^Ln7 z;DgPs5C4Ys#NK0%H@nH0k6m}(H?Mh6D}R7%&4Jy&*6cZjccISygB2e>#pm50&bvO` zR!lT_>LZlPtS!ywyZVEnmY@FA!OyMuz$dSHaO4*czWdw$`@ScxIrZR3?SKB%```cm zKjD@l9h#9_zxS`s`TI|h?=REO{N#>ne_Q#!|GmxkU9{;Bbo<`7>F3Mbp5lK~+$}&! z0o|uYq6feJ`WNbd^A?g%*|Jsk=0yp(EarRc80gUcr;k235b^e!UW{13} zME#T*{`ad${ok*;eqHh6DrKS5V|t1~Q{k==d0EHyGJHvWkp)GTu`H|1aAFs!CwASi zu6TivC6r_kbkj2%PCWi#HexO;=F>|uM~Jc|-uQ~~Uc~Q9=9@+Wv0tk|rK1QA%h3tL zc9UiAsu8$|{-3uw4l2M0b2Yv&%Yg=wf(+iy%GRt6@_<|s3J}w3o4LQ*9^LJx-6rfy z5x8cR@{JZfvMi0_DM_Q2qEcxoI(1&_)DZBT{?)AGiIRkquf&uRyF=Td4SH8bsKUsQ z24wFVZnu-VM)MW+j@{*&E9V`-AyEBvmuq*O-Cd;Z(ssuZx{Lkpb=rYs@0iWL%Q2{^AJ0ztNZq2JU^X_2(QM`J3|_7~FOyFFvA{XN!9p*=rP48Ic)K@(sFx;FgW z^RDAPlWuX6k<|!B8K4Zjn9&Jw*w=g@HP|lFt9n9mLezyalKar7cQ;nIic2 zs?XL`cWbM=3(KO^FZ+tMGfmrTlB?YKG%6pV-s=jmjl^YKf?^icyeZW}3oe>TvqP8d zqA`$@30M`rrFbp$ZAkySq}ow#d*wh13N6S_j6a^_{rVT|Kff|kU5@m{dw6ho0 z);0DqA;2XQBcuF&3pw)4%Fu8;cBNN=0p?i7OVKbxX zk_ALgNnG^uG8SmDt2ujjT*ZA$nf#{u5JXFv%1sUIUP-XFeoWim^_mWnzevk(oc>`A z9EZMHbbyXQJ4CZ=LSp#QH~u+2#vT=`{G!!0<~y`)I=v)XCCx<6R;o|3PY9l5Q215- zsrHxRX;R((3OuXsx`kk)TF)AvIn}y;=e+qKa*n!lt*GU$*9wi--)UNZI!qP?Hv8 z3)kq4g^oqwBZ#)~-i@}7qwPuHXur3QlBo-_8l7hWV?25eq2RRG4~ zHGXlJU3P}J?0%swkL1Tns7j+reL?%I*{kHa7GXgx?sJuZ6W#jMeiPEPF)o5)efum3 z5=&#F%{v^1fFh$t&%k;jb1_A1g-dX1)RwvLN+zH=bc57r8SB* zQ+)cD<>3`{PyBM2K;V_x@mY30rz(T?X!UpF7?#G@cuSrE>Iuy!Fq66cz<9AGsMN^}D#-i<`vvadsca83tzH2s`8QVTKeqgM6o*bVVo4b4O*w*R& zGuz2KJvluS?)Oek&kZkl{=TvCoxA2jhFznxBjdAsCr9tT3CM0aFg7zY4us=7qWv?I z5wFK)HcjmqI~eU5og14OAD!%c3Z+b2Gp(YisxsO!GkTZ5+u>E*GP7fB#xghzk$*Qb zwtaeLR85cW8lM3ZQ?ui9cY}bjyY^1c%x&5cjc%W#!CUu^(!8BB)BE=hPw$_ai*}C9 zU8koT$7koF(H%RsP1_>~+B1D%?An>>Jqw?U$w_-P8(sc}p_@l;f5VPFi@FG-D*9SI zihkxm6m2<6wnxdghQ8L1`h3gg&H*cML!|k_UzW(w%MrX&j-#!cR zP3_!t*UejRU%!6)uF;v>p}+CD*-dwC+{n8(k4{cbZy%kTp3#eq8_UlxUw`}U?FHPv zeR|KH>8VXO-mzY}qDXc2bVt!UY1{8mw|>;V%Xp_Bwd?ZBL%T{Ey?xe*RNt$OTgYqe ze{;xZ#M{bbcqsF{Nu&iBkasJGm-E3g&y|+vE+g@_Oe*ILd4$7v0_$Zx)Vm~_wl6}{ z_JwHTg%F^2KTDbVS+?Efuvq}h^{RjUWn+70x6h34og15a!-3IB$`nrbl6IV5!Fh)3 zmi3p5RByTY?HZz!(Sk?&=3(-Q#vb6RpGUdwaN6172!2yQ@Q!M*m3{Ch}SYMPi>hTof>1r>+eoD zi-s`77|l+N?k(^2{zh1skLkHxdxutE~t0!=-#oJo5dz)cTL~*edU`?Q`gOm-o17EC``1J#QV|vv)Fpd+Qk7ac1k-+#JoCjYjv}F+Mgm zccVCLbjRrSiJkDLsU5}zw@vR|_-bf+Zf<%{w10NY;j-^$y}`r#0bz10UqTc*dS=E{fR{WA!Kkg{oZdduj{=$`WVrm@*w8+Yui(u=97={e)Mv#MVB z6tCSn{)1pqC{tAq{I=<9$LCyT?H#4wxmHp%zGrmj*vQ^-FfZA3)9BtC#|}WF5?VV{ zsQ2`)>A7fj@7VV7JIA+|RYe;|XXbVlQ`>h zLxb6R_w3x*o=Cag)_Qb$t#GmaO0l$CI|q*#7d&hdULx6h`FiD9%uU22? zQO+ae(a*!myLtJ{%KPW!5f4{hs*axE`es${49s|JV`2il&5?TaDVw_YJ?GHJHZ*;Pm;c6 znG7oHxlrd#fe=>FJpr<5Q@PP8AY4CReT}|N6^rxfun_ zM^Yl!3f0Fl--B)tGkUDgg9;EA&{F?l_;>NM5opZZdROd46G1Avt zXD%F(Ugw{azGayVYTFB>>*qP;+q`r}<-4fiI8Z*}e+|#}v)b)}E7j5bTo|6;%yWwk ziz!7jy%_(sf5k9qJbmnf$aOd>Y-1VmKAfcw_BpxCs zkOhUWze2Qd-PrV=vALPMtv(xXPXnjMo5tQ}xPOk{^)~j(x;?EAxRthL$ z3fuBGqhg&S(|hPF&sgY1M7rNtddah`Bzs?S(lTH9Y}v~PwDvvG9nv1qEQY2JIt3I+ zVzL1+ciShYXUAseOwfBn!VAey6alO`h^l36&Q0IEf98%^Qxu{%i5i42jk~X(1D>ll z-j~t2-j&ZQZF4J8_`i@e^`FxExZlR_A<9{A{3f88bd^0q8g|T%sCf3@XXh!mdP=$V zs}z#%|Bmdh(=tuz`Xvqh(mCfQbrDLkU{a|+M>*mM`sPu7pW*jne%HTI?HX#so9BO$ zG?l45s`r7mdp+yD($SjD+A8zvDs5Nwl=X@8+_%Qd+OmFwDr`gSq8G+{9O(cL_IC2E zMw0Gt<9?1`VReM7e&)kBBJWOO4@if|QA{QwhfZSeAjJ+sbNp82 z3jkp0qHe(zBwLj%I4>eiKZ1|ZX)>|OzbtYA)WbuS=!1Xj{+T<)+4uo~NBF=H$m9E73rhxu-ITo* zGP}=6iZfP7@LjZ!9QyPg`tc0?dg<-A-*xHCuJNg9l;_sX?9BE{@1VRV57AH#OVp5 zUX;lQpFARJ(cGGlUsnwj5wjQs{9}hWh~Cfht9Qmc#l!g#^O{==$6KHMd*yi~Z8eXm9ahJv=3c4&m#x>L->bX}x!2Di*Rt+;?$=+5;yQfGO3=|97-I!Sqh z(o+r;EBAf8*N;Yl+M>K_&$GN&$28XQopq^=qt=~10LRnwpD&_didl-7R z%S-G^aYR`|V?Q`PHsexd$M_z(UE!Riwqy!tr@FyFAC+rFZ`)Og@!4CA%^;o8Oy_2% zCvU@tGIr28#-7oMu_|2;H&0Jh&*M|#)U#u*^+dQQQEo;mVDL=id)N|$dKO^5y7wFS@;&=m-iC$x);qoOTO}|{*hx$@AQp+9wVO| z|9=ai4vo+48QqINdk4OSU9O(rfzx#hY<2t~9+Fu+Ew|i>cM8qXZa2x55)`QLwF(8! zk5R5Uqg}hI64i!JlcpclO*>8fDl;hk8QyE$DE%pZ-yVH`^p5Bz^wpb24@T{3QrYi& zNZUhNP>S*Rj7A6WOyFKaVct1CrCx5=GsmaorqaB_)EtVi^lf@`3<09I?b@gDr2SK( zGE{8bA&}7xTW`4;my11>{oJO!sSQ8>ZPr9n@v6RDkmm{VsGU*+ALCyAsbBr*S$(wB zue4A7D4+5_9ln*ySAN%iUzUO6)4mctl;4OZbgw*mukXbS#hmqCu$8}}9lg_4`P#1% z6lHHL(F^6k?C69Rnq7@o8N%JyUZa-LHuy(S;!O@It>?9lGo?LGKEY;rMgOFgZPit5 zQ&_TbLYl5>qrOuc^|N-s>Hp5epCj6>@vt*IZwu+Wcor#`x}Y1gSuu3RW+v}OzL!jK z$9_gXuD3Buub#@}QlBVGuvwt((1rQvtcH-)Qpy^uhc-je_-RYA7KU0+l{jv_+PZfa zT3g8T*8QV9I$p^1#BEik>+#mH10~pPr39VU-)zI)!)fX&nRMORXx1~#Mq}o=9iJ_= z!J9^>#_yb-lmb~6d*|f%-k?8}Esfj~F$Rxrckpg?3;q7jxyv%${~h;z^owgAB`PEL z6@;?ezkLIFC7<-|MciM{uYMljI!?dt=68bMNq*OFKpt)#mo-vIE8EoW@d=Y12l+0T z7!}x}%~PRV(G+9eOpGc6^(oI8(qir{Pb-U2i_*Uo$`IYPmth$~8MUUDp*)h$&vDgw z)zx@?cqiHGNGWk-bY!yqddk?cM518WL7INvsXUvP$V8g*oeXd&pXxryRs6)=O{z0= zhF5F#KBh94$)Ga-oOJy>seHk4wXh`Rt9`rUK=}*@T&-?%7?oIWwf9Q$x3yQp*b-IN zHq!KSqw=)1x5`AC@*NCyD&HftcNbUHxn*-pXH~wBg?!>A>KFY=r%~VNUOY?Jb}d@Z zJr~hmCJn>Tq+aP^&r_b#FD6}OSZy{~J^e!7D}7r?R~{cmZ9~`^$b>F=G!~S|2s9LbQL{JnIRODD2*yJW81PnyM?m~>C*m6`v6h+0tZcN0$Ry+m<<}vPn%{1LPxr302%bi(Ox+K4o;2j?G91DVo}lR<;CE62P8lP6=rxnjDyk4=?IeepF?* zVNYgKG-MD?)ki?X5#hu$#>g2K%~*AwU=v7O!oY10@?&O7GT+jdvv4OzTD&qR*KqJ7 zX*n&BlMyUR$m|5fmt)1Y07S#qWMt@%9VX(ALO)d(ibH<_9{@uTby>NGJOJzpvcOP) z;Uh-q^-Q*_pFGqDr#VUnA7`1Ou@GNWWsOw*WJj_iEJ;ewWFuCR(y}LnSA?~@(vH}v z5|0=kKOvW7WI>UE_Uc^pUjhqHRo ztTybxf2u`da>q$1UWq@(F|<4JV2ckq{b(1?S=ACjHU{DjA?QLs*MTrdagB+z2;zUt zOhcb5$qS+}K|+gA!GjX-hrJxh5UMCptyGVk(7lYXq7qjHtzjhuuVE($GfFfpe~Lvz z1d>R(g=Rb|JGF>ZmKuRl0TXjgC=d}<1*mQ#1gCDL5tvv^LSQ1Q3QXNb2u$5dBQVxZ zz;OfPo1F@>e-Fs9u(1&>mY=JzL5cbw+6u`osSa(+X$VTIfpYP*R6Hu~4RJP!qa2JN zg~VAF?HefR7akcAh_w*01*wBLvThs|>5tQRGzSTF62VCQTdqPWIjjryB+i&SSc4=i z)f1}&m5gusOcswSw)78BUjhQ|iVWquMYCR7LpoIn*ORg+z%-lpJUtnSDY zH>u-9jVvNCWCOu^Kx9#)SY!=QkF5K1XG$Ozd`Hluj!8j1xb{NYdT8-<1#g(Pz4L19?LaUzfCcnsIP;v69s z%;X7a>|j;N8#6b1M1)84dRC`n70=0-kx@yxd>*H!4v1wba~+8AvGAwySE7^;Vn~uga0!y1Tt{P*b5S7d zs$32## z&yad@1slE)4V6WPFyT!cyF~ZK$ph)mz1UdJC0VcrA$clg!nk+AHd+)q9E4-$NLFlE zDhPszuHmsi+40pb7p|p^wE`&>1$rs!fr4}?iN*Mas8jO-7qTtp0hWGH-8+y(+a}HPmp;k(?Doz^Sr%ye8QMLWhvM?+8Aw2Y-?C+ zmrF=}pdIL_4@jU}1EvF90h}GcnjiDaOdz9v76o_m8wj0LB6MP3kpE18z%F23h;BXX zSpQX{qxHM!ht}=nmt@!-BW1qfvgh;UbClbzu`pFtIg#z-qeDR1=AC7uzgoogiE_`9k7K2egC0vOV;fOX&QsGyvinr(19noh0g^=2@7wAa26Mf%*_CWO0K}YSL=;HwF6rUx9 z$D2#xs9BPqeAtmXMQ&VdK=8r)+zZ%KnB;yNVAYA}SQllBT?(Tb?1+Zg%1Rp0dcmIh zn82tJE0wC5^=C)8lcFPcqWcOzBF~EpEr}RUe;nwAV}GRs#XnyNXF9nHvW_AFxo5(j z(ns#YSd0?MAS}S_G1bW8C0IL?n7O+mCcq`kQ5G!|Z3g;DT{BZ1oCyI63u>9_?plB#Iw}UiDY$pY4Dq#=Xkfc?5+!1DA9P|BLKE17Fo}7D zQQ*;&GviyrK;sC>y^^c2M}e`IQosuct3l{&?*g4zcj_nFSZiYu>_XD$xLfCkIYP*q zxLM*!XKrkpOeWf8Df?}d=z|}rb9x~Vw%1i=p6`4q0Co{Ypke$74PTgj*wRJA^5?C# zhBHZ(a|>D=3qkWCkXs|);ML&1h@Vj3gQDb-RDdFDm&!r(I3yD6D{-C}gN*?l$4AM= z$jOs>`=N^vSiZ+I#(`=q#)OW0B78ZHnTMJeR#*}Is1{+d{Hx(~<3SDOdqd1nOiI_U zM?5YWMn(7XL!t$su|{#_L0}$4Nqm@WMHIiChzl5W1(2k9L)q$)=9{wI zqbPQnFiLeTB?=PO%Km~}l2n;WCP9^1KQ5R+93?+LxgV81F?!gok6vn)(kMO?{jU$u zLjSMXT_W)ZOD_Om%f{@1j(t^KTDUtQG!->_ntMKVLm(Je9zi!lIHX9Y@B%rTP?88d z2$aS0$<$OaV;+mU6j1u`p|;Ze4h zi1W6Sju^*J-c^CagTSn&+7bp`TgI4Hv!HU^gD`16*Z`f>DF>!WtfW{QAU_iT34n=! zM8HpFM0H1byoL-nqT4EjN$nnUWH~LK(_Iz9BRaYh@B(-nc8vW~3i!~8 z@F`z-zzF&JXF^Ix3(%3WCi=er?1AWqfsXP=^z3*OdIZwsD&WXV*{WvwO%wc49n&4* zeCV_uA-xUrAzlX^sXhy{yj+D}_kSRwbbSI1snKNP%EGu zlE8u^a!9*5$|=Q5B}#T==5-;oR?US9tKA%gCDd-YL{wY1K||^)(F(O&E-`3`-T=Lu z(ns`Et4)BX0zL`2a5^QXMi3TKp?p0}yiMgSz-l!FVTd=2hQXW&p!i&&lbQ{k%7Nk` zvlu6pgJ`C*Z^O>MgdC_Z#SjMd-gEHl{trYH=W8L(VxsN)Yht3cbmHYfv{WAUz)5)0 z7&_sUW-^OHm1Jgh$L=W!=~P=EQW~Q{qd9V_5g;EF?_$u9noYFAjm4is9 zl&0Izt$Tx`AmDtb#FwRP)5MLeUW`FU^+ItpX=+IUSn{djji2syt zwKXs0TY+#j^Q}gJcqt9DK|?B;Xf^Y#K}@tcprL#dEvpOYmd@aE3OLU<^Tv0O+{p5+ z13EF^pFmH_40cqXqVzNoo!XYI}r^qPH9lZetA2pUqOh*q;NY7&En=of>I z@<;TPzg^IY+e13bFVdO}v&exWd#Z!B%6SBv!C!4C;En3!HT<~$haMHq7{0UYInuT#cN#TRfI3z)0$uQ>#25>e7Wfriv;qSYJ%HHeAU zx-U;U(NfYKfYUHQI^onk$V@t=znWk!i}NQ@gaXpodh(_?8~%hxR>~!!v}A&Y)Mla; z9$6`mm}qZ)|QcI9;RPzF7+L3tpZfSL$h;w3XJc$hn;DAH+4 zAf1L?(rFkXow_UO)YM6*Dkq&5exy@5l1^c(>z|NDDpxC{N7O|-bvl%*^xiTpqboXM z2vpL<3MQR=Q;8_|UTWgYPB97v6HZVG9+MR=vx%4Z`Ht3Kq>B{Z4q%@6Qd=`@UUU8X zp*G5QW+kkEJ1IY+6PD~{!=aj340ozAq9dUEh&q`WUwm{7m<|xQ!r7n1AF|QR0G@U< z4s-E-3PZ)q)8v6!_%I1+h~_||j7yiZ{&3VJt+aqQ?SjT(x>5@LjrkeWm(E06f8jnLL1S5IXo zi7e5cmhIBRtF(;rjMS}&gjV6SUY(A{FS=1sgp;EAlCpS`vPfJQ{y0kzW;W8whz~NsKBYv_-zxbb%m% z>fti6ZM&#MhE&6lcseKdhjSb?T}d0Z8WppoBo%{Uxkq_!K>n$&+SrSvXzRiYX==G= zC(Tzui*?DrEhbp|`wCjB2XRfn2Hk=NF&v`Gw4ZCU14JS*Nl;$@$f_O zlOKf;AGHA{(!%-RF2sO>Xat?tK{iV%x+Ad8iJ{Jo8iXEfw374#lPU-|E~p*gM3(NU zXpk}A!sTZT2Re3D;NY11DaWS2w5P|2R`2uO)^ zG>y1f8+()s5DY?m++dCvID+tQ6+gztBnKgJTC%g(s>Cw_&Xg_B z#g}9eB_z`z_+jVI>_&fCyPpE#q!FT2qWZ&y)N)DGg-=kBK!6O1$PeLm;`5Cpb%Nyr z22UgpVr391i5^T(I|kuqYrK$Srr zI3A|A&X_0}DiMc?@~{{lEiFp~v+%qTYEzvc3VDGLAXf6iWfZdL6~|?>sSznNO9Vj@ zJgi1G5DTK0kA&;S{4F*bH26cBtaou95G!XCJ_e9hD1Vw@1x68bVUrr{Nm2cXEkyK2 z?tZYRfrs2Zhho6lsb#dOHq|UJB@#18>og!dDp@V8$Cjd{cUy&WS0gUZIzqD@OSl-#uUa?Xo_vCo zh#;WzOG4^PR>P^+U_OS9`;`ZxcLyEyJx_O5EiJ)}5v~{q z%{~+-?am$Ghs=VHfsp%^OZvzH0_1*lBP|M&;Mi%%A3qqB6;DJ1;+3MpT*F6gh6H+7w9C>Q0bXRI}mv z;?WE5Xb7)r2vRFlEL`Z4-T31YUKrVUv&1+=%SL+r)iFEQhnlGqq|ryMff zH#`jc+h_=}`bBw$dkUUNP?p(amrbog7Wpdzr4P?=M#8eGdgg@UVDifS!IWe7zHnCp z+2O*mLh-ZaCW_1A85GFjDy3)H^9`yBV>1==#)^zLZPnctrHdM$*8^8;gljhaQLWM8 zo`JaM5*$Sjtn`ydVsJz4@c|14Wo+3;GXXZ`WUeYDE(VRo49TBiI1vg<=?RN?(ZA4^)XPk*Xw^zm7&axlu$ujf zG4mVXJKzW4C-xzK@4}yLU@+g%)_*bl{cpc;h6O8s(EY!VgH}&E<(bkx3NtZ(Mwc3; z=Fm;e}1r4eBL@ONimPbsq$3R2t4Wiv80PQQtEbcGpSsF1s+FOtt z=>+!#KV;@2{S*_8DDpVggW!?U-P)hlA4;KM4Fb0AfjcQ@q7yC$lnsgK;^0o{B|2K; z1VX2E4`x-#e8DU|d|u0_dRbQim)?WpWo`09ej~`Q0ASwu`@@dbF=ALE9oZ2*=>+`P zno=nC9pzZd@@o}9^~BDL%O#?`ID&>$7oru8t;;0_4bew{j`B|Q#EO~^ozllyVC0jA zNM7nx=36P;avUkFQ*a}7m}mqRm}Uq(&KGc}IEjwxSSOIjNqa)kOeK3e00$iTT1HhV zK|ol-$V?!;2bT`zxsV+(&7fGZmi5Ver(M!AnyO8uUY@gB5Okoc5e(6btyrM*V-E+a z(W$!=y?Q*V2CFv7z%P|XYqBRJpgxj&y;2IavskLZzLZTZ6cPe3`O9Mm$D6O{a ziDrL|Cz|I6(ItMNak~nHPxHKP*>$J!_xrz;&bjlXl@xx-OYTgG`vmnpb3(yFM z#pM!FlFfpVUMK_TXt<1vP#!VSb^#4#k7%g@c|oW2NfWOL`Dh{7kSpZ_xHJupCs;m2 zSMo!^PXH)3VPaFwMXcph)$Bru!ui6l36UmB-)+#6x=Mb8^M!x%#PT1)^F)3|!Q74j z@v;HE4WElBOiGJ;2;Stqu4S}ai}EHBD48#~5-!#f`T&hS>?uNej_?%R_?;(OcJNnp zn2CZ$%D2Ovm`jwR^)Qplg-+#2Ks8P}N!O8?hD-{#3mQ4;)TK#pff^^BYKU~o71QAd zx-|2KmeCkWJXwy%_ovbcC5y1au~gfkaRu z)gq>SlQgqX%jmtX=pr@(FO^hM!PKfT#BZ&G@ZDKJylbHj*9NBkP`B*?5CP! ztcYud@=7NM=`K}W)I^k(O&dMOAJ`7{)(9Jm?##9Dm?;Y7h3?V z!oe)VO9+a1JpUkG3S@6VA_#uxo^AmA!$Xp)VAU3G#6DC*=?aQU3PL!9Qdx)>Qn-x- zPuI)o9h_giRY?c#>_tl4djwyLQs*9oPcy{^Md8*bJ*^4e7FL)zS0$DrE|=j04P$VL zTkUZer475AA3^_7tQPQrA|~{x$`%!#`l`hsoCcC#6H)tV0UA=)L@P-Alouk=dVz)p zL!za52QNjifrEONvtTBB>Jffu{CBZb(PDj9@E~>o(WAPX7$Dj4FQc6? z1xoToDJNW8^U%>+4TPZ4;(^@ zRBeVOX%I#bFarkv2SbDqfG`fI4+nJ6t{M#ZxvrXp^64E;GcX1RRbpVj93%!|bR~N6 zf!o2t;+(aXvZc#fU~we1epQnyOGGW&IEq!5L$weS(7c-CI_~o_O9Q@tKyuT1%(jdi8D~37Lgewd3dhg?2C?%~tLShmOzC)N2a}FfV@czdl8WHW;qcPW ziro{mB2I6V6^@Cle8O?dUl!KFDlCRxO=@99(s5w{Ev#^|X(h_!(A_qKftOt5f>&Qz zn3a!Qb$Pn8RWM%hu)@m*E%96d6CHvWGLET)2il2su6pK7d`zp{M}V;;QC(7^a~CxX zTt(sbQcrOSqjJR&6u;q$X&$mmbjaiw#7?qgk@3RiKC%Gwh+Z3J##p)xDP~jHtZF2c zPYd%pBd^A7X=YzJb`fMMrvvD%|dY}BKulBJX;2MjSGTL$z=%lDUM+@dManbki)IJf& zzkMgr*y1yTYH_)gOH1+OloJoxFIKaERDS$Xn(;@e=e2D7<=JNVB+!*hu4W7wU z`@B~v?8P~^%<#+RT;uV4jG}pw#zSl1q;GAasna}4c67=sf;vwA_}Z%6If9Li(V!6) zVC53AH2`Qx%_mxcJ6bL=Xo&t8=xB^3dT~srd4hzg)lQhPFp*xLFFo&{8NM{kfY_nE zE05pVQN6|)yo@bFlqd20{GA`hNy;9;vt+RQw=VQJhEggnP7*H}O9#62iBx`euOj7J zEWwpqvo#p;Ze-vg2}gY*%2~@n`H>`_P&5}M;>!!aSeE{~N4^>!fH+BM0n}G1R%-UMC*v$>inh^4W$q}Y zo=HEaR{blM|98p#cb_yDYa>olWmB84mf9{!VDL}XHpr4)2JLTYwc;sn2G6fI}oCp+k#dlrpsNZo4UfD6on;-=_0{!i&JPjc% z4GR8(H^g-UNP?2@?nQ*j{{}_%$8$swVgcK|H?FDh@ty4GU>%pbJS&Zd(*(mPiGEnS z6yCr06(z+O=a~3ROd5`hyb0?GPW%|63Q3lAn!%+{IUR)MwVMSh_V6?AX* z#xav*7kOP`BV=nPH9;&cku@X;-@Ol0$^Qxbya-{%qWoxq@O2NC5y6ZPIf_kME1Ez~H8{u$a(to#;qUBo_A;lq{hfsr_A=!K#CuTcC~p z^sND+DFqF+13KGy1Tz8E4UI_#Gr5_@S9N<47j>eS-dn6~w20j{AyTS#5kxXtMT=%~ zLz37rnOGi_SW41tL`zAc?;Ox~6bL8^YI13CBfpe9(M)R?(Jb1NPIP2XW;#kCv%@T| z3pb?2sCiZJThp3$lqjUB4*XG5r}P=XOsXApN*f&+QTj-UIK?g65giTd2ZSM2RrjnN z@j;atshRRNgiU2daZug}s46MX=U`@4lbEV)G?^_m$eFCj znUYE*r0BCcl*AKA;=r=s#1faSY7P{2r`gOa^NTdHMXMSKOWrWhQ1U2z*V#D3Tc&Na z)Bq+{m^7wPK@nhxAdxZQdPgq^l7gcORyo^(O2qe@*;n#}BOtK{D?iQ?$*+d+hj&Tf z5g$?o7oBd-3MO0;4~oXys1ysG4U4$=O&LXaSb!!0w}_OB6s(%CS3&6PE3(SY&h{rv zQW@grYCWGlf+!3C;tsuB{<`pqA0czB^ft`lB}~T@C}DPwR^9y=+^KZQeHYAPqo=P+ zTm??$N{Z}7ob1@5UvMW}bSIrH_Jy$6hml~WqY&~hx{*%tsp}LENs>@2`VMywl0R_~ zzKq63?_PAq!6fLJV5@M28Kbi&_MW&GO>Y;5NA?Vt`$N(`2&)bH(3+25&=?YF79Iy> zp|Y|$I+y32;v}gT8d&mS=YaeWPJ;#MVkacl2c@m4A*GE!t}lML4ZTiSTL@p!WVlm9g$Rlz$8;P0`VI)YV(IEhyg(bx&h2?o#6{T(IP@p91Q2*+6m_)P??6J z{6r4|;h(lc4B~BE80ly<2y)Z-xH8R@LcWyEkKW8=M`ccEtx8*>{MnXh%lfBA$DU3S z3k>B)J-_38UiMU`q^PXv17%d!G{=+f3L3HOsa+9HZH<6vM0aXKqF-s|8f~MV)H>Cv zMGDb3#U}a|V^^mqcWL5UZKJkgIz*qe{t|tX9kpx1#SqC(dKf-W7*M3@fl@^DO+9Kt zEmchj7fbN@K7pks7N7V@KPsxw1>}?aMQO69$jYNz3_9w{;&=P#{tU&{9dQYAQJ!=S zgk3x5SD+-0!mY+0xbY-WzoaB_6Qu2X(YH?Bdkh>&Cxwim!Z7rrD4`3GqY71BvnxZV zau5b+@`tY)V1Ovf0Qng!nWpGf^y9u~p&%$BX@suWIa6QG# zc#KMkX~rTHf(!1uPzVC5Nz+TXu*N8aj}W-|;{9xPq)6WQhf93rwEfYHjTbw=0x)Py z?2t%7m~EgOSN!lD50bnR>kiyQ1%DhR7G#4Y^go#W50S)-iDi|?)~!T`>p-|>Pgevr zZ%_>xD0T0JHbHl&PAp}RaO@tjOlAEJ8HA2;+h`Q-&$MzrFR{z5jEg6bh{<8)+HMIi zS1Lo2XCxp_JPlwTQCllM@&TYMJQJUOmiveqDLCHr1|jKP~C=X z)p2oDlO}G}hG10d6dni-wlsta8$_j~g*vy5PTolR!wh~%C8zS1u^KHyA@|3PS$4D7 zCp?vPMgGIFo~RF6bWYRVDr?74I$lP9Ixavd)JN#J-lpwRUms1dB2u z`;jOtE;r&M6v9A@&&;8J2zSvb3Fd9bfm^ zwR_VGFE4HGy4&!;_#FxQHaSlh)HyWY&umYfw^o_K&ZGCfE0P{eo^bVM?dOl~gbv8Q zT6%KIm88L`imHbC?DOr#Y57dIVOrG+6?(qDT`PTgZ(=a#lvP;g5oSTIkEEln zMUDeWhY`N>z zzrAEx{Voj1?i4M_UH72%{;Sdrcl#79xzRQB*roM!pL z8D*p6__$7mwj*6D{&{-5$(=5vb>07zYBx4BGdeic%J}qe*?~iLP6@X%yz0Mq^&{B@ z)6YdCQco8)ZIGIuxodO5$r@dE4xMDOt7Ye52li}J>^p3gl=E`;&O^(MAMCEM{PK?I zj=it_wLSLoALfm2=_FOZ9aY81_hE8pzs22xLrpe&2ah$nX!_`nABKw>XGkjUSfxL~ zaZ#n$m)99g3w*0p_j{gR(C{HXAI}EJ6Gz)AYCY8p2^(W^_4~c%S7vyRy=iu?&z*qq zyL&%>E;%^2=fOQSuFcIHRlM%{^@K&2)*Q>bVL$NgUAH#@`!59y$=!HIFS~nRyDaCM z%}s8&T2$Qrp-%<7QDb$aM@vTC9dtKfbIIH=+xUY)PP2E)&el%y&rQ1={+r!{z=2C* zjn7r+ZM1)Hb?pYN8tD!?*m>XaCPoLc7JBb$t`od-*rp!^dA1kxvuCX;Y*8np==aP# zzZH$%`o37XE;y#*qEPL0J;RmV?M$Cf3DA$MIz*y7xzC{`)yC#L$Y{P}oV~?v;}vr+ zFElxL?eU(H+vDrsy=8Ru@|CGQ9$bC%Y3H55u%w&y&R0J;>Gwu^KirSa43F)-r{)uH zgXs~$m42M*tmi+>NUQRXRq}a389tw`{0Nctx~Qlg_n>0htIH+@o=H0W^LAF~C~Fk3 z>0|X#`Tcqax!j5k>((duQpRoX>xKSCcO71LzR|%WBRBo{s{KV1FJ|?A_9MHs<=X=X zX6EfX(|XZPkB#egS?k*6?^~o-aK7n~BF{qsg$-Md_1~G(M|MoxB5?TP=Hbmc95mXs zX0Gv3i@Unsb4s*rXyB3BTGX2CW7p=WhfQ;x;|jlCrLhyHZt@;{GDYfUFthNK%l^De zGmA^UoGmPUXzZ4~wO>L;_FS8!RTnK&rwyo**T<}3p~sbo(zu1v64{`FjF#1&WOpdY zPPI%)PSPEkYB9NHx@5(zFE*LW&)TIAA0GR}Qla>9x4ZP}DpT+CYps))JTzZD+sY|x zlPos<;KuvKZ=UAAE4EH5Ix5dPe)G?a;lrP=c5-ZTE+jnYTf~@*5ZjI=-p#C980s5^ z+FMq6XXkV`OMm!@(Vh`6TgyXAKfY_We|Mhk+L#5FGuox;&;L7jb&Y*1-)kZd99K4ZKzij)9SSvnpe6RVQ;-c+OX@U zkZ``grgZf0 z`@I8Gi(`8h7Am~!y4jeIOt3RvX=Bm&j-{kq6-nw|58I@)`92x5E_q}xtbV-o&XCfQ zV~aQCm0U|H{O~MSKV$P8%RM1Qwkw;JG@J4=KIF_+=ZGue7Q;WaZ02qcp-o91L3fAo2E(YH?L-d!K}t@vVYNP6No@2q9r z3|D7Ov`^lWX>Hl!jk$jNHcrjzM#b9d@46r1{60TqfL)T)*x0P$zWocnoUQca{^fJo z=RVI#F1XjHO3HSlhI4kGib(rohID_j)9{8B^cD8Ck99YgakH5R?&Y3+yJk-QbDyGbhZ~kWDLimLx7yeIO(xxwW;RO5 zTF`y3b$-Mr^Rq#nou2d<8~g3|>=60wQtyQ09Sl8Qjj?zBxFf?b=k@9;YucXcJ~Q&0 z^&;y39fI?;!mcJ)7<$l2`}Sr-{pWdS^|Hp#(V0@+O*dyiRnvK$FIBoeWvR*il0n9P zpKBX>EV^M((PmY}uDypDMXYON+57aPI(1{$)@xd4RNbvctt?i^Ue?aLyQSv)?4Vkw z-nOsuBlTm|jg59xUf>#KR?WMMdBfJWrG#uABtwDojPpr(}S=Vo}E3R3+qJ7PsMtyBMx?;B` zrIw>@j*i*cV)?1}Ew`=h(CYA<t%!>5Tn$=ES(nmbr>)jtQYfU+lx5&Hoan zn3^5(`-|9M?SZy|?d{(M8BE;n)7?DO*MCcM*?=R@MqMq~D1Ugs&u?Fa2L5w`?gdz# zEpV=}#@wZUJ&(TeF$?;9^(}HcYiQYj;T~_-4R=!er7l0_wEeYS?+^VQdL6Nz?C99$ zyfn(Il0&m6EE3kd=;l`RGBb;w#f%KAUN`<@teOL4Uo@ zsrv2Y5*;_@`greWT>+q%50jI_rJ%+g?b!6ddv7%JACOm(9M9INo^i%3}%5Zk}*)sCn|- z;30W`cUf|D?c^&*mK0Y$ym@=Sv#XEKLrtAN^`^4X>1ch&b3H?+p09o7@I{-w+7}#B zwl9eN8alsakLC+&HF>sZWw(tfTRi=izWuAglH-Z@7JoXKyZYj`gjFe3?N_de|MG`* zcy?-4-PmP)_S>$QaOvIh*6nA^(I44&_K5o?bAx7GocH37#F@7~_Lw!5fNoL)|OE+&%@;tXF*>~fBDV=K8m}U}rZE9NngY>2QtTtcJ@&EI4*!nHw z_iQ-U^i$fqo}JeG`EC5VKb9U` zn;&&7`)Mt`T?aQg?4EIc@*d+`=d(KRsI=3klFN=k@>!XI4GQ*;bT;3o_rW8_cIAS- zyJi;UCf&0+0gugb4Et#R?i+CAn)t=(#MBJ*_bu?F3? zw@Lf`)ZCic0i7%6OQvN7*}Jzg9{FPFgV#f+Ok4W6>!oQ^<-6{6y5sFO?_&2RZDXgJ zJ^Jfmtk*!38!sIK4-NRV=2P?6$5t)2lTxMoK1 z#WB??Z9cR0zW&66$)k?yU5-c_v8RURlc723rCsityH0tUcj;bYLXc^zv*SXXV~?(_ zWODXo;Et%KCv{}5X*q@OQ^OtS|G4rs!QKCrMTeV%`!(Kc+QdP3_lc6s_3Ne^Th`9J zHECi&K*t4fmz$T?3tLp-W24Ug^Oq-0@7`*`ORq&uJx4k2veo}&e0Oudy8AA^+4$n_ z!tXP3S~>;PSmCK1(fGxF=hvf)TDhKXo%s3Ipr^fjSEh9j`Ft|zN{v9fBaxM6#-Hsl z{85Jc{b3G2Jp2EaTKpku`mBuVB^|1Na+s|1N8@g}*7cs%m&OfRw!PrqtTUOz+IHSO z^yP?4j|XKYxc9zR>%|X6{mwpyPSb8aeb(mGihgGWDx&tEMhB`t*Ol{oR-bZBi<)ni+Snd1{@)rB;1U zm&Ucsa6Wuk>)YiG{?cH}5h)+zJ2rD$nzDNM+cym#>(&bCH!7jv{oG4FPoM8D8aZ&T zVP1!4*<%ZxB!Rj0bMybQy*qv0VBglV&(|C1tO{O!VB-P(8%MMb*@f-e=3!`HzPx{A zogYmvY^XeS+@9$BCo7#VG+Vg&;MQhNm225I9J3)Y#{a|(Gp(_szGqyFTyZ+;OoKP; zhxAUVS(4sy+qZKgD)-WR)o+aNj5inNG;+LMEdAs+AimY-c6Ap?Z|-R1ap?N;=g&@j z-Z^_sFC!n**R}M6T-O{Nu%gQ?yT-}wmn}?j|K76Yvb~;-z7`d)sxo@`?AYq}+g)gB zm%hbiX60T_x>vB8l(c`$f_o3Eei-)qx1rBmQo9X$Q(teg!co@k>Yo>8ow%6ZA;Dfc z@%r#aN5gi`7&HF*t24*Kuh$t`amSkbkLp%?Hz|H|--4jCRkKWEw+vg8x3}Yo1g*sH z=_?;EGq*OfOS20bU^#bFb^ob(qi=R=Q54?7W$%^6uP5mkE_sr(W1Nf0y1FfQSU7jP z+|y=g*xyG#+6z!oUNDYcEZghy%ffvKGq>2kg@iJyt6s zxOCn#-wo|o`7K#zdTV-{_}e+_?_GOVcj@KA>4v*qng0vqPI3iif@K4vV>c-Q?nyOI?RHDr~)VK#`==se%EChw^3B>+dSf9=!8d z!;AZtEXz5tp;8m=oK+)rryHCwF3j3t^vt?d`1omFfv%O0$m(p_?Qhd{%x&p>ty`n@ zQZHNOmRvJyHhRaD8G5_3>#aWYXhU(%)wXf^r`J}Jd~TOwxOwh<)6|+1gH84ugm$%A z;HOyn#CMohNXURvONGp5ioErWw?2~o3cV#SEwna_n`}^eH@9uZ(~9_f=PUzbu(or|bb2*5yi~ukLUCpwHb6(ylj_6s*5= zEcD9t(!YBKJqvCZR`_*Wz;vgZqjK^cb!?*AR;cs%&x)=i@0g4~?XEkzOJi;6p9hW1 z%uX9yO&t=L{hL+zl%0G1uNq#EJz71Y=(B0l!qcgl`Kb*~7Hr-%bZ6HZEq9qr+H+u7 z=fnFH+g|1*SuH=bb9aT^4~(OCTweaywcZ{7xE#A(=T;;0sN2<(9{L(pS?t$2*(5Z$ z+t^_5&5ukk8Z9#X@kd2TM&k+kt9HDuw8(Lq!Me+JwcZ8>>E(U@=rd$^qCDViErs3a zun@hc->+JXnQ^80J+qr*y#wy_Irn+*-SD{wOFq}wbFk;A%(>UDUtd?e=F*}B`x|-3 z-0r>|cxivYn~k|c0=j4G-Eq#c>wCkb`OWPWEnMv?^!Xsw89VCksFI_b1MUv84Vzoy z6m&5DtZe7(T>qrnzlC2;8yNV&?woP#lKn=#D>TrqzITvrqgKcFbv~GNz^F;{UET|a z?F`n*EBLV~JO83>i^5g2elNNEL4&e&?>b~Mj0-fdyO@bcUhkFOmx8GpNEkI}8W^`~CBeD%%M2R#Ds?EF;k zW>VOsgVoP}*xTs$@XXlzHTU$6oo?X$^*I;H~AEeR(rIb}njdy=&dZeff5}=L_@}c@_<6+ORO- z&`$rcEsx3i`_j)S)XR=`}NLzT8T;JG=Zz?C{|~ z6bj3$((ZT9dz-FWl5D+p_G8So|*k>CvJj>zl{3Z`h)WrG4mKC%bnihU;g&jPM*?8X|AKzuCKwYi;v( z&#+t&GhaWgU5(Ybf14)H+4oy|QGA!I68ork@jrr#oexTnTTJjOYS!g-yqoSbp9IG| zH=Ajm2sQ zXIAWek2CxB5BFS`rZAsAr~CBfdtE-z<@v3Cru$Hxb#Lcb^r7Ps$LN<*D17OHm!{7Sn+xuxP$XHi%=IRG+H=V2B zEce@}ZPxd<@9LiaYIT()o2iCbiqp>4Tk<{3|N1+@sp$iGY+$SI@gMB0o%hr+w20QJ z(yV=7kKDHdo#*U~mls8hN+_{?`sCgB@89wcUCUoE{@mHLu1PcPzARs$zkf%rXWZsZ z@{XO8Qu{VYO|NK}{pGY-#@Xp3ONP5Sl`2d_3%j3>&NH3;&bvy_zoZSdk0>IJ+=!JX zF10J@J=f-m-Udnb@m!1K%@R`3b+vC!m(&N>Sm6Q%C$=kH}Lt)Cb4E@|^do1T{UTIqtG6f8PGa>OW zuS7U+{WRPn{JvAOmi|R{_a`3LPs8@#x8FN?1MRGLk1f*k*0)Yx5YTjWTBAqDGjhHa zt^R!d-MLN|i@%LaOb^LjmgW5|YqepwEy?y1TUc6Ww%0d*Q@5E@8-3f@DCda#y9R{h zzaQ(AWam3PEB5S{g8rB9KdJN?41jwD$#b@+ROz#OPD7(V(jrbJ@0ZS~&~Ug@ZF_~j zt$FujO-xOv1h}R4NbpRrFev`ZVOO8ClP1|8ub0}ebi_{6O@I7po^t!Kr^Mo`o2_?& zkI%9T@g6r^?z`1+|B^6xT!GCJpL3R1ZRX6Z+Ad}PzZco6dj?nTE^nT)DgM#CT;~nz7g+conAxoH+q1d%p6Ab5bNE}4 zPvMi2hShTKA28XJ|FzM~r0(4pWF_pY}#wr<@~ zb(&hVGTK`ErEEpbEqC*31!cdl(*EtKsvlE-RNm2Oqgj;e0`o53)vA4O-LQJj+_5#r zKI&)R&*@&rc`XV$pI$h(lhLQW-5pENf8T z`ki^RxLr+}?<=a;^y?_6CfzEIwi#_%+G6LJqb=W`THdO|+HDcb=NumG_4H#@-Juzg zx7#j@UG$sfm;r9hqiej0jJdX=SHg_jaTELQcAH>gP<{MGpZf8M(d*-S)Lk@IC+*C* z6KB3Da$9DGCOA$DxBpTU_GSLFknE|7;Mfzn1K&{d?y}}v{6{@I zGC;nu;HsbBfrtJLD(nlm7c|GY;H;I4`I;JiJ?i!Evmhqkt;qLle@nx&uHJhV_Dj99 z!RgrY)ZTipxA$`B|G{yx^%3d$HjWOJyrO!zJZaW**6cc-Zvx#1wbc$75|i6)@Sf|2 z9-j`+9@y#6{qEzdJs5B>;hOh@j>W%QdE6c9|8lC=`e{3cKVQ&pSku2Nj0o@LJ#t&C z(EIHz67PK+z30J>i?1F=Z76)wW#_P`-wQ(?=LEEQG}c77q){LHSC6bJy;!?y@XJx9 zvf@?`9zA*;4v(49ioU`Q12e%*R3*&~5y0Ndrkegp;nBMC4 zuw&8a_qlg>uAX)0ef`U~JM?{A5Mq4edb)qf)sh>#ui0cx%J*5Xb1CgY+bd5Zf-hUw z9)5hpce7(F7dJj}vsuE)nhq{`Lk6Eax}?kBN3Kj>d$@A(lC%A`Z$2~c`07)q$DKHB zRQb(0NB!vYQ$u@RJbb101?{~y3$~{?%n$tsg<&^+sdVWo?DhQ`0MTB zdx^(a=brquDq-8jmG)Ir{`eBVCN(?UdReS))fKk;`z(KVX~LWt?OV_8J5qnH$^8-Y zF3t*?nfS+xSv@}9nxW$|dHRWl*-6X&rcd@VJ(O5C^U0*!H!meGI_EiMz((I`HEMR6 zdM(l<{Xu@(W~+Tm|Mb_nuw{MN=ZxoZurU)OIha|L5ce$p17vHLFVY z_E+ebMJ0!TQ6U; z!S1z&tS;Wr`pXxy)zx}z{WSXSxWlIcPHx&36KT-%{DtsFOSVmnu&G?ry!-k+e^0BK zzpD1d;>K&E=Jm)tVYSs@Z12-)ZMJu-Irr4>6*~uHXHApjw{o`+TKZz7@sy#jA9Q`Z zbeeqXv`cq7-P?6>o||`U+a}%rdSo_rpjYfehnF`@J`Fe&_`3P0HO@;{AMI`enWSo85pwJ{fix1QNt>EJ~D`+7%5C67ppxctPj#-7sjoT0Af zce`B5dpad3A@Sb0v#m^z#yW?bHL0{VYDeHnna;_kg*j=ij^U~Azh3z<|CPUc!r+@7 zEKK({?zdalp~?Epk`tE3)7MS9m07!EK*7Z3m*W;J3aeMzxzWc8Nz3Q^FKE?$decQ- zFLybP@-+UWZ(FzD=DQo;T-^74;oTQbEpujguBZ|4qH%=w=-1BsPrJ4%y7f7+wQsMd zgF?Eet-O+S^79e9z#8#0D@8sU-r?-9`|cV2J%2cSC{Fz?W7hPj>K#g|>r8g|l-sTG zAN8NrvtBkRPCDye!S=SpGS9pmy1VnB$CpO*c2CIs@uJo>pU(9aH>Wuno@(>#X~f*Q zlbgI;b#?xL&pReR8a%R-mu;s_^9+;fwGDe>GWCc-cBhS@2ORFN*Zn&6NDY^!t+v1K z-=|H3G4E!ss+^MA{9v5b(!x4%rKkHIcFt&d`J2|^V5$Fxk0~Q8-I{fbAHF(e>EniP z-u4TrrTe}hVboKfOLIpS?S7tTICtRK>}MSUB~FF;U;s?NYx`Gg-@)@5T>mUv9=u9N z9}Iv)T1Red+ZASKZeZw9C$j(Y4HugJ*fVZw{_o)eb%@J>33N%;9zoNyIUy>m$h%%^1FK@ z&%Mi56&HP-J$!VPcK55trrWi=PGh(GFO2oyD_dNotM>-o zpQ*!;cETryB?f>a%&aRh1IEpI5-a?;8In@?<$=ZkI@ycnL`kAxwt@-E3eb>ATT7zD z&DvTpn~^yv43cI+epYyCpGnZ*J(zGS$W&SR$b3V7cBRKql(u+i8v;j)R{R2r6?2o$ z|Dkktv5?$Y+&B)8wThyb(nNA041c;dYz5gQ zi5O_4uN6cpA+O>as0_dd7nwuY12sykC`fwa)f?`@^Mu!kQ5>PNuy`vWli`u7qKaZ4 zfTM57^D?9Y5a@UUL4;`c#?j05eH7gD6QA{dzD*09s2VhKD6)`W0aw1+W612A${=0S>_L z!5v~R23~-gz~@V<>UV}Y4qy*FUs74WGR%R1CcrbHBLIWx0B7K@;a>-4Pe3(^xxqvD zZw_-bpdH*#!M_R2egJFWo1qhZGQbgdG5k|_!vL1RQzg~(Jz&NQ3kF?)-#~t9z)bRy zEr91hHwMfGxB~wK{}o^!2&jVi*CGCfFk{tj&<5_u5Wf-3K7ar8t#Hl%u%3P+j7HtW?!+ z3o|6z4D8^34)H_$$iN@a2>4d$RNhkoPQYKlKaCR*v^A&;d?n)dg*gGx4ft)uZvk@{ zpe68J=#V2dm<#9+{Hsv^1AtdX{*#3I9}V0V?s*8G(klbh2fh}%7GM%U3j6`wDLq30 z5VkU0DAa#E@Q%PQ3-uoayeaT3=#<_W02knI;h*Yr5WrkwW}vOb>puo~d$^ww>R%2V z*Gvrmgihr(1<(ulU+_=$57}dbI>46;^*;f4SKv2=`VR$e1AHHJYEN?j{eXXgf2x0K zGgXoQqeA^h0Y^O;o)GGv%CsTyjnIic3D6Vx6Zoh4{~dsGHcS!fe;n}6z^@DS9|9ch z+Hf~?YEQEOeSv>~f6C7gKn=C}$16w%9pHXJsQ&=qje%!Cr}(A;dINt2|5TnM0qD1e ztAzTW2)sM+BBB1nfwuyF5IVJ|c>p)y-~XlmA3^w3|Dym_pkD)B0!Rcn0KX4+s(&v) zP2dZJ`i}!{5B!o)|AD}p0N(+f>T5c{8TcFcr~3a@|359%zaMCVGWoPQb4T^&bqp8SrfARGu>deSp7*f2#jq_5bri{riKa5$Lx; zr}CZ(a031k{;B>)0O|t&L#Y1*;N5`V5$Zn-cuU|1pqm2b0{R30_AmYaD8i@o$^i92 zzYe+}2>+Cy zU-kcsLj4DTrZMQZL#Ozr0eS;3fqyDbvH!0Y>R;^tcZK>72Td!`AA(*9Fc07c{D;I$ zS4&buS4*p^Ze@vuu9jJ4U0q2nT`e7RU8$swu9kzDZUsqAT`lcuy7eWNx>{CMbPXl7 zb+rtt>rRq5An9hh+VDRK{%eu{nw53+$-kbtu8jPTGSkKLjJjGCs_EL2|JGG>O~}7- zb=@P(e+8@^b+L}r$G~EU)ua(tk(IEsb@{tR<;0v6VpvZB^8YN4Vv z6I5kERS{HGK~cG>D=I^EC6=eCi{&cnWs3JFv`lgTg#JtM*Z#NSH%7mo4R8gV1GE5a z1t0#*S=0Ez(J00#jHfbRe;z$Abaa0E~v zum&Il+y_(!EC37uTmskvb^t~L-T({%GXO4t(}1RcbU+Z`8K4$m8DJ=&5YQ2@7Z4Bl z4A2Ek0rUbK2mA)u0FVP71Iz(S0D}P60PO+UfEd7gfC*p@pda8ozy`1l5DIt+r~~)| zFdT3P&=qh1FahulP!&0-s%0oy1bq>759l7yuRy;7z3u+6>P^L57Oe(p$e6VTuH@xSou`Q-CC=Y8Jiecori&%&+3y$1Ih+zf68_m8-L#9fHH z5ch4kZ^Jzf_c+||;eHSINZccFKacx)+|9U~aes~bYup2H55T`y}qsxJTpOhIz{kPu;K#)G$sHG7y(B+EMW%=pi6Rj30Ep;na5^{#+zyl{=gfh? zXbLkUw+L&uoQ|937;O$*2z2Y1*^?cl(rGq```6|VW2Ce1IdEvwA4h>*bYu(?J-oqw z>OIJE%4R%HwGRAI)Uf-VdpU&?JO!Qx&w_t~UGC!^T(B7E8y949Ah$g-yfJ$pBd^^t z;*Ir{_08oKHSzN0Z8h=gy1L$YeQj+`ysf;tzOt>RrFQvp%u?p}g$HA?Z=omO26uq( zfIGo=!S}#j;QQbQ;BIgaxEI_9?gtNm2f;(&Vekle6#Ni81|A2~+>tgs)Uzx+SUmfr z^d+((I>|dH>TX=c4W%ciFW6ES)mVmT`mSKVo z{-u}PXmc^Z>OYhTMu&!jc)*EL8S~fJgpPe5>ML6ibKYzkIW8o~3d|e_&mAM7Uz68k zJ=!51=nCmzWpK!%;mjcmV(#VSB+;!5D8SBK?@p`nCy2@%&w*{u_lPX%D}zOuFuctR zbIzzf^o1Wn*+;)w-~Llb)n${rF+R*TZh~<6$EiH&ULUi`hfG5dTe_$(#{aRWKeOXL zU;M?qV?X)1#vlIt);D~wva)!V7-6hsXED0J^!uvLxXOd)C(zX&fhWO_LE#;MbrAGt z3~-|j7b>?BR=eWwA-sO8ABx3(LfD^zr@+(TXP}~rpd9az$(U`g?S}R`mo5%(@N^C& zynYwr{+w{X0KWv!fM0~q>Hg$2< zg--dPMEW_Mu=WaTa>7HF;{hC1TLTcJyLo*}Yhpv^`VFm}-R-Si2_8BO*~F&S&aU?L z9ZU?R)05+-+)k$Enjq%ZbdaJ;PP?1^GB+1x5-^qMFvuKcZWTU(uD{Xg=w0$xiQlnu_I{e>a`~erQN0K3{ocfNIkqeNn!8I-VEFL;n4H zbtEMpACF`k{pR_XT>N{ltI~O|14@tlh<*U3iqF$gIA0D#&*xDzK2K|^vZe3xue^{f zMKb`BfzqhBqUAQ@@VLxOLxbrcEnY&6Ig;>G=6X<5 zLv8JDwhlOa%Y5jc4-`^Ja#W?(@WbTSiqNph(+XLlrqO~CX(4`1km!3fJc)WI3;OJpXtGfuFb zs+Uc2`B^GV_TK{T)}1E=Vo2@eO~ruKaBPF)21qBlZ!o^Y&M*dBy0|KkF}xP zPEhDN(>SIKs4S>1@a>Vxh03?ysqDy~%9+Zg%ALNe%qm;}qGe6rHMrJK<;{mx9U%V- zC)%n*mK@czy3iXhywH6uh{zRi5kryZBzc@Mfg*$91uqPxz!B=(08-JT)ByAXC zk?M&j7%@j*i4{)W3Ia#foWA9Q+C#Y97mng`z6hf;xxz47M`puY+9thz>6SSrDok!p zbk%)JgD}48KE7om)VDB3^DP^tzJ+n}EtXE{OrkAVLA95kwPlt4ixx%LvM+L2^L|41oS&HQA|soL!GzCY>JKhXOQ!o0)G-{RG&C@ZT`24? z+w0d{yW?9N+t*KQsWi=(?eKTCc6YaT9MhF(-q_jM+R>d@+jvrIXQI8MrS-(fKq5MV zW{6LNW1F;Dk#1WL{d~&(-0FcK`%>HH&T~uqx~}e%*0vT9?;9Gi_3R6b;Dq+h)<47OdIjz)!Tvz@ zs{?Qk1m1pi5YMI9uPV!nb}r1xWseZQYzSwl-T_=s{_eYWT=?5R?XcnJvDmAK;ng4j z2ZMrXPBNgs#FVUfLuPT8Ll8 zVy{7thk`}mFmO0fZZF1N0(@>C!Sj(|DUh|Y)ZC8aAM09d87K!8pb}JpYET1eK^>?E z%N-hUj{+;eO0YIHknG(ajA7vAMursCdstAy)7ez#_fo^8`pUveC@T)L{Ix75+h2Uu znq+3ZBbA|Y!5nD%qh+x$0$U8T^%Gu3l4-1VT;&+_rG`1Kjc5wJQ@kR!M%@)@jeXPm z4VR)0Z`Wep0?*ERi4779X2Dm}E0uMV@2QdTiR}eGY<1$im_zJr6_)jA`*(?6I36AJbe9-{~|?KXD9akidrq$LB$wr zl|2%gXZz6HR#ny1TG_}ns z1}&f!v;mc^V{q4ic5o~>4y*<1K&fS`1HbFR25>x3+3EySm8~wmb%Tvy6F32!2u=bg zgHyn%;56`BkN~HHBJ;mQ7{I^!I>ZpBJwmN(TSCd zO*c&AQjKK?$0mnajh^CmRWy^{QZgeWJg1AAOU7iN&*n(LbOy`ZojMx}e$Mv>x_@IM zT)jn~?GzIY$+Cyi^Cq^y9#e0-cQLp2+YU$Qq|ix~ALgfwJE!_N-AhZm#nsGxDcv41 zvT9{{SkJ^JdQxYtS{WxzC%1O4Pjs(e+uGUK(cBswc37ZqD^^w3~ z2-V%1NZ9Mzb5wNsv?^*Y8VuFjw7jRGug`s9p3&Ybe#c0HJd5h?1b zxRKtxy0*n5#uAhs8|s_#``V$=$!&8~aPAS#>v^PY(i5p{+VlJOWk$a)4CckoLT6qF ze0yKaJ)7?>N!f?`0vTV-&CAO)>DMzkFtFY1`zQ-Ga1wbYMNP3qHy#Zz2~-wi9#`$4 znhZoAP#D!7Fb1TB#&TfvnSA#8Wqe=~D6A*h7f8bqxEGt|?j-{{MN)Y3 z1x-+0H#CC8Gfd30p-3kedyhTanQykfQrmi2ACSovp55tGoROxnF-_s>c5XXz2@YNv zlpEFHp(4yd%u>WL$e7%5r%5KQ6j0ACFt=Nm#mslNnt&!p7a2(@Fn5qMJ_~vo@R2Nf zeO4E=l0J|PEMZpXx;N8W>Ow?FcVCZ!p;=OSD86a`ZcD@8g|bdr=c%z^7T5_K^HBP+#((qWddCl zZWdU`A{;RXB+RU}8dY^Mk^V-zGfnr1x3Fufp`kgMnOHp87e;QFoa~FQYR}~PwG7J8 zTLLBZYUydtM9uZknQ4Qegi^o|7E^1mA}ANTMYPtMt6`?sTP6Y10~e_l9i-ydb6$8#lJMSk7jW zPEp2ec&f4nU+B()X5n#Bpw)T>l?%Ntzip`5_$)TX=0pt z2p7TOR12EpXFN5dY>Cd54b)VDC?twu2}?_6$=ZzBooH$7ZhRqf1pMyH>KD&NMRb26Mk=U3^z*l#plOsLU zeEBp~uBl`vl755i5Y`)a@_HGUV%hrHR)wY(vz}3<(WDlC(b`AKXk*9vj+54{--xAf z&Bl)7rVHnX;=|tY=!qVBKj)sYYq!PQ}YiuMswiRzSrJPmpogMJyruuzYbvD=P_8eTf+hS~T9Ng3qGB6(yHj-duZLL{|y~ z5i*op;ATf*a@0%1Ry!p|90?XJce4*|vw$IHlng2JgMq#@nxEDfv@f+~h}DxL$?Y;* zF)S)EvRwJDV8~LMuH;H}$_gbfPe-wvM+&R!!WG6yRTK^BtH0^0yaNAKm3B$?6jfxd ze?MCGrP~jw!!fz{U@~HEB$NFN!KWA?mmmU+|5_Bm=rye_j@5{GklAb}m;w!BGprMy zB8JGwzDJF#p`tMu%CnVK%X&-(R7Rn4O_*-X)+v^zT0mZ^PQ6lQ|)H5X|#ziNZY!};fWyz@>-+q?K~xl*fCxf zTc_EDh;X9$VA8l$s~Fs(Q!_M02xc7=(lCj~7(0~CFjo`F=m^22%Q{wAjioWsOipBE zk|0xqOfQ6GvzSnDSssAEf}Zo%AR&9>|uaXp|;z%=m_t&RLC8e~vplu}^8zESF2KhTa;3 z_7>I>PMKX%*<=kk4XTy~)k}k#@})s7r~_nmEO7Q>R`F$ms+M&@`?B@Q z0G5Ph#?lSJdcRa!Bd_7j%#?W2izb7`N{pF6YlwAnS2>xN9?QfACnm-lmMz=5b!%B#Z>##s$gfy8 z1tzMekxItPBpRu=y=X4q48u(IK2?{&*CM{i8e^>sS!x(;f~I?)u;rP-$LLtjtI`SC zrn_0@U`mMWv_-;F=4Z)vkr0JUYr-YpBv>?H0h7f#WYdIPE2HR=_^L36NT+`;!b4c) zqXa&iG=Gz{ZLev~8ycja(h_LdflY~&O383nhD_ubMzvlI@gkD z?qAb=#_D2P>0)~3YYTe^GjjIrwd*gCe?}E^{h9n$rf4}%k?eB`3W;Vl%P#-6DjJ(P z4Q?Z4n9S*tuvU96XHJ3fW)o03V+uA^c&gcLRdzL`Stc~)2+#>28`zgD8`$W5Dap4R zI!j9aG1~h%bMlS167$cK%x0hE$Fg5oYQ2)^#^Ek)4Vamo&})e-h5jL(Wov>6-J1HD zcxxFNDUmhrGnccR(va`P6&fmIb>-bif%)Cs`OhTVumiD7m96~~QOkVL?Y7f~-FX&$ z%~;f<)S}8l=Q3x3pY8Iiy>KU1QoO~fU;pI?$fLogT0eaBnd;Mu;B%81a%!fsHSEX_ z;w3n3xM+*#%qFl4I9{{k?3x0TH9Jg9bMH?Bm&>-$m6wS4GL(C zINDe?TS#HG^#-3}HJC3IYiyWK6T}E_hIFoJ@9JLPc~WO<^ZL$~nNnFu-6zuVnsB_9 ztRgfRIzSo|oIQ!7q(rWBzDr5py2kF-&i2N&E@_>EY;{a+NT;@#?jdH5HmC7E6_~q%gw^780TMR#Sw=XCD8S;E> z`!Q>}XUc1O-`Wm}jQx;k2J?p#gb$T%L2YuPA{a=GaqIx9U0e!Nr!rgy#XMHd)NMie zUEg$UYqL*epn@ANGj6t=hE_XERIIKkq~-P9EeEk#jff5f ze)qR_iE1a5c8F?MXAd|7Xa}lxrOKcD>o?9sM0%)al3A(vDwfIV5E@3-^nb8ilm(4& zK42EBj?mO~W$OHhc9C0==5(cztuNb0#wIgS z{`$s@bv`W%_N)?|?b}Dm6{)5wi=;Fg*lUCjSj^;8kkryB=9I10j+XUprvKnpW)v$_ zrU8eyb!^R|&UHP0v&?gHRQ<=ypcYhVDNcsv9DbABh%vL;Iiz(ARS8N#acipFdf?)f zY~Ea-A4q9L8X7ab$Le*wjGN4HbuRg07=O3p zPx~M$b$XN$CvwU6h`(Pte~%= z<6cIXPl3z9r@?1H0S&iHR~!7bfmSre9gQF3UO|}8g3p1^gD-%58b$^ri>WuJ`6kVz zx!IKqLD9PwI({y(vaGzcHKdeNUq8DxI6U^rti=x3oF>+^Hny~O`UYUKwpw$jLC=tx z0Ax`=QJWni)n*|F^N(&%I*X8FLo@k~_?>E5THt;$yTLva!|-CZ<7mx2rjTTBU?v9b zbfL)|_VK-DUhIp=@=M@KaFyhmf5KRWGI%4+d%L!whx?m%C#-%GxK|VI%itPtEw~Qk z=TC+5*bTva9(Ow$x8Pn+m>a;2;3kDH;6o*bS?feo`!RNvZOF^}9i3edofrEGVQ&Vv zfUg##V@^L)qoi#H?JP}Tbsr-e&(q_M)>>%YO8DEr?ci(0Xc;EeTw`37&Ot=FwwSQ` zy&L!Ig!=~gCiqq{8XJs&B^hIP^?VFF8h7D-n{ao4?|?gt(a_;`l{R-2|5YyR-izkN zzKj3wfxE!>i-p}_Ht2Z^Tqx7uK;u8Kf8EICQ}Fczc)1(g1MV%xmryog3VUI0jhj0QL>!3AaU~zW- z%9{OvzuTJ2yqSv$%!scpbM~RxNt5Hjw+}4B>K!BwEVD!X{=AH^OAvEMC)y6Sy0S8= z`j_Tx2sfprg4)5E6`b_oj*^J6AC#2{c1dwAft)WhP-%2quJa>Jr_P+@bO-v+-bYcY zS#OSj5)kznniR}rwp|ed=_ET-azCl(~wXSqhPlvnTD1<7?v2#fq&Qxvv&57P5~D_ufxsi2{S_$=Cbx}X+L)b zs03A@8q|PVPzQAMu7{btY)Eabf)Zv*dnzk#b^5pCE-Ygyp zr4agYi_o$=CfKRNpfsy04CAOYqdJx|h>f+fbEJvFKDv7dKDjLpyjdB`|(1gnUm>b%Aa0BZ|dC31VO*-Y(Dlb)9D4&GiL`!4H($j?9 zDHAPXM5@6?SXssx%o_zWwP99+6h;GTw~7lXM*hkT<10Yp)zoD2>TGAS733$2&a-_d ze=w9~#y`0#n??M#MB0tN=+dc6=f&=$ZMh#j0IJldSm$bftk;PyS23?4+o~$fU^7;! zen!+&^rLC;qc2Q*e+2QIO8bR#3Z8e^i3jzokDM24ao;|LE1n(%4~ZvxFs*Hk zB0Nbmb0UJy*@P9Hjxn>Q(#}?f$Ac(lPR@zXi#-h8N5G@thoGP@wc)rb_3D~^>*bTH z`jAsj+c#m6ZMA)w9~0l4+SxQG{!Mclta$xGne7KW0gt{Pp!aw2euxJCN4(#RJFLc@ z-->1PVo&n!$KWU6r$E0`dH>J(oZ8FZ|HgeD==Uk!Kh3+JfuDn40R5Ii|Cc;J1AO03 z@6X}=uXy)s@Eh=3V0uUD{;tJrk00gpf%wx;|Eiy;UxoJV%io6NP}=w{ka|c;UJ$R* zolVdZ&6WtwZSL97bX_2G+^-_S?EP>JJcGC&)-%7q!uRjUpJ%~y;P*g3@gW`g1BmK~!bunY2%@^M4LX0~-JgNa z^YO9a?U*Ilbu`GtI=d!Vn>C>2m@?~RKQgJVQ>~aYgkGMvv#hDW7IFpPpteEwla=R??y`(Ei`&JEvb&{c_5{V=;W5_X74s@m zWi94d6FAOWh8;?v%*F0JD$IhpR2Ds)(H|b*+`5gSl%(3wfE{N&2IlmJ^q5xkMQm31=NY>-8}Wu`tjlN6B!x zBWq^K3CRk@rG$mU9ALqgXPuQDrOxs<2yZ~sjqc1Yxmj{kMdRKxR~yF6R${w2H#aV& zuWK@`CC#y_+P0diiaO4At!%1~SNB!dapqA)TfC{dwYIIfvbwFh$xZ9T_C9J}?C+G> ze}I32e}R7k&Fegms~a=sfgQk(U?;FMDD}*v`S>+AkK*nI%AvUcw-UDsRD*^5y*sGm zc@Nw@!Cqi*@DlJ+@G`IucsbY?><3-}UJ3RG2Y>^?LEu&3)gS-|gG0b;z@cCfI1C&P z7K0@q6s?oCQQPpPS4DHER`_=&K9WvXOb^M$8G=m;>U>2o3xDR&_u&$8>?@?QP@+%B(ssrKS5R^+>_xP}{f&U+u& z-5!x*ZNn$*g2@Ei?+SzVG14|>O?_gEl;75rVRWDA-yW2&SjqcS%1>J%r{Xk@b;2X} zT{uq7P)d6`)cA`=_7f9f?ET>0lu3?G(ULC9jy!cha7qgLSay~^hi6Fyi&w17t+AkS zmQu7tE4%#`K|{G>I?5GurJNIcupq`-twFAu;Fzu0#&I4vBe^*_e#&XI+B9<<``Wj} z9HqP_xh2(6@Y=nJEmc5ed?GyUYw@D8iaH%4E6394=1A)d3(pxds!u@VuGTeG9~*D7 zuNoG_{BG5hZ}Ni>|=5*Id0Y{W95g4nD_yOUHxkDh>np0t8>?F5t<`N+@#^NL@_18IePg_>qP(%Xu4;KjWsA%m z=C_)5=?LoPBf(M-2W6nxdV^)WF9)+-Z%{#)N}%-yRk+na>kXRWAPIVbPG(L4y4|Qg zZ3WQZ{?umw%&VBOK!>8>h5GcQBUry#PL_&*T;nv2RQed@t2qw5uHaH>La`|)N9>9i=mDz`iBPZgPCa924xOUKr+H0FkFta=$vj$$$?nP@Y2 z$_%8iqT3O{VwTQN$yi>WHtq{fd{t*kQ&j5c77tw5i+yndn|jAC#~w@TRC{AEo;ms6 z`id=AX5d65aMn09GZ~k&s?+FxXvd}1jhRD|KVcWvSDvo=VN2;uxt9~#1v4h8&?<{b ziSdB;5F3kDlD(=WXhjZPHT|mS{TOm)wmc^a8A{lU#Yzrq4kiu9Z(f;n9e{p^9z8Es zL!D6z>Oegx81ZkYCmE)9#VFj4_je-Pf65kgBea*pM*}zttSHLUaXC!MesKxm^xJ#& zyx2;@tO7@a)u0jN&qJE}EUfDiO|&#R*(1@$_`@IIEV_?7x^IW>R;t4$Xf=Zt&F7#SRAUB9vWpyZn9hT1@2W_&a$0s`xoc_1 z()N{1Zl*4+CeFv(cJgr)5b(rp0B)JWe;Yg<1J;0cP>`Q}*52~~e)TJFoEJM5f5(Bf zU|q2=#$aP-(V30W^5{?(b|pLtVz)5q$*xCuB~ZZ9u@lYrXu zlW|W0r-IYKYe532JwF}yAc|Wlr<5o0+XH$*A4q|IFaWYQUIgPq+furIC34wC_>O&J z8LeltFI?v{qiayu@M!nB(IGu+(w-w>tAW^|q-~HmhQJwMGZ+R++X$}GHVTxsF)$8h zo3=9vlLi?u0Vcr~uodL1fgfj>f!5qCN#^#H(3X^w@Ahm-*}Qe&K+2qJXf^;E*|~*S zMr5q{U|DeQpgHGvCK|;9nljdce7BL#?cgl%I&e0Sd|!_%`Mv>2zHbEQfZ577Uw7UF zjW>gH0rJf)Facv9dz~fQqZh=MWnS#`D|fJV+MINg9Qt^BlOC0a3ROV4 zl+E{Y_g#tq4bSJn!};JXK)Q7SD5hH%^8T%0wrQVEw<^?9&?+0m%#colj_43?%D;`Cs3qCNDxcIg$=%q}W3}Z?H7!les|BCsxgJ;Q5vB^=(ITeQwuG5AHC0Sm5OjryX|FOml^-TGbjqSW zjkOo`;d>;(-=}%YcGh5CB&>pwDWPn2xsW(AT@uC<+qQf0vg~_C@y0GezMmsqp9fz6 zUj$zQlJAwcSAna+m%%mQT5ugGRle8b_XcnyxCuzUU%`#q3%z`A=6x66Z^8X4xE0(6 zZUkv??pk#2QDpr? z@ECX;JOQM0Kf-+y{22TM{1iL|){(!Z>fF=#FIDG$hTj)M=Y9^IsLuU@zf09QHVllQ z6LOpq>43xqG55Z=(&>sDle=z!zIt1MbdrITI~$3di!3w<>7*PP&3jnV+O4SLEwr0L zCeG07mQd#kKm{F~o75!|;8ZDgeb3}U@8ZyHis}uEI7Ie0<{({v-=Gce+NwXbq zl06h5lS7%YX_y$EQfMT=D8Hi9lvG$YyOF3-?eNXov9WQLTz}-DSM#bF)%+fG{Fms! zGvHU?*WfomI{sVS-+^a=_SZaz`+J~vzEmCm1OERA{serx^=F=o)vN!@d$n7`q=v-H z$;?7)F99zFF9Z94 zmxF!5e&7}0m0*8x05}jF1YQMR4FYg5I0U=~910czR}v*jgsvIKNLyCWm&xXu3osnb znev>jkEzCT$!Bc#&P>)dP~};l`E8SYcT`akBF_(Ax zH-6WlER@`Oy)wpq+jB7D7#_@J?tVd`-#N)SFov>|r7bO{Q``6cd9lNg`{7_QSOSg! z`aQz8BY9p5;-CyH1NyDKXkM(G=L%2>sz5c+?{>b`@LUV(Ks{Iv^sD~ByjTOzM}ZY! zC0GSq#`sFDX6DiY570WAu&Y5MXadck1+;=Ta12-j+QG5lIN;06TAtT|4zM0<0LOz) z@EB#T3%46=1e?GK;6&id%Sk++3{C->Up^K0H1JxG0H=c_=mEXJrLmy_8?owl(@n;Z zJix>WjpS&&lGzipriERfGFHRbzTfhvt%u?a0R!PO_s2IuS~6TWKd@Kt3oqr_@# zBPA;AYjNO{E~L?_-xZ7cRvJ|rNp$;qPOC=zJ1Mqlv~wHDuq~V>3cLJ79;o}nzC>hI zPJ{Rc9C#-d_|Zu1H|l^&r&nq*k^v@8LwB6aPxGfu#5aKw;#e5T@y^n{1}# z^7*_en$IqY1ou6woD5fU+`_5m4Y+Q#Fy3f;ugeP_O$o8dDLay^t zD`DQ6I?Ws{?kZ1oZ+8^e^_9D}m=$+B66FK?*G2_F@& zI_ITGXr;tsnLt@t6pLp0x*@B)t)BBRG3w-nyi2GDujGKW5u+-_CBXXb{uEnPUTL_fBjm*_z&6>ElNzFB zo9XD-*3oEvz{p7TxinXk;jHxVHCi{-Vnai>HjNu)E+`RxpXPJ|(u7o{R&k8zLL)Jy z1(8l?T*)d%XRXg2BR#2_@f_AgtjiNsvkI<&pt>2HA(T~@yu9W-Qgdi>Ht#s-!e&=K zZv~`Y5Mwi`(+b+vuwiSRvSE`+#b&>19j%u`6YQhWw4CoKpI3fAI-w!eaK`Lp2&3z^ zwU{|fQM8mtJ|Owee*6XI(5Hz(CNynMGJVQ|*KAd$!lHUcQA(XdWiQB~P!O7fa0f|8 z;&W5$X7H7tBMQo;!|1D$DGtq|e%Aaijb{1u`BYqQa_VBIqZ&z5gLN$HTGqPkgam)) zGvqn|-KMyh!Fr@2c>%>s3hx%Ay*LC1lIb3uUid<(jSZ(*v(%e%hLC|MK~-gC<&ml) z^lTgNX+4i)RL@sAmkp`|v+LKU80{EOks*AcwXdK@8P>Fs9_3n7mP{4y>Km{BMj>;o z1o=_e9;Nd))v!NW)wktT^)6s!Ko18xr98+Hav%wNzcN%=HvC2zI#Nc#wNPy(7r>>` zu2ip-dS-*pn9!1>?)mUlQ2yUk28(TFV%lOBSlRlQZN|Eud<;#3CD+JxpOO3^8>VH%#iGPV`5>oM zflZrWl40$4R5;71c+6iBDr^8xrH97S5Mc$TW^1SBB)rB{I%iLSSP&~Mgj>Ku3)nkI z1@7{tpp05+vHB>-G!jj9*w@T<@I$SS6uUN4>_jI&$Mp`OOcRC8VCPTLn6a*Kej^4N zQ_Lb_rYh4YAoM8qR_6Yt+)Y{qvK>vA)e+U!a48$;v~0$&Jtg~El#6J3z6Z&Ln}zM= z&L-M?_FyQ|?J@c*!I>7BLP3QTRmS4Xlp9_M7VUBQ1~V)f`MgcpMN{!XeVqS{WhKi+ zR5?ZxUA{KVvexD0WP;k-z)sAB?OQykxrlgfrZ`^>;UtZt%JW$4F@C)GL3DP>U>!hwG@OobG3mSJp($}KQrt z`BIUI$}Sv98DqytVv~wK1ws?i(cq_%T#fuW^Vm@+D;%ds15mV__z;Cn))XP_SwCUe zNQZ#=ugGgtDNQGxtjVlMsGRylZC}10$E_V+m|RHOfkCzaM}%dX@~p;XLq+N&4Ku7P zA(xHLcN14GD&9jh#iU>{!Bk4&YN=40q9xK~rmm@AT|$0CW8ad~)5c^ATj)2kTww}+ zR38-eo+E!F>q3uexQhmk2-VawVmq7=k11HzG#T*_8qv(=z^SZN#nOnT8#9+Wn;h5X zmfhNdW4d;Qt>1ze&0m!J)6Bpy>2|8QQdcDK#8xh^no4X+dC{~*tVJ+L*Nz)ICg@?h zv0^l_ISpYnq6wplbV5jGab0X?-au#Fr5$v(!8p)f-vF!!9YE(TGy(b3+Ijie+Kb03()&~Kl(NVMuFDo`>+AeJs=5s!DbKu#V0;}7$2YLDn6zN%3FetYK6Y&4gkd+054m~DViVp=JQg|-k-unX}p-S*M5rsR9Z#X zr$Ku3|5ENzUZj^gpG5ow&K<@5Z{&ZJ50!!crcAntPx_`fl`nyFrB9{mqIizd_`j-) z`Du;HUHM-L{u}u}9Us0f3LG#0DIJ`ShvEMNa`RCbUQUH8CRcbkq+@du8 zua<-Sv_{K;%JP4+{+W)Ke#cAT-2YTrXoG$ds1BHGdn{g~H2$yVeSTV_c`yC_Z|41U zyr>USYF+HQ+J3W~Oe>kCrt6NwB%J#9^wzPP=K0jnK+}DBuVqOr@;YNrbSU!0SY-?8 z_~j`@Lim*@MLv0Mv-B7^*+C)0F>hSMSZ0SUq5X#?0cMyV8O?Zei7~$y(ko#c8OjE~ z)>M?sgCtnMff*=j&Ts1YXG_akbE3mnlADvFSyMC%he(Y-UL|Ky;~bJ(%y{%dnGCHk z5rb4wn(&*G6}c7{2FuZmG$sz279z?d6)wm~y%gnqn`k}@vn6Qe%%{uD*%D-FRGta$ zg|b*mZqAm=QgYMKMCLj9QCe<>#@lFK+bN>lyjJ~Ikk@Yd#F^r==Cgc3&X!=d79fXm zO02j3=dd$vQtb>(Zemp17bIpXV}5>OW=Ak;n779Gsp^i|(bZ(2uPXC}WGJBzImw(> zNZD0>ZliUM(Cya}mh@%qS6PCrn&j-&R38T%xYNXPX;;a1s*0s(%;xL#F<0f*=~(j} z!5L!i^8SiNY(CB^-mtYTMlNh>9@k9f=q*HpB{gd6UOk!hSQHQ72ivSor?_RRZr0&WA4Pebw5 zqD2$py_S$Wc%4D&c7df)4$q2J7iQ=kPL=`nu|I7<@p)JLll>Bg(uCDAMzV6($w z^A{F+W1MFd7#%454F_-FyXv-F(r$Ici$OS#26EOy2ppZeb(Uc#+TqeUOSyHe*D4<@ zxouX#oH9B#x=A-1!~Eh@yI#a;S%=oC*+nBc8Ru(9&p?TM=Laj1?`W{>S`2PAm`E}s zq(bI8M7ENY;u^-Hs%B_pm*<$wwxcgyv0&DvG%U~t;XGor@vb2F>d_RDDSep*4>__5 zn;8!34Ckuc__QooZ;oCR|HN&XbP3CZcbB*Yk~9>mZ8TpNbPuN3h#2mDHo2!X=@4Sp zX!WY$cPnvK>Wt>+aAj0TWr1rkmGwp-<}MktsNYn>aFiM<2<2z)wENMn-wX+40z<)< zbw49#7FZfKir1$_+29y5lPlUM5=Ni}Fd=nEtSe-{uw2<{+Aez1L|-n&;{M6s4y}?R z;WLwNHS4KnLE5bPEQz#X!dMAP&B+`b7*^oBLg!BoGogG|t*dYDmbTbYri%typ zxFwe(lU!=bHrD~xRZp>)`0RpnTmMIvK&p4D=-`# zxVR~m66eF!uxm#os~yx~-Oc)KaP))H5SRl^(Q8n`S*-6U^V^M8jnI9}+xx32l` zYOpiKaM&~z)>uGqKyW9Z$H~4EDO%89*sP4?? zko~3bAX!O9em+pol5YV18~o|cR<7_WU~h4oD&g7HRc?1v%&b+7nRV|mtv-$QrngXs zr2Au1E2nv?LKiQ}ETI;$eYM3LP-=Ha#r%e(7_B&sUl7DhZ^k^h(O@h-Nf&4=7T=1+ zhC?FyG)Uo3v*0NE=N9{xJ8U@7H~V^YES>YWi&-6$ME`JdAf{^4F{%RY3UXe7j+S9t z0l7peS;)1D>W;Eh87cP*V#Nxt+a(&-X4$ID9<|9f#))Kl!q#E4s1d^Y0D?_wdra+Z z&L|unP7Nf7>87Uf11jYCN-#ZL~W(HV>gKhU9T z5lvDwCff)ZKgId+-HV21)?{xSozinMaI%DlspYLqLIzX$;o2{=1H(o(YR6$tFs(MLwD{We*UDnk z`EH{y5E17;MloUj6Hh{kBoZfBrLI7v8cc&(q0hT$;iPp!+#AmS>&kYu~oOXa?w&I7O%yFFJ zPl@2DG;&`}R2BHvE8ZIwLO2Oyouqdzhtp(VoY0Yr&DC1s-&M=rYc?5dVANZg9_kCEgQx zwwHgpbWRnkPp@QAB+lH=_I%dMTs-IEe$zdVTC;*_6h;^~&opt`&o<7txp+3?e!)GTh=mgWfcE zP_e+m2cqVPS{Vz?Zg&3{rq&5E3)6s$Y{qbO47AH^mZ3(5`uc{=VWPzY(lK#%XNtO= zs4+dHaD-&fFSb4HFI+q?*0(!-RnQLxK*4#nVMa>Z%yEjX60geEd4$(bSa*Kjr(`Jii{i0lX2M z1N1xll6kQ=@%(0RE;tVqrb!p|CR$sLX-#nBbe9sO^qfdo{jS12pKxyh7k~?k(de<~ z_xcU)%gbquj5a-qgdbUVWt~Z z#P_suOc|=e^miuGW0O5-y{@8KO!(2tn#MLQ^)kQukIaj`3mtej zcn^3lcpuQ2U2mo+oeRzbI$Q1iyuS#{=4>^c3HkxTTnu!Vz$LgJ1iDLL6CCL7$pAC~ zKekjQr+0p=uCcV%p84^#zG=Oz{+3_4!_JjojlTn+eHI!!t4$Vdjn$JtYbxdH=c+VS z!(H@z^tV4{x_o(6c~xC`yu77`gP&6=4t_4L=X6yhThq*y4E2rGmd1(5@I$2OQt)B$ z5%5tU8Ga0x(M)WAC&Q2P_b0$?W$5^{xgTq8tZOVUUtSrnYi%Wm`^59aPSHG*MoL7cX*h3`whPhU$_pT0XO z>06)sR;fECqG6|Mw{^!})L)UdyU>fRV{axd#d$IAkKOY*xW5EpKhW|vRqVMbC=z$R z2i5I$$XD^c9QQ=`9K$_b&-}#8waEKAa6PyI+z9lO|C@OJ3b+~E0=^37T2FIH&v3?8 z)lEr18*5DC<6Mo3afBHwC*h9NR%K+QMs;r%g@g-! zQKpmt|F^!)p#T`JX1b8{)K7BhI|b}Ea69-KDCop(FjZN{WV$EQtaY*U;#AP<39DaJ z$o}v3JahmpbZ-XHMp2DlgM&Aa`-V}CVKM)_Hb!qXsEfcb=$+vmpJyGW7;R6d&(Vv< zJ=MFpgvpOvv^|}29x8$C2l@U={q|zgnvMq_kK~kzHNN-G`3rB_|KPowg5L7;R$sp1 z+o%6xeAhQEdC#$**nIWdzxKs@fAEuMe_!#^y?4k|b{=wF=lh%fl76UX`TJgR?(42O z`LeIx|KV>u|HmIa)pKZbb+FUCeSY3|$Hb>v&hI|s^5d>seEx11{qARX|M?5wddGuZ zwduoNvTynPhqwH1|3#awZ9COo=xdrUH84quej)~=a2q*{m+&@xW~7CTK$bfKfceKcIuxvrti@1 zom(FJ%-?VL-~~5cocP5X{&LVAD;~c8us_tkXZ}_Dp3uK#%gfuUH?4hiNsWtPr9X@#bD}J);o9i!I{QB>m`q0 z?vv}z*!Qg8FSuv_YmRttL)pLf`t9%&>%RZFufFYzzx>FZ?>Ox2U55KkZC(4x73VJf z@qu64{ngK|dj7P#&wldKE5CBvqwo6b$MzeoeZ$6%iPyfb?8~d}-SfFuNhLl|zRT97 z$vuDfmE(8&;48m5vdg>vbjEni<-N~bc2VC)D=&O(&rjX>`1V&l^Wcv!eDuDHk6e5G zZeKn7>No5XzwsCEIq0@`_w9YdL4OFYI%Cb5H~sNPjklin`Uifv|F55ZV&wSi9{AH8 z=ih(srb~ujcKa2Z`_B4X&%b_iXX<~}pE_@!M~CP4_O9IP?|dk?LB zy7BY}(`OER^4_O+Tye|;J0170^Y^%K^K}a@|N9l!)eK+q<)(-3y!o-!f4k)iPYpj( zIeF@nt#|M7(Bmhqy!Z28D1Y>@hS-meKj-*|zq4=hp4+bb)3pb`=9|}S`rt)hxqGk6 zzk1_GZ`@(=t{3i{`0}H>fBVp97G8b9*<;IIvUc#q&mWZh+RAwU_3v(ce9QbZADeee zaNi|I?ET=gZ$1C&Ghcn(_46-(`nHc%J#gbMKY8aa+p7N-+v}ZI?Dm0IT(aj+ZrVPjdLcN6J&@cf@-B@Y{>#55Mm6c?W#$PrJVPgm3Qk z=WjoHRcgaCH@x&$H{SZTvoE~qpHCk2L{EGChcEx*+4sLawf2F3oS6Lbx*r^WUBg7| zmdk!xe)9v3mp^&z!xufWa^#!$e(E28dg%T$o*8QX=cA+5Z5IxF@tzw`|6wM+Q_If| z+OaCJcERWFJbRBPR-C!>hR-$baQNPPFTC|F!R~i1x^D1cRo7ne@E!ZDyym1g?Qw0{QKx?OhA$3(<@Vs*8-IKKHMf0e_n%(B z=OfHPu}#mzdd{FJDYF4VZR68dDZE^yzHTe-tfMAKk?14 zKT_ZMhbPzne&FVN2Tr)<(@)I5? zr5y$NAB^++5;9``M<~RSbO?eJ2ige+0&og z_~gJ9&%SfCV*9N_ZEwFO`Mv$m?Z4&Y_l+&8{pn!mMO$~jYsX_2-h9)(I~=m?u$?#F zdem3GwRquIzx>{luRU_7Gp;%HGtWQ#^@_Xix$bQreRSzTU;5E0SAD(j(!>7H`nxPJFb-+AAA z_qg@JpWX26<7XfIzwfPYI_0pN{`V(auNvEW z%nh&j`N_LpIDW=n&wux*`TeIZocEIFKbm^i(3g7tdjIpAldX3T@4M}%3qJn4`}TOa z``n#6zJAS)%aexmyKYHWcUq0rU=dUY2@DE@8>i>NGE8i`D-!)yIzU!pZc-s4q96O@!)`i!8;+?yH@wDl|sIwNH{9fJKQrA-5QWtMs z&h@+g&XSs9ca}W&g?X_%;QKq^PVimuJ-Kvl@50si_4~N8PaMJXkzh9W8+7154zO;9 zcWV3qdj5=L-NEbc)X*8py5FE1EC+Yv&)=z`zXjc?A(wMsV)yVpfqO5m?$o#sSKsc( zeE{_H{Gfa1?>Er*hj^zuH6F%&1Uw3U2p$7-$Rj%>Fz0G##*?FNb4IRF%bZgm=(8I; z?EXdW2P3{GKpni)3*@2AOq7f9=F{Yjng7tB)O@hRT7CN`-*g9@|FVA6jp)KRF{cIf zGit#(S2CW4kDq{_f~UaKKsxX<+@FJA0O`Olai0OR)q!6T=GVaAtMVJ3y$<}A=VChW zJKjfi;935D4oC-nkNXGkM<5;e6RvdN&$#~!{sR6Aybk=0XRiZ)=Q*ka|KRTzLkC#X z;1+5bWhls+hKBacCY{`gWm3mmYt5$Y?3I0Hcx`ex%*Q;>Rqg=K3^Ng&@!2=TqC2zr zFTBCt&O7812^Pe<*SD<4+S)%fI>eHgB0L?Fn$SQD&-{qWZ!U zF*A+fUtZsq&!}&j3;Z`Se;&m4V7LW#0MfS|ad!ec1Kp`IA9oip8-1J2JwCq9d7=0C z6CVv=D58I_-{-WY=LPle4or-EFi%Je<4+@V`&YlcjgwzC! zJ4=o_VCswQ0NYrh%E8+@-<-PHjIXooS^c?^e3$+-KJe81g=MeikUv4UA%RGqWWz@7LV`J~=uxk;rUk1KG$lo|*I* zPC=;@zu}#xlyzn!D9sHG8wuIfn;cFpX4N`NpD3K>m5KMTrCQ5g@HXIbS{ASNPG%;? zMzC~o=xH6o7sqvrrCBNb}d!{QL}C8%!) zM!C{=BFA$ikv_yAH{YJ6{SYODX}tWI`3oO7;0KSa?l-^Xt2=Le)%m|Zey7!+UeLB^ za)%Ec{i~V_u50?_-&diMv2)HVYm^&X{jSx?|DU&>vg)Fzwmr4w zw>Pgm@QyS2|HDUH`kgMGSFEN87`tKA{8sE8Rc%bwvHvDpqs(=JwJBP=BvmvvO-obX zP`4Jda&{A|2K86i32m-#uB~ltYm7HlHEVvSyga_Vy1G1G+1OHB%k7<2b<0~J!0+AH z&x?z)Qi)fchW%;JzH}3;aDDrQFZCAO2qfUI}!s+y1z||B>GZ z)%`=J0*jDB~&ojYo62je znboQ_G8-CMOC^&`$69M@%IoVZo8oQN)h+Sr3a&nFtY~eE*S6M_H#apkaTjWp3(;tA zzKXR}SJc(j*Ak?*nILTymGR}R6>ahI+N!qdit3i8)+QfGC$pZ(;aFR9UG?(j$~G=O zCmX6O+FF>gR9@7!EU#`WudZus@sX@gGf#>=H&$L%QCVGISx=bd|l%Za9uSv0Qpt0}LnSl(FEwA^NS zto&ByugQbM!D6rk90Ak^I8uKrk3e+Xv6R1OljnZi;qRQPApCTBuCal@8t~XrU^dD*PwIVv099@0g%XV*q*-4oJd%*GE>2F&%`te_d@kAXn- zi7y)s4fIXfK{!5~+^*81!$b5NL_VvC>u9hVNIs3An0%Ue-wfs=pBBP-`LyypoqXE( zehkQ$&l=u&`Ly#a`5X(51JldEmG-HGfE><44r`&a4s-y?VLd1&hYh?x9?V4!orLpp z=;C=gIdt=VBgmJ-Cf<2DoWOH2JxQC&itYf-bp|6xfzqt7CGAtB`$TA*1WpD@_bH%Q zx=-c(X<#ntel6jAx)VH4m+sT~o&@>nj@BJLq6d84;mgMeeR^y;eWYCXeJJGxol9pn zh}}-wd!f+>Qb1|%2gTAp!23Zkm$VNN&Zqqho~KLuX1))D>C!$TdcdbWtNZj&xhgNO z0m@(AdUuHa;K#6n zOJGJPM|x6ewoRC|y8cg1Q;Ck2pVR?OAL^&~{+nD)weaM#`DFTjY%53J z&zJ}^)-J)H=~rl6K0MZI2Zb}pFf%!BE{;#=IDo0xZM^XOT^7E2?{%yJ2Df)G%2~ft ztQ@=>+yi18c8WFLh_B0b*0AaoKtmxtYpCR(_4i0Jv$=4n8fLGz0GcB1dih<_COxmi z$}p_E?l~`cbRt$;*VtH7(O46&TV6r0zp1r3UfZ>@q(KHu05uVlxLd$ha2jL2ZMfTkmZY7|-v`mdDrGp;#z5J2#>@~In-#n!k+9t{m$ zsflI?A_=N)tR0vtJqcTorVqQ86-o3j*QQ1XCI%I_F&wn9nF@)s>p!Vi*1l@1;H-_r zMUiJJ={E&r$k~6&V&UJc(h7Qp2C%y(`*h-fy-~?-EE?AeR3tOjpCGk*>cRff!Dfzf zcHODYFNSl?j_#$Ng49*m*8dbGfWjOB6y`{v@JoTh#DT)7MOGNK zGzz2ETw&BoD~uMpC`=Vlm};OfH9%o%fx@T&D@;957>$+{MvIH&_b4F08gnDC}`SVb=nMT?Z7l z11PL?LSc>v^4kgIw+qN`H;~_rKz=s?`8@&1?;Kj;k`{$s@|}6Hv-V&Y05}`G9u&;f zZaB_tQ1Zn{>C>XH3BtxUs!J9-A(UC1ywbA&WAH*mv1Ksp{Oh5W#EM0oxF;EHlVVW>i^dSiIH7tZlg& zDp)($lN&-!Mr&v1`p)7M%o5Ms@KN8ankJo}0at?Sz%Ae#;34pRFo(QJ$u3Uyl^E4Z zyWAd<-S=vkcD~GVyp~kc*jQSUV(;%n=9IqF0N2~ab#TneoL8rjJ?7W<#Jt!W$j>)| zx$vk1Gc?-FzE;E>9sP#9|7{l%MG)PW7#*n#5PpOKca>#b#N%?`!6?ClRqBPlTka~&S`V(j<9;7iiAlT+PYjI{2b=0D%DIU>sByP+nY(|q zb2KkLFBhqRt3;^THDT<&m`Uu0hOOcbL6$A(;uhHgGO2CJ5yk7$If#f5gpzBP4NMWK zNisycbV@~+HEF_xD~=0J8k;0hHsgHlI*tzMxFcd3nxOI;IxE$;V6oYHA=_xMeQXlh znH?@uCqim1#V~W;>B-&+?qpoLAQ@&4c7n|*WSV6COlGs9_HU4KW{iR7pfWf)thtGa z!7--THA7(!EuSH+EZ!gi6GJJwdATX&B)#xkuJYLBKViet!_tmk%dE0Rtay=4ox!23 zQxghNBzEdt^X0{l+f+uLfu^AenxAgfArT48%y_$sOxV_x6ts1;Z?3WCu-uLD2<2MG z^^l0Jbk=jj&6efd(51(k!}>ZZUN!Qi2lo>5A?GY|PoVUub2yh(UcHvmgL}>uaswSS zx+imHe)h5FGOu3l95`37+3_4ET0v*4Vryr|W=;~1VaN0wim?ecyCg=6@hl_FVb~AY zXP+%T>olIw7ACg*4`zqC!%ovy+o9ySu*hz&{Mp!SOXQUsXJSa3$~362hPfnGj6b5zob2gDcIQE z7O!{X3)zKFjGwgcvSSZgecNk~x?tkK0}k+%!tqG9Pd6Q*kpP7HBYA^mx` z+kSF@E$fp5;Vi!LNzkv&PstebnMO<+HEJNZX8k%0$!Yxb7@u~|LAHL`-TOcsECPBb zoBver0>0(@ldZp$cM2<;yX^0>$&ycJ14?D<@=WV9ArsLDcwHbPf zQ}J(g&p0!tL;QFdC@leyj6_HII|TZjE4k@i0OVKSqVmj7r}AO0X$%M}d5SmjsAr|s z^Xuh4SANz*OFA(ZoswQ{1d_MXD7lCqpVl_4zL)?e1{;Dy77b?(SrF??Vavh{mHT!s z<(-M;Z~Q>rs7fYDvi=Ll{?WHW}C$wyDYqSXaj#V0wrFVD$lJzlatCQ z;bfpl6uAQXj9SFAluEyu=mhwYe<`3cJphY=r?J$%`+xQK65{vil0k4p$4_X!BVkrI zaEDqT189)!(fo?LOu+8YVikii12ZjWmq*akRLC+&7FBe;?*u6@0HOs`GS*L%u{NV@ zrJ;B2Knf!TkQ}8%QVjj_{l|GXoqv7TFEIaR^Fw(fx>A-XUy4`!s=`TZFYan$}F*B=0y|}d`Y*iVZ2v=udhRzbP@x6Z!j?Dre02hNxzz4yHz@^}XLCn9h zAKJAT_HYxK3LT@G-e{-IUTQoUYpn5!8Ikk#Gc3O`X5pcJE=f1%{0*}6j$HJGP?dsY z@8#kP@4Ov$+-c|eyX?A~pY92#nK&z5H@;{}v!|OS9qk#HRw;@x{&KdbZjnjbshR}V zXKX!8quS*m#R!^fU#6vKF5Q@dVk1_Sk?{#h#fW!%kktHzjM+y1T9atDI$6EnnldN< zM`yNJ%hg&zUF-0)}?DR_NWSmmkw=%+eR-}JY>Wh>2WSuDn1F@I|uEybs z&~2Ww5p{Hhf%tGTD)%Mhmri-fqvpia`H69O9seQ zoiADeI0+~%zDz5OelH|^K=>#xp0?!WJ9moT^QZXrE5%=czYZX|`aI{+q-C)l_${%H zNu%T!_2=6=@gjM6-IGk$f;P|z#Ear-1fn514g&Ek-W5*o^c)8Yqj$;^#V7hom%{27 zrSJXwI0C+x5|8M}pO>NZNc<=c@!kzQZRPU_5D)UF-#Xy=7C%v4(4a?Y*E{+5=~ACr zKZR9X`ld80KK-qHR#>G)i{?bP*}3AS75F+@WmUiFXiXJnJ>f)Ga+V*-Apq?TIHruv zg|>KBT1J4Lm48a-RJjL-D7$W*hf0gc09o03VlJ<+peaKr~M8U-6Crg_U7J@fP#n!F%zizEJ>F z?|FXZUwq3ItpG&hisrGtNzQVke)KL06u)Rm{$37VZYtwSkABh_>9qLv-^Guf^_%W{ z!1ri+wZPdeb!FFwS70HX2e?@k~Zl9P{%M-yJ* zL{ISxqUY&1^6b;8aN;d0zvCgTU!_NXXFvc%OMiQR`SkU70HX2eZ~6E6tZzPDibrw_fMg&ZJYV@Vd>F-{Z=NUd zsCW8X?~_1r3F0-He$Su$1VDW0IhtO{Rb|NY>-o`p@h5&2PJR@hmsc6j0cZz`$Mfsc zsCeXG{ECj}Me*pJbYHXrAX&(-@=D*LY4y*Z7k!ss&x=p5;#b_>U%<1ziEp0nMc{$Id7e1VqiO=^QXo!aZFHeGx7BDWz_EF=;L) z&Q>6~4FK^VUc{^PV+oi|S|l6EDFCAB)92s4n6zFj-Z#Lf@>^+EUUq?Ec^vS5u4#Iq zalBY*J)ShE&QQKchm~ICjn{LZKZ|(hb=K?a3A~eTNCqkol9~EBl3M`Qg7rXsEtMfX zcYtmXfVs-v>q&iAzN+5y ze#Mt~(RGG+uR9f{5vZONJl_G&#r#TVeBG-0$m^T@ zPsevF-^HWCD7@sPw#t&%fLVx>w)O-CpfOypV04UvZCF@ecufw0@AYGMz@vEQm!Rw89_OghL;?q4}zDE-YH)3pm+Kf0Hs;+_XGJ+x|L@6 z_3^8c z*yIjFDud!z>5z<6?}*m`MCGS6NSA#YByahX?kK#TqxDuPVU_1m9iA?XuS>jq6>q*S z>01E)KkD8CzN#u~|33s#1nj-e6-7b0nB1E_pt&uAK!^|-8-|bqku*~%)>uZd*RhQS z6%ic^j%92piUr?M2WQl=5B6SWtYiIspMB20EjJ;F`o91F{p86$=j`6s-g~XJdkQBx z-O97=k4nGtu5eYKsyAC6KAzVVrt+eGr7=_fl{OzxJFxAeH{}|`z1ouER$A1CGL1bw zg=_nQ4ab(T#w*pKtv991=HJ%qFA;x~aBUf?Jo*xU`Z(^xzin443tJ}orhKUF>6_}( z=1tGC*Sim>JjR0%P#N^J9_p!{HNGhgDp#4RBgJpqU8XvbF!%_d_9Q`Y9~cRyfHIJ2 z58Lv4s2ml(!c~09W63DqZJqs+>9TRD{1mqjs63TtTfRz*>eSY&&A)BuKJ4^O{%zZ} z;r(x>TWMCBY`^=p@{y^{HhTHDeNA!OHmUm6&j;+drm|Ie$-S*tsTD;-2l8LB$!M$4 zZSSErsekcaqO~b+a8-!E8tJ?C`4YfGx~bMkQ>FSvqOH2BrJkvy%l8G|mubk|&8?L} zZRE46=OU2R*2Si!xy>+fdlm;G1lrmL0|%mxX?~Kd(maV3!*DxTMjqt0W+Bfc2|~Cs z#4jS*9iwDpj5BRca|d%_vKuQwnC6x;JQS#ZCD(s%+UE@X_<(*spuVbpr?zCk{w@Fuiecr>J$3;KwsiZ_xE$+o<`ithsHFOlb+Q!CjkAdh;=pM zjV;`&nrI`D!0fZlBx%a8o)XUE1&;iZSw-*9}ylOC47DLl0Um8IH`@}+j7^y!=I z^y_OHe{PsQ;>?uZ@qAYrZCh7+Qn}f-Ec#q!tY2T!{7Z%FBi>BuR@=4dw(Z!aS@W5G zeM#@n4bMk>-KF!FC{LxYyKtspr@X5?Y@1hF)ZXm7=$}cz_8D77z406M!fN#V|D3R_ zU$rUWmq)~PwDI~93OTQfbH9w(~|R} zFw^tgU06!fFBO)Lu(SqMeI(~g-xa^UDb02a<*~bH$PI?x7 zu5z_?raDl)>%08v*OUKDe&t^2RKB`vb3KLGQSvm)KaGi+ zgJk-^bdWsf*s+Vx#*KB`TX#PEb>~)glYz!-&3!^&EHBz5U2oCK?vxlwW7)@@jdj} z^1(wN%^JW*A( zy)vP&(nFeODYd-8Qds ztNhu#=w0FJC-*i!eLnywo|qF_dHdj@2M*v_P+1NH@+UtE+g6sIZDrcL*-BG-6>qZp zs1sUz$Kauhq7~a`IhSw5W$)lha24n(f;_RnTemArBjTKkKmG2;a$@rfgbek$2@@bL)g@Ih8SfCUCT? zE+G6@O4l>5>+O)`Tu0bT!S!Gn$dFc}ms}z~CIUi|NcLJUy=M}}e1f|Hzc+$Eftx_) z^qM`7+SUo>Iko^28y6%yC)bLppG&k`*=4T}FD?eU*T|&qZ5rxqYVar|#d83VZ z$1LY&@_!4s6)Xq00rzI922??XbV>CDbMd&-lF^+yU-X zI8x|7tzNR)HlPW*$OO%I|IqzBjUGrzBmPiQZuWf|RQC@RY~LVw*juXa_`q5Q1M*Fs3_ z$X20bGNDDWYgC~Iqtd%J=0)L8h~_CYBe|C`HgqP>AsSbumQA42Yc=F`==QWO{xc+h zYAvdvni2_ULI);lmyz=Hf^GBFE?|#)j8k8OF6TOFHY88d{M^<^8Z&(7Samkrij9?N z1!#9Bt5Wp5xsFC!i5|3fwWGn0vO;Ygn#x+u{p_y34SV%oXq{K@p$6HP<=jpCzX#k4 z?gRG&X$Q9#es0f}fAYmU(hmM>UcBRux;9&MSjN|3q;vFBZvUOb^9s`V0C*6r1P_5O zV?jQ}Go_U}%C*64$CO2cp-PaJ zBt00tDRFfpPC+0iT2xw8;1A~K=J|7Tt8@K@;aI*u9Egyr!{KOWtw^pf;x$Ler zTGd|V1WQW`va<^!{$Mma*Pojotn(M<#)AHwU?3EUg^O~s^8==Moz6Y8oEIsxm%z*5 z74Rxh`JPDoevRjU0k!kBn6HDrw(~de^Cox;ybay~XArg}oJ9EOSp=JEU4)iQ9}rQO z=RI4ZNjoVb{Hj1Zs0MR@56lAk-Ue!ccE6iJJ!k@Q?*kpcx@qOP2H5aqjsf~ns-}#k z5UrTgEp+PZ?5v^AXLa$9G;gi~W)$~3<^&Hn4q7P9aL412sOsIl!9_=c#Xg51oYC0C zWAIc(W#}wih!!h|6y^AXd8OQzRuJUwv)tT#ZaAQtBhmakt~KjlV@2vQRoygGPW2ov zpJ-_4dL09^g^q;W-(uR$yVT=*;C=7`_zJBAo<9YB?U&L@jGjks*D&|L zYU(hu=GE;D3(>2oDlsaYrPxB3AvF2Syv7t{4p2QOhmNS3ks{j3cV*CcE%r@oMqhFA z2L-E^0ddYrZEJmtlwKLGA1}&8H4bqCsC#a1ZsCr|R;D}&kh)5jahm!Wt)gbOP4_~_ zCv#1HUA5smiu|F|$y_Yg<^>@gfG!na{7auGYL_*VNMu!!4?$;aQ`#5OWs+2E5(GWL z&|KSUB6q(h+MOC)e6GyS%PT4f2E+cmU@+j%Evm`!hl+yv{_H%B90mEIXiUA)aqg!6 zKci0n4gLc@2VVfy|Cg9w0Xsi_&GR?lzo0Mkz`idLbDNR+s$BHjv;>nHv(;nsBaVxQL;p(fChOyzTkZ$GtqS8MWhcH*nKwA1|* z<^9rhU{cJjnrJLe_2Akp@Kr%TWwJs0KsPo=Ww3D@q`5@O zX`W5bf)>;DFi?Q4wJk0Ir4bIkg-CNlvpcP1M{>B8GAHQI)rFP0rQDj%^@aspVOUTU zEhs3BgtN7{)$bMBhwi8t0J6Y9upY4U${?QC2OEIFU_;Q?d1VNGPNm+5VxES1IvB>Y z(x5TF!80|!`+z;!)%dRQUUP!xh$dj?1ufi@=Y=Tm$@2oQj+pR#v%S)B2ZgHEqz_Xm zx2lGu4;oUcT1TQySCm1`s3a?GI3%Y>&r0`5VAH7d=)2}N*(uGk8wGq|Hc%du!%5B; z#bSPD6ln7~erE`E%qqpS>p))jGzH$Ehh|ZiYZec5a^%u<5Ia}7_Ge-*dP#JYu~uyO zyNU0jqXXF*V!E^@ThGP3D^{Lp*zj)fN7G`+M8Qq@~1VdgJ1pT zU~Y!L&A}F6ORyE_LIdj(Gm}_Vj!XZJ$8SGqzICnCiKwgE!VJ@VfUAkZrOfbw{2ZPcy9_)uHc+6{9A*d6rc z<|3P7UqbOCm!vZnLAn@SG!9y;xvEIBFv_AsO*Ju;uGG}j)_7l_W4(o-x$?lhJt$$< zT5B$5hw-VawG$t^A&*ifVNavgfm0{NwOjEFK|MSE?xx4b#*U}2($y3ViGW5dM|UGt z)i-H4eWLa%yF~S-}IJ24;bnAin3AQha z&&M4KD6nZTg($kKmfMb4n|l+Ol+B3L$=HNk+6R|zO_-qE+{f2hp%%J%tN6>{?D4|P z7*=udglMd^^1$+Fh=oPP!R0YsnGMC&ZRCXJrdrDyF*>X6d&o}DS?O(P@n$o|yX9&; zgf)5N?huNR1(8>hruxklk+3LL&u#0F3jA72M)s5`w*-4zn5NOI1g6^d+-=yFkaHtQ z>M*^H>zApxG2F30=~TKGGq|_57|K4C$n3{BNVhVqjHxRM6Pfa!a*>?3marO{yjrV{ zU!Gy~BBl6bQXix%aFlE(u4#|J1~o#B?hbgy}1pShVOwMAM#+Te2~7%CU&(nQx)d~aa_ zV_>MkZ1ie3j|qQHe0PC{7=JF!4UnpW9Dg7u#`V|vTw`4nF5))xNH8zEptLklSZWpw zTDynAG@$29YkhqaP1Xb~myYNhAD962U9^yBHPJeH*V#g%e2J~f(v^VavfxUz_hvxtYf{YioYah?1UF}alXti?E zDq2my9rLoBT>R&Od{6)ifzn-sc@pV98E8+anD^g;zS8bv@iPuABAk6N_XXM$3ZjfR zvBT9b@a2=BZ4vC^VnT3w($-mQwlP&6-GR2uPTNAn`=gVoLQ zhoYrXe?e{}P{_re`T04fP8CKA@|pN*i-emJ`@Q{47;>cZTda3nXI$cvbXa;pk!{YAO4 zJbySG3gk!g*)T2a61jS~Jr>F@Ey&Bu&GDD!<}-&C&Z_l?LIp8Dkq5)!SayD4s31M< zlgt%A3@=52K)4{}&kN^sj!~DB<1Y+yq7lu_$qNt!XkAgBzbHSu$WJN@b4vq(P&n8nDhfG%opz)!P*@nvFCxAuYs9*|Y|1E9>dy=0 zg|f2?DX2)$)cGZ}|08Let)LCGgASnfKMyl`?wQZ~0$0JjM%6H~hvKc@#AK*)?T6XrQT$XC zEutd#I*_;Q7cp#Tr-sVufy%0QlrCwUbgfxC?ZPU)uc>czSIO?Kql7Qwm!x6mUccC_ zDYHu z@9_a7Q5oo8^-B7T+^Ni0?NCO9zzXATWi-=k_gmK zZJ3>Rg&il4QyY@m)Nn8^+HT;9(E{b{i0)QC}7H}%CCjO2by?Kt$UWm{)?n5^Y!EXEFG{AliCMG;75yqB)t!3-R6rwAtSQw0IRU z&jrhOoV?-CFMXR*cvq9LYrwTY9cc+r;a!KB+>w^@em&??P`Qn}pJ4{O4bvQ8 z*E8C%jmM>!>q>8O0mtXakxH1G6WH@i*3H{Gstg75iNcQuc8piNOq!#P>2sBsjOv_2 z{C=&~5E)bs*%LkO{+7_6Z=(O)3~mAH&$j~g=jE8m{rNWD{|x%mpKr(C9YFp0PRySA z^Ih0?-=FO&HJlREIx9F^wdVc~qkdOWj*oyx zf$H}$p!$6rGr4|O^Zo?rPyPN6{{9M7zfWSWtA4xbckV_+|LfNE-=$*X!T!8jhzGOI z8qkH#@k&@g9Xv&uJPrN^)_`Y#>frB~{{VL9{8^r#1J8rL>|Xp6_b&jubN(XFFM*dq zrk!*8mD&`$IPiFzxzsScCJt83DshP6KCwG;c6U{$3(_CrbDLOPSBOp*WLFQgljjn2 zv?rc@!1}j4wKk05*o^=p0=3JhciVuc@vQi@quLCbfZYLBTt6r5@%UHRO0Nh}oroxn zKA>-kOV63OE|pH3ex+S$vS}{m*{0Qor(I{2hdtr3etXJ0brgzkwy3sBIoaZr%gHZd z-#C&R@aG3~vQ}7F?S~1r$X}Yn=3THL5-SSWo%=6nd#}(2UInj#e}T0?ZBHDr$)_KC z^1c`7>muZJ!V~9G`XVHec%M@5v&nC){$C50prhad!;v3|CKjwDBDUDvfxT;7+@cU6O}8cY*1; zd(yf~j~90y{b|DcqC0oWuD6q`cz2}!OuU9cJ&9M^9g9p@QO`||IFsaGd$aoCe-3sg ze}+pw$v^3_G=p_}vW-h=_Y#YLqVnl599kr;MXqMjfrCu;3m{2JaqZ84ToNR z(6$F>Ih$g+8Q2_b0k#C061)}Pw+6pIWNrNR1Har_)u7wIuyFubZW@w&gAPizz22#!-4X-E9P#%cF7Su z?+&hk5wkB{@><+40k%tC$MaHfJy-_v=!W)SOnf=WDW^Ti`$#Yf>~4C!$gY0p5`B~uK>-O0F9KGVoL<1U%3yUDssCF_hkO9hBD zdbT@vNz=XDeQ({Rl68T~B(iQd+MG$7n7fl}+V*%=6k(0W-QDkGIYE(s$?noxgsE5D zx8Po7gKOv~qu4OkMdmIqCs>MfOweBtC}fF|4L?9(es+#OJ3kT&6@~*G+iRJl-{`}# zoN=^^eZan;1cZRviIx~)o+Ci#98t^|=<5=r6hHd`EiuMpP5}Q~%t2OYPs`Tya||s{ zMQdvrs^zPelloD%)AJU@%UACnQD2w?uR}1J=ZBdBiZ2IQ(V|dZfj^R)TNDiDmzL%P zB5oLyDA)bT=R`0GOa^5@c!@54k$vWM;F#glyV>C2%r;KFTdgCU&Jk;G5r+jQ>vZEl^`Qq^QmzsERf z9$3q^^~xXNO=wGmSgw8Zn)>RIql$fq_0`Ts^rpV4rK4R$u6tsRaVF4uhJZwP{t8PnxDCf$w-(G zg;!!sOUc2?i5OqTapKNgZ<1DpP-B|2cnso|PxeDnlV5En%=OI1!ua%a+%wj7=)UL7 z(5Cprq>`}tQ#Se>iS?WmPyAEYsO%O?aYx%Sb2AjyAT-~I11E|?TRU{5zy z3I_&ZHu9C(jM+@JZiT~y%f0FBD*j6L+uG|}&~RVhzRu4_ z$_|_wp607DeYW@Bb;7q!&(e{MBQ)?gyZRyA3{7Kg-cal2TBC$Fm?&3?5v50`)$Ca99h8fO;fv$%T>JP-Z}UH~tGm%z*56<~_l6~?d& z9fl=DE#V(5Y|PAQy->8U^&D$C+#t7f)sJ z!HGg`lbI;EK%J4)E8+RA_0$Op!Wa7LI#9a8Ng55yNa}k0>w{aEEsl^;j~a+)s%Y;s0DRkHc)-d!PM4wJ?0T$ zE@%LKrAr&}(*)ADz9*3eZTE{V6iuk33Ll68d;FpAO+ZHp^`JTFokt^pY6?;>&_^ZY zNAypQgbKP8lI!Lm+*kpEM|f@~Z7twP&Xr#2QQ&<>rM;^NMo)5UxeYP{YX{E{$ZvY|LgZ(lUXfXh7lUx4JIN=^zdB zze*fsPCJomJQJ`wwqKrwCMw_q$72UNs% zDWemv7T>upgj>0g1(UxtScq1VoEkF*7Nm}WZW+v}&vH&7zEi;> za2hxrs0_})JQLV5IE&}A!8zbuuoLO(%@}eXZqEm{3@+fgr!wfz=&UkuM`sn3-jKO$ zn=V2vd5(o4b&e*%g$;D=jwa@w`AtqWOn`P>X723SO0o15Hm zG@c2)HqNH}+5XXzf4#hR~b#?F=*jGv3896p@q*m%Z z9v=nZ(I1TSM()Ryni(5r(5}2Sy$(boQsALZ>ptYhq6N|1Xn5V4qSnlXXOZ@DF}MU= z3N8a`pMS)>99#je1XqE@U^4Bpw|%q>w^!rm8gMPpKGzb=>%dZQJxJaUoQ8b`SjP7o zz=O=CH)8$?+yoBA?q6np0r!IY z!2MtacmO;IOzZZ_v)Jd+Z`9?XsKOXAn^;`}PjjkhH8Z!yn}K-S`qjCbXarPXQU|k1 zD4qr_+HMwotO~rQM#;chJ|pl%%A>tmtK|Er|<@ z#-8lP<(<@O4?ZH#xzY7w_^|y{t{G0GX>UxnZzR=j*NOZ1#NpGIuq)vt}( zDDPpz!RWhlBZu}(jwRWp<)kq=HPx5q7+$G1+OY}E76zNrY!0jeOXkbm!XOq(Tkn=Tk|2W@BXSCBm&YiXvG)6jJ;>G=B)X=F=OcT>wzm&FO zghBDeHZ4h4Kpq`}2;|0R%&k%s}8m3I6bgjIqmF26iskXL8 zng(?(O>NU0X+dCZ*y^s(Qg%w5>NbDm5T~ZrAB{P+P5uL?Ikj#6nD@UD?P@hm4NhIF zzjUfo-|nAK;T%!r-@n|M+w7k>m6d_Ne3H}B?k}%!j+*0-l<9xP?@$xNl|4*UNLRa3 z=<}H&o9DMxHdM1(s&C>WYGi6r-Gm;9Rw@R@0gt9PIhbU!B>kGK2BmZI&I8sTGy3 zn@ZMB6#vLck;>4dNuyCqWojdn&qP-GRm;tDI}(~VrF*~fd)#mIaVhP9V-8X(!DWG*sx{SH&I!vl0~h+A?#jV)MPekr6Xw$3oD$Lw)Y?ku^ed9&&5_lQB0yMt9iuoF_nf9M>A$^b=`>lQj z;@IsT25&bSKbr2w!7Mlyzxxas+k;A-`+UdyXP?WRz&J0 zI)I}R_2`}Ia2f_Bk{|U414Rhk)&-FEEr>&5W!m*A84} z&yd-z9|MWaTq&tv;=8>Z%&rYMGtbUTljut#-GASl@1APiubMp?8Ek9QUdT6&IIJow9!{ zQZZplS#dEfBH=$%;*uR#H?+01_rW~^M9Hpge@V|&A_1;sbwbgOb!mvrUgoYMc2LEE zQ)89W%F$_CTO`w^oUtzxZnDP4)@G1BV{2OS#_z&ji z;0qvyYhPl11^T)c`WipqfG*wDj(nopE!Ap;)a?kE$x~8c_tk4kZJ6akcM-?gAh{HI zhy#;ji8?&tCa3y<=#bJ$o}7JurPH(#H~&Qx<^!9W;l=UPin>5wO>g$1d# znDVfrw=3`O>i$aoW>!t2ug%ISK~T8sCwB^3M-*llPK_byQ_m5U&so@34+-3`P|84S zCVTW^d{3SX#-H=@#d{V)Pr%s}4or{*27>j#Ah15z01O5jf+1ii7zQ>18-q>2reHI$IoJYh3AO@T zgKfaJU^}oq*a7Sab^<$t-vA%j1q=tfg5AIfushfTj0B^=o?tIvR+6@GltI>#pB>|_ zC3D4_Gqq-}wdUN-kV+G3rp#(<)@=>(5;XpX(MP{nJW!!H_wziO@cm#6*c)U6_17HC z00;u{*W_a6fxgmran^B>HTseEn-nj~*v%yn74C@c4gSV{HTHI$d@gmFPrL=75J>TM z5vI~ujF~KE@LS%;g1)A49Deoz>1QoLv7xW4je2>SLplxXx|mS9@gt{NyI+o5{nk4^ z%h{KBOF#&OLDxM11y~s=tDQfYNnF=b#LrJ&A)I~?b?Z{ZY0`E%X^W7aD2Rd5jA;|o zT4ls0tRmtn2wi~R_@2FnL^G8$W)pH55U*1?kXVe)vFtgOxQ}(VrSm{Rb~gv(cfhhYxebm72;n; z{5{>Sk_bFz8jF&-fOJkFUFF~aFcoxZY~@C0o7W{H2_@TvqJ}$(F!cKfa~l3Dz=7ae zI^}fC%z0+R#oUGy=%H(LbD0czy_~p|*M2l~<~mnzd+4!~hYY=M%P}P;^-iv5j`hq* zuGx8jXO?>34|mOyBfRg;o>}ghb6xYZ<6Lv)BG+7druRPGGv~SH_;Wn-D%YHMy=$(y z(KQ_}zRnZe_m<0DbJN>gbNqv@dC+67x$6_I>8p0l&eL4;Julq*SGw=J{M|LT{FgCT z&Uo9Hoh#mTzptF_nrqH;&5!@=n&Y4L-rw-d{}|Kv(2vGk*|}cG;kWp#^^NJQZ7|=R zO)qiH3+^{&=U-klW_f|@zw;T_-<*@TF#fU++u4{O-M6dze(DG}ypki`a29*z_0S{k z`$KOT)7NCQ6FO^uvz2lG+L5~(v*q)kF-Lwp&Y0teMvQs()lp+Q?hA zHu=n$%eNn9+-EhzS@}1;`t^V=aL(Y zx$?(<7_;-ouZ?-`k6RmeBR2~e|0myN!gU56e2CsxKC(f*%#yp0YL~fu?}5h}cNe^L zg0UaE%Skess*f%)=H0g%vt-pv#w@QLVC-MpdAKnT+H_x;B@a%UE_3Dk6Xwcn$-Dj- znZC+lr^{U8yXr!j%g#CLa+yp2bKuo77ys?pYh>~}zQhqUBBv*+YnWWMnDyCy#;Ji7GndLMgqfeHVCSH^#&chd0% z=Gu7`Mc-kr-1d(HHTn52JM3LE39{_@eF^ShX>j{>%OJNuy>N}`Kb>D}5>q(N*v;q2 z^xb&CsWMycZn{?H*n|A0d>f{G@(k~t2X8m}EzeZEi;sOOD~tG?C&oWB40Azw!(Cfq zmjB_)r?$sjbkdcv-(a@9cyxykliy5lyqmPDM6YY7d}Yk#Te|(``SFtp*XdmPxT%jj z-aPPpy)Qm!(sG&K439h^b5ebg%Axa}4c90g&VFy!uTS_ZFZ$~8tuRY=9DCnz%-udZ zE9%EwzVwx4xtPn2|Dm=RbIk=q&f5p`va?1EE5YRV-svUAY;ecP#nZks?=O5gO5t}t zpH*SZ6YgCobIA|8n)d$2BNyDud&vene*B!izw-OHKE+(Q`ol}sBixdeyFRiF=JLIo zt{90q^Wg*kn~yo*u$i+Wm`9bLeBI=*)A?A-@fTI_-hTFi0f%5-)w0cdGcZs5=ZzJW znEcK?tHhYEx#M@?`R+JYcz(9xb^iFVI}SV*J5lfN-08c{Gatn)8Fke$Z(y$6ccb&a z$9#5&`XyTsZprX9qm`~ro8MWba*??|=~?>hqR>IS&;9nk|5o$9`0Go*YQ)^6uB@{? z?99C2r>eyZv485k3nv`Q_f2=Jy7D;Q`Heoe#F$In@o~|I1C?~XR@6Bgl7Au{VFcwWfr0Ig3|rp!7M*eZ|K7eD-XaC10I&p3D>9 zJoYY`?brYC0%qs@yJmcYx$+M?4OP2)WWjkTE2S~rduIm<9i3}N?YhNbm^<&>@JuuD zpZef;vpaeJY3+-zlfIHuK3{Y3WteXsbMcQ$!cO_t9V3stC5%LLSpOegA9i-E{HnNO1^)T%c0q}2y7R=^9o_jSch3@~uXC3vO)^(@thrd`PMbIy zzd9$~f1}#Ln%vys0|{s5lJ{$N!QAmr-$lk^2HKvxo_5$dy8o_TfH*>`jQ5p$Q5u8Q{N>+cIY-|zA5IZcm-ojHFk`Og(khn?wHO#aaSY}k2W z%%}f8_l2-?`rbIViQae3QkBT8@Q?~Z7b zx$>O1%{aV8edA+#KW)GI4>8wHx?uiBgmdmmxB5n74*l>C>yO7=x=-QJvq<-UA9;TT z?P2A?7hOL2+_3Z5KQ~(O;x%Ds^rb&t_T}wiXW`xJ-!yww*!ldW=?@?FOnBw1Cx&0T zj&wCVbM&A8@iysv;L-CR`XuZ;eftrgZTv;J!^YLQeA-Zg4eyw4K37viEkfE;@eYP)2=l9uz>oASGXV1)X z4&D@|P9U0`MS5#JTzaCVS7WE&u(QyBi~V6>1~?parORt#aw~sd;7>uZ7?c_36?0i} zV!7*P#o^HT8rCnw(*fpz`CtK92#x|rgJZz4;5cwR=mfoC#yJ6ZCxVl}$>8@u%s8iD zo(dL$(?Bva&gs1OhrXu_+>?ojcYj#ZY|bye(iv06%g7NOqx}1z^4>ktAL|RcTU#o= z6bGf&K~6Ug=GB|cJ@;OmZrs%M!p*$;Lq~L&o0h!unQp`CwY%(f1ZO1n>}gOPI|w@7 zN1}!uN7zERbkJ-ib}5oXO2ye-jTZ81870-1A4FXo?9{Hwh2g=hT`yndET?^T^Xnws-zrvXgddkCm?3EU!-KN#Yv+}6)90inyl=$6#j09?FoSSE?FDF^%1y=tC zBsbc9Ii4ynQ%yAJBoD!i9)hVJY7#!!a_&g>lCAbIqGO!fBwU7obkB$!xf{sS^8!^3GKfKhH#Z%Y!xGH{z-)TR?Z<+0HZfoyof-lOIWAcqmV*W`jm^!b_ zW^oK>H~)>I``R%+Uqp9mm@{hI`Q*C3x|TBlSBS!tX-#v76h3i&T@9N7EEU$(y34s# zX&ysIosrVV-9&K!jJ#K^r$5T88RNq^MvO&FHSD>=eV8Ey8XVwH5yL^3AZq#?DVNLX zOgbsjx`t*NcWSOP$60I#s{~(`k3Gd|SWOaN;Mz#b1((-Q(Cw`qwT`<1=@m+(#74&try!|&X=ncmm9A4)jifU zpXIi(GNQ|Q%+XMlQa zP`HAgZTQJ^f}ZuWe(m_{<6B?+8x>tyW}$Adf@q?c}&mc6b_RfwlgjAgKyR#!<<$;Z&%u+Y0Y$=ZsOR!sIL zo0>Q+mj{z+3dcsgOT}HZA>;yUkl|LK{yo7t@wO3W_Lw|FM{E4kI4qi1zXO2AW_{P8 znP^SfX@#bv{msb5e2 zWgh`{PEnYeZ#3R3P5Rk%c=ZNDuN{Dje+S2(n0>{^svhfd0eRA@rn|xUXN#TN4`Csi zZSFT$2Z>9VPOYtTa$ut?4TNL`>C?Jp0AD}xvJn0wTyX|oxZDuVKb$*y8g$FXcm z9DMeMy%>7iF_}+rvl-$n&f6xQG^tbdSlwVbi^t(I%u+yVTS-aoCUGp!ywt%i9S@|s zwHnGSyAW;vCLQK>FeXRa?h>91L#D^0hVI#=i|$!kb$#r{PcHlTxG&z^nlx}r!emN1 zYUtPL-V#C6L7a6Xr=T>z<<@yzA4X%%tty!1FDeakrB7*LULcSc$mRMoubpmtEqeWE zAD4p5z#qZoK<)Gj%qziFU@^EFTm$;bFmWw@mH;tKT!*<7Tn~C$Py0xp=s5Lr_4`>s zRUG**6-v`P;6|n0B%?)y+xx)IjbB)WQ=2MwDA;do6DrW-Z*52Iclv3%=0o)1#JH9glW%TlDu1<$sG_4xWb>JU2#$I`imY z+s93{9q)0^}8JNe*CQf4}b^3N|1g7boU%V zZg?$TcyD1og#U-ZD)5NH5(C||diZ~8JzgrEf_T%cB!~@>?B?x$FC5>}Eay@DJ_a5K zt22coA?1iT!(G4Y;YYs)%qMXFKj5$6$xMDthSa<&JjzfrZtVgUkjkgT3+oQdr||zY z_#0T0DXe%WwqeC;W_xvYrWelln9tz%@8BQc*JrfIVb6WUN1Fq_#{Zbtx-%N}@;m>)_!cr{+td8g-kgP~eth4rYMmwV+-AA+5>d+?J_l`>-SRQHb)o7P+Cf6@uX6R3=Rta%f(%3?CZSs^>Bvx8+izFEpub54_nC%_o z69IsRjv8__{>?IrMyij%J+aBs^%`vmq5ch{x@`7C*zU<3mv zAaA5g(Ac|lBqggye#vva-XpGPoF-Ls2oX<#n>sl$OxYjMo<9U1fxm9#I3HuG&m1xo z!4ICTFs5AttIzaB7&EopQg%$b3bn;oTM1uH%3_P%ElBJGBwU#FSdt5GGlvT|nve&M zyVL0h8}8Dy)sB*p?p(RblB}x)x#OvcN(6`eCIVeXicvL^L8Vl}t-BhhxmdCU!9@|7 zjFD;p##);~T!}MBf~lT+W6L7d5Kic2b>bhiTZO17?&Tcu$ND;Sk@3)yoDLB`HpoO` zx+L5xV3as!B48yXIWVsPY4#{WF47l-;-BJMcaD0od{VN1nSI-+#icH%g=uf@*A* z9VoJ6h?Dr1knP6SW-sY6#op*@Hq~WaCJMro{T}2;f<*nE-DlS}bB|=}!c_U^E`^M! zu%=uN2YlA9ONz__n=&rH-EFVVW-yO}EHDtPtFMW&w}JM#m(kz9pe%aqW62SCy(@Z$ zo7VM6-ypC)*r1PTrQJ(PD04Ms=v1?kL}O-^2c)8!;oB3)i3>TFPYYJ|-1RiZEvBr! zzW1x7!A*jC4gzv_x@9$(^4Sm!0YleaR^4T=&sD7aK1qG`RL9*#W(U)ece!~OM!Giw z8-q>ypNF4S^$f1Il5=_*U(co7fAu=f(U4o#(K@Tm)qBR9``{@+i}uKM*^l9;Npa~I z(bLt#g(z)iw{iD2Zb7u_nxv;hG-bOtxE!=1%h{az+5&6|wgOuNjo;f~ZVR>p+k+jz zj-anf)^@_r&fp&CeerevhG!qx1q=tte4W#KZnVJ&nk&GGpIJd6%NwHYuRilBu=1!B=i>tA@X)238%PA&=UI_&h4zM&O!; zt{vhKJ=9rMgO0iU@fV-fT#dS8=x~G&b+{Ipo|+GiqvAQSN~lrYB-LH+DO{qR;xs8= z$&Pi4Tn5)ESx;sWgcA3{LThz5KHFe!Mq!*O;?%gh^J3`nWRBXDq`L}*(bV0e3s~Ri z8d`=}X@BF-<=o4d#e@%cW7!;1ro>F~Ypp4*U>^TBGOSGQ5yKK>_DtNzjN$j>w|fs& zZgeQ#ZEh5I57Dt;6Fnbas`7yi6B2E;1g3|klBhD8-rRGFre;sqTe`#IV!8$!2#~Q8 zDLQG^RH>yZr4}bDspx89wN)GKRvAKZ7jLQ(a^UKxHba@G<*;XO<9zMeW~9sU^r)vN z%2mI;&OmZ{8%Uz&A>%kNHjD*Ei6QFHYFKmm!U)3psv2fD!*?);m87R*%&z8=ayUk6 z;F`SkY*M`$t{>^rhD&5NtK>+P;U8&5vo_0dRdRC^oFww>t7~m;q!z=qN7b_f)QiAr zGVS$s^=N*;x0Z@-K&<_Am0cox5Gl|I`!PpI(IRc!Tgcg7NFzz>|8|~{mVR}5tlQxA z^}2YHtzk}&y)vg%A>v#T(~}cN}V%t^1nZAx)bN2BLX!u(sS3l`O|Ao zIF_>dOp{Q;g_&aytv5}z%k37)lV!q-Ei{i6Qf1_Ae`zX@Pqwm7n{X3hXkb>X>qwn( zrjFFBX)+OfydQdrkpK4PW?vi3(+z3KO`YH~g`vVZ+OT3CYZGE;-~N)vZ7wg7yEad2 z!YGYSn%&$vEO}Zos%>39X^(a;VJv8(FEoQ9`eHJ=1YUve<>uD%*5;ayYGzNi7^RJw zYstBO##JsK;w$qccXOtD{zy?Ur>HcPTbLa(OP3gHu2^7tqB^wRe-zNO4oU80FL8@BVCF8GO=QOcZ(Q zt?K@-XQn%G5q^DHR|WNX>zBafG< z8Cx64tKrddcMX%qKzHn2r0H^S1-KGi1=9IwT(##?_zMN?@vS_9-+m03 zZqnoPUZjCTrwKJ?P3JwEDG8;uQugPomc2QhQlse3b;1=(9gVCDwR-DllOWsgbil;1 zlGa1sxv&nwxbVq`kQ|b7+-zhPWPK03rzF`7%tAR_B6+=tsG~#Yb*Z`NDIzy;<4Z&m zxgEnk*{z2kF!b5Y9{kbS^rQj9KYMP&>*tkk_3UPykm7pZX7$Yd-b}K;^VuoiZn1Te z{YllIPE2zD=?{Z8*mEE3H+KEM{Np5l@_Xdr=Fj%Lc5vwxFI@lkN1om@|E>Wi56PW; z>BLjE+M%p<+#_%2R9Dv3?fLYi6V}}M$%;Q#Y(rIg92wb;r%jn%$aQH`W{(ZUYl~^0 zSA%Q7eVpN6i>dK`Tly9ITJeiQlF#sa+mChGP&{qL#T@Ox4r%#wOuI}v(Bu)@_!xJf zdy$8AJ%)oJ4m&xg7DG^5b4M$@{v9>(b!#>2UAnqPa_tetm(^xbP0MXHI)dRSGMBr$ zan+i;_Nwp$8@bQxNlrX$mNG31E6r{zEx|USWUt1~`U`h(mz=vxV`!7AS*+*BI2B@1 z7nxgy`hBk5>u5u`#B<44_d=pXi*}h&6O9mld$T9y)-R*H+rcTk($IuSvFH$^Ou(ys z@vQAao$6Qqbe6M(y1Wi71=oXRKy`Zq=8fP_;3jZ0xCQiOJ$oze*JB;J9Mi66H{d;a zj80$A4#EC5+z-Rt2y6^C0b0*)1~vyK-vZ+MD{h_yPl2bw-@qF14EQ_v2k1+D z+6zR5wT%+y9`|<%aXm{|&w=N`Kfw#&Meq_(TsChn^W2-fd1JnpJ2##?i02jjzY1Oh z{{m~l>);KL9FNNIO|Y&qG#gSjM_zuOC7!qN`!;w7ybIm~?}HCOdOXU{hoGmr^2EAR z^79$-e1yM`!6)ET@EQ0w_z%bwkMi?5=&nr|nUJ*n40|30ocQ??dbe{A~rc2HSvb!FFJKu&y{&emmeUdAzW!1vXaI z)NUzDGlgL>c6 zf)H1|m4>8fOTX_)_h{nugE3%lkPS4ylCWp`+F}AZxM7XdGQ#CEb-#Ph;wyBg@;amo&=#6FxoP80MZ=z&{HYB7JqB@fY_o!q zq-ZGqrf`uW7ROgxHB>k5`%7#I$SU(pPp~*mRLLabr;w1pyhX^wU^(6 zBuks{yEfU*@b)L{zPEPb200Mo-iAt;+tMaXUW}}P88ED^NpEhlJv0*xT`Bez)?V64 z?FN~3LHO;SeZb9c!$Pt6drjIpQ{FDL?j)O_&LDRYb%iv?xV=9)&eZN<()=FwRsr(Z ze`Yy(wBvkG0181DxeCQ;%G2ff_u)ssV=#+wUkrW=#)5GmeQoOLi=}8>|1&-Rt1v$UO=HRexo1;N)=8Q`4IELE4=b{mdU0azvqQf zP8j>)Z#ktv@2{iKJx`m<-A? zr%Np?rDfPMdIbOajee0mTl|)T1Hjab;i$4r%Zul282JhE{TSW4v;Nx?a<6xIjgxH$xF2h?uf};B?-qWyxzH- zPrt3~wh$8`8h75Y(mGOF%>^~RbIBdiMpIvxB5v!LRnt@L8~bikZEU-(diFf3=WyLv zH*J&MUv48cmzH*4Z*s(yZiu7wP`PrXtJE5}|Fq|zI1MpcYu4dR+D6vl1d0egn8vEs zx$AI<<}@kPaMJ|?laVtRXT+?`c)8>?F5YH*JQy4T4%JxMWlqwt?T%-a6Bx!OMC0S` zG5D8dT^(%9_IQpDDccX(*^&I5g4`m1G*=f(*H%~i3xlCTe}Jrlf=u4M*}q$SnFJu^@7GIh@WG71C^SiwZcsjfCuo- zo!|s;A~*?X-|uA1--AO4_fT*O&!>V#;52YLI0Kvs&H`tHbHKUaJa9g^09**>;=ci; z)kD9oJ2R?Ayiq+)$|ToHaWSW4Ix9j7q>+;{W-az7>=B*yX{x21I{5?Xy9itiE&-PU zTPK(C{6}y(xB^@Wt^&Q?XaE00o!Ex$GX?AO$_Hf&My^fLiELZyYIv5jZMxBDh+|5d zSM0p!xG&QQlWW6_3tLmp+Sd(eeP$y}b!FV;;!gcU?L_>#5m7CAvnvIpY6}ZXgSiF% z(!$bE{7%Y9hTGOJd^^jzhPt{IECJVnr9gFiJ?1iS1JGIMjhKG|H-X;Pt=^AlW)3t5&1__h=FCJo5LL*dIgUV(UXQ+! zt}x>$>cK1-k_yU{;YsA%yA0e|r4%1bA=x>hoT$GvC$Au0d})`}+f)`8n*>=_Aj`TN z^is`UN8F_jL%iSJ?_@c5k@vrVyTLu+UZ8Tn5A%MoJ+4*mD|mhYT*EljmvX-r_e+2s z=dR;mI;EUEMa_9V&I)dZ-+C9F3gqEounIf^ z9tFzpW0;Qvo8Q$uKOsBNm;C+}_fLYSfZEy9n12Inz%w8{zc#zdm#c%$Fnqgwe?DK? z6t~?a_}5KEyW3EvoJ@hZW%Up87cZ-Zg=R&nvT`51g39hiL4CxT2Wfo{JP-Z}UH~em z7cpM~FN0UWtKc=z7rBRj;eIW69lQZlPH$qq1>OelfaG#|m-qL;```ocA@~S<3_byp zd-xQSYu=sDF#iqy10?tGIp!DOOYjx=8hiu(3%&*4f$zZ&;79NiaJFDCA7p`nKwB|p z@fN@RJBdS6+nG@9X7FXaU6)g?uCt1b;6zO3`?HsJo3QuF;o!JSU}7CKeO$-0J=t@F zJI`o&>BfB5qRbvMkOvEZ|A6Nj#SIO>9h5Y5MLd-7Ia<8zbH! zTasJM;+hnsuo#GgQ=6CJM6XPk{3#RMs3eLbM_psC}YoVF|}cI6tY9Dsj#-Sf7Fs~ zItfyVFQ)IhJa^`_pRH-1?b6x;2?BKQEkv%8y!j6GLHMbMu2m(SpKYATMeO2IsHU6gThuc=GDV=t`lCoNP+W?nu zS500m+iUi+e+zqu`AomjpJq8b5#P>WE}t5JIkS;J?cc}|es;v&lVb+h`jkz)KHUdz zF3n%}T>93(vz%RsZ#dW$>;^^v)#dJ(dw`K(6xb8&1^UuQ%4oZz@#6<$facP@Wr7?K z0O@^1GK4|C=gJP`g91LcSY_W}EY5)cAm5CKsT1EpX;Fdj?* z`-6#K5||8ZQJZpP4+-50O|@M`$kLs~r=SE|e*?OFUHwLON@7^I(@DI}h{Dd!$&+os z@G^FFEjhI~t)J|{$n3aNRdU$z$=u8`W`Z@yW zdBQo=%uZB*&z(=qGK24|Eu>*QM>{$x<)db+cKbAKO|nS({cHK)OK~@~cHT6XuU}Ex z$52jwAdt;al#?F}Yi*J1FU-!)&agCd3cq5Xh4WCxSBaudags!eWele$Qq0~tkrH&( zf_g5acV#p!k(VAaTgY62ATDh~=p;8}nEVpN8jr3Bq}wD@<53u6+{=3)J!r|Zlf&U~ zDX?ctJtBB)X*U@zE6|9`;RTFn7 zZcn!rxqB3e(A+U)5Bk65^E-y;CiVsw&?gpxqrlPN7+_TQd70zr$(5kdK*YFIgFndw zX=D<9@_zj|7j12CtxbrvOs(K;a&2Qd{21&dwl>cXyUN`CQVXvY1IJ`&F*DguaT{tt z9aS0fFNUbj?05oQLxRKHXy1X_$okf{_Q;%iA4L{`P9Ly|_&0;IQ2e3&}t zu)6K69@i$%ej4U@5CSTPOh4sMR71YMF)tlGXG$}n*Kw(HPt$tHL z8PL5(>FLq8bU(T$Ll^-XfWp$RA8o0vp|-X~TFKn5G_AdsB{TcaRcKLkIhc*kvN2AU zOFlZMM&I3k+uz-_@A~I%`}vitw=MbW&6{m;IHq(a?8iKlZ?DtIzoD>is6& zHuj3;?FS6I>6W4I{LcaYOT&Nq`r4xJmo@+P;xlIc^p6ux37xcK=<8Ru9DKrzD;~P@ zqaj~Uxq9QZQ{S1nd-0ajDy}>AinGFhxaGV-V?J2(ZR_Z&>T~A*u>ajVY!hz&*LzEM z|ML-dEFLr`c1Oj;?fms`Gw z9Z_)I<7?}W*l~5~J!hP{a8AQBCpe)?z8E_6?JV2>thw}~HCrD%a8lV-8~>;Nhy5BJ z8T-?H*KhaJN#lQi)@|>;JZ1aiN{hFAW59jGCf+o8)x_yjzg>HB)u|oJ#Ey!Uq`QbIQo}|kK3bYqs=aQ>7(1X zxp$jb*_E@ex%+$HZmNkJ$It^5{Q&w1?#TRQ6E=;b+^9p7#ca>49JInb~igy)d@VilZ9F zKKJ>zppj*eSi}>Hk-6wsolzZ39YYOT zzm35_uy}31E^3yFz5435_zW@G)nJMyKSs1Rago$jbu_f=^BB8bKdrXCoyOlL%}b1l zt+jC6HL+I%xzN#6i-xXhSnFIaL^E`yhbisOo?6IXGFVSv^sf-IR|rO5oR$hs=uTb-u5B)HLeKKF zTjxO!t;C(vdD5zt+xnvw-|a%2`W@_kcTQfl(g~g1Ic(^#PVZBv`LvQxH*ZkB`5?BL z2RY|%G04f@a*(qIv}`rV*>vkc&T>$`%^>IQZL!;akh2z?yTc%7kq>jXLCz&32RVyJ z4|0}e<2Dy}g@c@xV+T2_O9&@A$a!HrekS2|F8&+%23i^ky9xK8yqUPcxh;d735|`l z(BAE}sJf`DU*MzuOOY*}jD|IOe{u07(wEoe$Ght%U%2h7@9(tx%u`CXUeV%z|EDP@ zoPOC>LvP6`n&4ff;c49La-G9-4_sRG$%3}Yiw_<+e7DDbI53^$(a0&1N9{u-jZ8}t zymVsWdGFspd;haed3M4N2bFVOtV=feFed@EC6Q5TW428l`Okwty|jGhluNgK=%gb) zeCy<7Kjoeuxs@MFYVqi7a?^oFjqThaa{Z4>g4;zJP8Zptv`LIau{CU$+tz*E$yo&r@Pg=@@+}7+8zFQ z=Q-b0jDO(Sw?`jv@kYZ`UN&w^BHFxmY}0zd2U9NyopfDs#rDDJ*~+WJlD*=WKkIhM zN!Psdz=tCyT{G;qEe=_@-QeVSZF#5EWnvlRT>0Of*8gVAzWbl!pSXGP)-z9&Uxl6N zDjYsL;}0r6Cp7>ytwUU!#ZDbK0F|NVnJ2;HlN=&``cSjoE%(l?WFTp zuLzvI>mygy558x_aA&=;J1)88j%_MyfAA5;p$drK3@7x>sl$ej8&)-Fm&gCs7QL=> z)r-U4d;Ho7*s1=ifk;swu<6n_)t$&~)k}jDTDfT0(6h4E-|DY(ud1IBy?XeR#~1AN z$SIiu(JZe*lSc)lLRVlaSViat$OFF}K7GD?%`>N$oU-zhGx7&7+DCrn&Q_?O=S*>g z_^vo?;uXHFTpOo4f=!1dE80f9@$$`Qemv>4{RS=`|M6?nHCW28>;*k5)2bXBp2F~f zX&_M`rchoueb~^+xr1*1dY}3C$F4Z_iY+@YzbM+5Fl{GP9PauewET==LmSUox#aEJ zcMX=D`{(a+K8l_H$G(Kw_p%8sJCg=dvhBp?%};-S@`EQIamn(A^0QPIDhFE@KAwAM zAfFM&6$Act#71LF8V0vjyF2KE6bF~8159d3e+|L_mY>;C1(#Cdf-o;+qG}<+CR%~Z2siD z+aI{-vAz^;4`F{p*sl-#!?IPQPn&jJ=@kzY|IZzTGr#R?0dyY$x#2e>vfb@>opRdb zi~Yap`0=wB%hf7Xz_#_Op!NI5JtM~MGRPNNG=H<--1&jCwOvK3Roh-;SH)YjkKFa0 zBQ}g)@XD4OT=eaNnoMp3CR9$j9vD3``{hvhzC&Bjy>;k{12ee|dTy5vJF;cQwm*h% zxwEEV*+YjPoyn~#yb7oR%<=rayX3i3&bjg4DfeExI{N80>s`Gc-&K)nEH;0C+C6Li zWqXy!?i+Id_G4doql+7z&}9St7Oz}f`PH45?sxOYxA?~FK6sI)F_l;HR5`I^`JkKU zuOIFl{=>0XZ2a`!RIW#QZj?U7uLj)agx)%T*wA|pU%cT!XZI~Rz5pY^DA0vv(Ci6` zLqD!Z*2o};U2p;Z`?F*>0x9moW-5E>DBWgN0`ss@#gB#yyLHcA^i8r@fS z8&GbGT9^AVef40rCDilu8__V^RDzG^SwOT#J8%n<2Y#wt&Aa-kv)Jc}NzZDi zK9C*;pGlKtLI))XC@RPSML$=Y~J3Hl>zPo2DvdUNo5p~rk3F~7{dLBs zd3W3@99xpkJI{)HLf?jh?y4v%D_cKpT=q3zxV^%SgzlX zIqUBgOslH?*-kc+J_Nsuo@3g6sF8N>;R{8 z8;=Guxzk;V!ji83!L0}90nkyIP7~bAwHia1C*qDE&T{G6b+KYw3+KcO`^{caCZ-Mb z5_-Z+XUF|&fK66bE7p>45vWOgU&vYsU~DcnDSrsgYDc8XytRKhWFllXTx=>BEL z^ek`R3TNpHO-R|vp$(W9L=0}M#gm(-D4aQR1WL=9>CJNCuLL=EK0o5`g*@+3+WQ6x zhZ+nSeI@N{K9d=joSMSUJuF?{i_d!!(h_4*Q}`1&qjy?bN?HaRAST)|Xbb6&Bi-?k z0Ev(Uy`VSrfxeIo{opX@5B77(!!ZwlfiMVCAQjRe9Wo#j27~_k9fHh)Bj89F3d3MH zjDV34x|3i&IqQKiDOi7CFgHG_lMlfA;8h!tU%)v73@+3p;gD?NH;a>a~5voPk;GrU*(w z<2=rtL-p71N6)3Uz1lWB2|F8>uGz6w*VUHXyZ4H*T5Uc6Lm~XS?EPV=U8$|gUTw`j z9?IT^t83lB@6TSd?N;vPSMe!4+eZCGePU)(PeEX+$w>ppU z(me;MdZ2Pt{wa+CQ2yDzRrbm&<*WKa<%j%Ox5{IgWhbmV<*lx%zSJaM_1iUxa}4fO zZ&imCuj+%!Bmj}d%9_MI48Ka3!W8~AC_Y_NdTd-6L$5n@t+Ki*ztS;MKG!DB0AblQ zj=($|^w(oe!%|s@v7oZ_mz~Y00QPke$7sT_^(wp_jpUm0O?69BeUhz1lCrNVO;%SH zKnUAymv4{$mJNh`8v*l?hXIBvZ<8@+*JJO4CHI>R`f2YPAK0+fQW5`GIa>&aPS`}4 zFuYxa-R+j`KEkBEs(Pb*wRu&F*R8cbL!YlUsgM`fdU)CUGYeUGl& za+7;&4q%o(woR$7sBTE#0LY!%sKVC0f^`?bZ0kxF%yKW}*0#M-m~C7dAN+p*EuDsN zGo_Ua=@*=Or+iU<`O|FEh4oJ3xcVEVLHVV269DyFRkabhl{ zm6y^j;F)b%NLs(vy+8b+T$i3UOqGk=`^Q<`OY>bApNveKt2^)MWXMi-nhK)(c{UGY zE(G`cen->1y^pNruJ@Ax;u{U>Yf`|z_py3*!OrSs>x=A^<^WjTmAA?R)l1bGoBy$x z1F*Ysux^glUGR2f-uXUJVJkn?*VREh%01^^+n=uRaZcs(e@KfS$rOiEbWRBcoZYIO zELGkpKkYq!0JHL3X|wsO^8omDRDP>n=srhMZX}g2>QhvY0^s*!!>g>kdPMLeJ;L)F zD<^D?N&fJa2I-*msQ$}e4k$k96e)Z=?kPQrOKxSSaAc0;XSU}?*X1XY8^y0MB-JlU z51AFV%2(I&K{|E^9Ra9|H2stM{nzvFpUjJYtN#7n^{1-v>!SRt3O~I3s&e~J<^R9g zw(BbH+LT*e#a&n7{2k?O>z99A(b!28noKyd;{$JyBr2CT-vDa86KkDZzb7$j< z!HvST<3?rfBi$cFihnp^RTbt?u1kMAE-O6&h;&b2?{}n|#xwb|`C^|TZJevEx`HihM9q%@cV_@gp)})D!2cuK9z-|`z9Oy49o$r_Y;a!`JwA-E8%r5 zfZguqOJ#bfE-7BcrFd-FDqiUj@TAH+()%Y}m%geOLSZTYZU3Th6^{IuKmh(p-EG~r zbYIp zVV)d}%mCR3AUuD=+pv8Oz^R$VUi3Wz?k^t#?{x28T|7#s>Xq$>R8Ib|gHG7`-97zP z|COiycJ2>%suQk$nfeFP!`7upX|?@uEbc7Uj+8Ugs+*VX-2;3uQR2+>Vw#nvI(BD% zzO&X{Uhd&UI4hQd$<3RUr={(jNtW(Je(SoKb#)Zmh1x|C`2B-3R|pvx2! zI9c8@^+;$hg!GiY0q6osuguCD<-1UNZ9lE{qCP^`?YL20T?pV-&o^@Ck3(T9ya4#G zb?00<+5Gc9aB&{a+3%h8LwNUD#{Lp!IDcw|&RHL5)Tj}o0%4YJy^U0dZ1~h3%4arz zKL%7XdIjw4IT2P?PBqFe6#UUai3B$6b!-@NPU+^QR}T&{=@3V4l^d8{xsz9KnkxUpQgM}U>MPVnB8{!K53~8G zx^4+z_Lrf;sA)Tl)Tb#t)eT+qhwE>9{%e)h4JV6zb%S?hydh)-C|(gV&9>0{7k>)d zN%G3cX^5klMq1`ITDrM1=ar#ja008$JEd1VgMX)IvuS3abC)Td3D5h3UsE|Ui*u%F zVc34QI|waNqpmCBI~m=-1i+tum5a(K50t;QEFwKy$V~yrPXMeN8?N$KVcYBey!G2p zz)hfz-KxB-?lrl$RQ!H@d-(KGSUUeZuLlU<>YL`ZH@%{ChBNvQ`(DxJx$;u&MTg43 z-mmF?Rc%9Kki9OqL8y!SRf2Ku4OFRAPc%L$KE+|}6qk)#?Kc2fAU&;n>8G@)js-w% zt*X93;n@7K-!t*ir`t`rGir_djhVYduzz;`t`{oiJbRlVJ8dv#fbYv1=L5r%U7r0a z`+fuUoOGz1lzyc}d7v~(+A>fc$Q*#tpgdE%NrwRV^TfXQ$J&g9zaw0)`*Gg6A4(iC zpt(?8KxJg_19k3nBuKS|0H{nX82L7*xVWsy`xr_K5ycwk#kzY#?Tn(%UIuI|(0zozM!b3i)CEFJuMak|sFeKr`5 zz0I!xX88|-@=x*UsKvLzJ1rMq$!hT?w{Mi0-GC)Z+OR|kjD|5Vd~|7Ib~X#0WM`L5 z=Tk8j)h_i`FAf%GjdXT$P+iNLS(G0fJ~}%-i6y)>F*Z9ln9o$%tkLn=85vwi_F^t9 zcC9ipoVwS9csFJ?u<`8Tedh?|$(a^>-}fz=YYOmcPi{ayb!0s*j@>89OoVI*LIIS) zTv!B`!_AQ2KFVAS6Clts%JhXXPz2|J-)<@A>!5yvMzzTQ60ND7Q#@sQ9{X@jDWZ2{ zFD|~X61NfDAiFo8+#F$ z^Qc`X;dvkaWtEK6;&*)b$NlHjoP0Lj^WQ2t>zvt5(R3kXPUzdaZ)$Q{dQ3v!l#H09 zjFdjCi_?cMB-0a<<5CkdlKZ6fRVi|e=^tgrQ^_X4(J&E?0j-O3Eb=r)tJ6ViXHMe$ zIMCiH_T$JmF~0?G!#l76-i7zzebAbv+4wn&Fpfu_jXVd=1vT98fkTb9)I(-<+-ksT zgm#eCbv4oe$cQHao3ej1%w@dJdnAr^h>Z?VK@`B zwbEUv)SbrMm-2jdX{{!uRM$zRxb4ULG(h_`1X$ECG^gGj|76VueV$YA$t~Pzg>{8f z?|#R<-S>y;%CJS@Ul?f{Z@R| z?d0*UG3Z;3N zhw2rntrJD~two*KsqIv@A58urcYr?ueP%#D%!FzN_>o6Ct1hc9k8}1ji8#h5?mEWH z${DV?!E8a?RSojHMBFHdIo+VHUWxJ(wCKbJQI+qf6HTi6HRwU?Li`wSg&P?^*l(@Z+x8-O_;Y?O^W54sl zg4{f3vW0X1qWcGLi-L-l=`QxI#T496OxbTti+NPcZt|LwIVjDo1GCA)IWQMasy;uR zh1pdDG*o4Aa~d&6tGGASgVE~Ls`GP~Nd6Bw>ddxq?hcvoQc^T4HpA_@vvRXavO)_3 z?{qunI1JX*A+JVj5F)Et^trT+5rZ=OnUUul1GuZ80F2&zsxC}0bYQ{wS{$#hi6vZDP&Z_!o8@L9PvFND# zcmIn3>22-xe4%F$J*Vgo{~+i&MaM{}?Ak=G$*p}h!RSobkbA|aBNOa(J+J88h9|#c zKr+$`QN^ui9{c=JidkvU5gzU^>=drjBCH?#B4z}3!$Fft!k>?-(jg7^(m^O4$`>29 z;*(A`9eOTP+&b?DT|m!<-9blfM@j7)#a~T3cP`re%#~d8l+tXyHi(_$8(71m z3B>t^Whoaa0RPAwDS%9FmU&0Pil5VhrGXrGIZXa~&dYTb2u+~%@`T;>LYr@?x2@1G zXwK3*u zxCX9;>p=bC^~fc#6mEbU;U?IfwSJf3{${uZ)F0l8ybW%LJ0J%YyCce`9W9?55dFm2s{dp!Q=1*JPA+1)35?o!ZYwJ?39moAz%N#3!(LX zN3*ts`~QsJnf^qGwBWQ|$~UsFo$dzgAA7#wCo`SExp@e8SaE?*8gw(`EbJ39rPm9@{UW>s>)>UbQ!m$154ejnuW;@kr+24bYVW5Q z!XV=-9j7DJ%lq+{jGy(yEh$tlC11tP*2~v0zYcFe`{q1nA>RV)NAhjXJK{G0o!}kL zH-O!bOHFh-`S31w@4@@95#qJ?sBQLgM0rHxPWK7kJ-(CkDZ1(0^-gW1-+u<)^?qjn z{uNFSdoz!#hw4_LAFbnm)C7wrLhhI{Om!k{I0y(+tJsrpi6tDqTu%K+F z+ugI8Z>h7ptvke&73Fd_%xiGxoq8Z^1dp7#?)zzd-I2xue!TQjH&oG1GUGCP#b>6* zB=$Aq$KgmORp)pPOPP#ABJ(KP+bp$|v;tCz}U19no?K-jgv+Y>W zeUCFe-GX|gwC~Z2FKAqdNSiiczEd`}@3i$Ji(~x^Ar4$)mIyc|hOg3!e z`J|odsar)=S-mRiR96LO=8uUoKT>agf}i0R_!U%7e?$HbcHj9wF#id=yYIXd4LF)M zbMD)9FRkZ;QV4+l*p%!H$`j?8@$*AqpBm zLudqzp$RmFX3!j3z#gzCw1jAA1$#kjXaj9wZ`cRg!M?B`><{gs0~`PyApo7AGaLv9 zK^Hg}xYk z5Tt-jV=Cq}NQVr_guyTbvfv0f5{AMs7!D&~Bpd~!U^I+@u`mwC!vr`QCPGcpcnt22 zg<7@)y|~g}6FWa&PG$596!1vNGDu}irsW-MaVgt@Im>{#A1bIXRzIh{N^Mq$`Y|20 zUsE5g{!e|O`bD8OE;}8%9?5R9bJe}BICRKu8Q8dV?jIL8b==5roK868M*jWhHVkVQ z9)|ZGdeoRqbp_5osP2sc;RHa(F&;8c@{rl~_kT z{OcnT9rl{+{QaujDIaWJ`Q6$44{%*(^|gnAjaO1S`Ss+~xgKsGz~0&^92+)9$J}2A z4m;k-EcZ5Tp&>Q3jO`qPN}_Y$n@(Ge*Gnw*`J>7TLYEnDkLQ~DD}R2-Uv+s9UJtDw zE2Y0RYwWcj;3ytl3xe`hhpwxP?fWI=v+9|~HM!H_uLHKe+i>LHh9#*sWb3?@w%*(N zijz?NS3I`7ZF~w_VM$;4weRvUI{GUd=_L0~M*Hk-oHksA74F{JS+`1qbWqwQZF%Ur zt#`6hd^Y{cFKZ`z#izsiQJgkC3SVWaLuMPVO{elq_DZMyzC!6%96Go88_7MFojj2n zn`TKHx8hY;I#*h({?boz>X2E7%_p0uI=AlS_e9T)@<{R7IOM*vYc@`sFO|7d7>cVp zce*aU6t0jCHh*MR+7*uSRQaX+)1hm6ZyV{F+Ofjy;$3$aZg<}qE3G!)C6#B=Re7p9 zsJLT5@hVQmp}4G_%`2Vjy8ZS<{-wX{Y?`e<=}~45vB}NH6D08|R}sH?i(J2Cq+b|H)Z~KOL)c zqRb5ZyyDzeEAf76!oH|>~WcB^t%S!5oO^9{Y{Vq&nkF#IZO2~CJ$a#puu zzQbTcCGVMjRt{_Zm2@}GK_RqrJOycelg>!TCalH<5uE&i+kKTALZ)iT9yL{{YYCe(Nc-c$uNk ziu$`Qn(-*g|7(Frhx3JBfOq=&rQT(}i!5a$z%IRV1A#6HiP|<5LsD)8E9~RrVw1FA zo!(HX^oJHY1DD<~EvMX_ofl<4gr!te85m{PVXr#j`H)O0Ac@YRil$ee#6G8UP81#q3L5IERa8AgF4d`F`Rju!?sjTje zbxJ7@dqt)CZ^M1C3*<15q#$P+FQagz?Gu?O<}OU>e!|J8MiKmkyY<3|b@b(DOm37} zNZL*X+YgK{&MER5eZRn{ATOphaSv4r1G#Lx6l->J;4agwjkSukao1E@GAVv4v~qeU?hMKA z6!h0o57ejWP#r8RE-Q&;1Fyp3Z0-0~Rzg!`_cz~$jspU5yne25n{z@{Zv8=p_APc} zDay|&a5oh!v^%mjGj|2ZQ*_x6c?rrLfIJui&Q zcnQ|Q%kT=Uhgab>cpct=H{mUK8{UBp@GiUu@54s;06v6Guo*srkKq&e6h4E`;S1OT zUjj|ld=1~gx9}Zog>CRX`~W|~Pw+GR0>8p<@H_kgf5LXy0e?X`*z_u`N~h8y-KD4W z+6>Z7dPzU&CcUJSbhCO%C+TMOl1|b`dPyhgBfX@D^s%}G0&W@F^1qd9ow0iusr*qn z-wvunDqGcu<2$?Mqr5!;^Zm$NWDrz$RQ~lbw}O_iGK#k;NYx?LgM$f6`K!E99k>Bh zulB=@+Q1NGBT#))ojEw**3*@kSAoh`?Lc+8b-=CX?U5az9qbFr`xm>od8~S#j@@05 z4E>-U8BiZOVOBle4|xE@!LyvJzC8n~bE<>>I;T2l>smR9P~EfjOLZ-t_|$II_S8l; zAm4@e;8D&$K)w%aK{}m^-T&Y;I31MUGm&S(A~+k)fpg(JI3F&63!%F9etP8jg?f9E7Z3#vpdmDZ#?S0SdgP;o>3|*lcbcaLWQ0M_M5DPsa4&osJ5+MnCL2u{-eIXh8 z!84TAv#<(Q!$9l?K?@!uc}J8z65%-U@2(YX8S!r}n6JdNoXWZS2(^FpA8J>R zLPPAc;R(!#BUS$gAm_svcn14tp+08yhsnrBuoiO*as z_J#dme`pUK-~i|d0q6vs;XpVDy1>EE6}mxpI0O!b9uNbu&=cYy9ugoClAss#hCa|2 zlA#|Q2L0i17ytud5TrmVq(M4lKqd@^A&><}0FOm_1Tw>5IE(<5@2TXA%0JTmpKbr@ z|5g6A>HpRK)&HyfBlZ7hL^0mLO}JSGH-jyI+x~z5ZP)T|OL(^YAHdIpa5~}m%YPB) zXTv$5@;?vx6!HS(g>VsE441&Auoy0b%i#*R60U-);TpIWu7m4g2`q&h;1Be>3Aqez zhFjoPxD9THJK#>Z3+{${;9j^7?uQ59L0AqC!Nc$fJPMD&;BO8{~Ivto=pA!`=I_m4nG>t)c z{|f^G0U`~W|~PjEhkZ~uM89suK;S=~2K7-HU3)li*!dI}E_%4Gz1B~5pIcAmrDUB-c|84u9 zi<@NJs{Nmd`AS#>_d-?U|Lt5?|9>%3{lA`>S|Im;$FW;#I7iy@udM%n8aMx!^4}l- z;pN{EyJw&ibcO@rAm{=ILs#eq-Qf^86na1m#6nMqgLp`QL`Z^O&>K`|`yl&5GW3H% zkOHZY2Agq{j#T^4KxV>V7y^;ne-`#ffbRd){*S~w6o$cY_zAla$dRBre-!d}r668|YPCfLO{|?S? z!Twg*0J{GhN4*Iz|25cG*Zv90dH*vW|Ka8TIoI^85eo)%&%YYubD;bEy};i8>se=S z=nTE#5I79i_lJqUWbgnDuPZ6Y7EP`ECEN`~B8n z@Bj6zv=1Bzec({&4>_Rw|4dN-KMd*b|4TU6bI~y{162Nc?l}c?|L-sVORx(sf7||T z|F8CcK4I+c_;34vwg2nz|6d&c?em|?Uv+COJP$9xi|`T{?wejlyaMY%&wm>K*=;a1 z{x8L@s`39`u1Dcs<-Zv|f{!5zdt3ghn~~aoJ^X#kHQWC6{HO8%KtaCpQYmy0iX+*eOg&g=@pBiCdgII^-dzfYnQWZ8*{~(sisue)BO4xs&_Kbh2Sp z=bj)OeH4y#QTnP2Tl&|;Jgq{N-4m4aRfh6Qg=iaS}?i+DCPHVDqOcw+d6&6?awcZ2hi@TZL)s zuD_iq4%JWD*?hF&*3>;-Lt!gl!~M%nM^$l!Uz7W(pn72IowZZB3Dpf9b3ys8wy!#7 z;}5sz(oT9RT>igWs>hQrc`d%D+h0 zu@23ya2A)WY*50T^i&?G-2MH1xILF0H;Pa7Lur-S??+)ox{h_Ik5m50ozfy~T5P`- zejO*FGE`i)y!?J`8HZn&Uxjb;P4!T2{PnxC>o{}Dva&Q;y{*2LrCYiQ9V$C(wo?70 zHD`Nf|GD3fy^b?ywreE6Di_r+TgMcZ(y#KARC=uLHoThn!J8AO^iepZs10+|(rBY`isbYvZnoTdSjOo8f8n+uLx% z%SB;Sm%mp3>fG5ls&l6}he1{C!1gn8I}vKqU&yV(wdwYUaU}LOKK0@LeDJ%gsb1>4 zbktNg>pqfx_F7GJv~H!pO;4nLI=mg#)VcXw5dxb3>s?sm~n*8|l;>T*jsEJ#J>CX#) zz1zYyjpdQbG*UeccdxL+^F#g=U$|RaE;V(pu&W9u(lz<<=ZWnzD$}JVZmk}X^21+# zx?WSa(o08Wc@Zhxnz*&$DsL(aV>;JtT5SH*)~!E28>VD<{Hn*ZK}UExH2#FUuc^D; z4cCr!;qm#yiBu>2`QvwcB8uv{#HRhH2x(Csm4J?#x|dt&sBo2E&OL&6uCYXRDn}i1 zBRd<1J=e9)`r**K0JU=+U0vZkqxzqRls7upVdIwB|Ey4zF3KaTLrwjJhp)aVQW&z= zp>S>bY#xNi8-6|9pFe!-*1C!G&O~v_UgaokK3aSKx$;oYgDNk7-pXzGHLRTU*nIcv zW$mmRTOP`X@brdX3-_aQJ*(=FwDHKSJk%kxEqj@zzkRO7xXbgf4Z})(ccH_d7vbhe z?;4aZa_7IMaQyMxIQ(|jjikcxUsIV%cYm9x$_-9;3B$%2?$7U5=J0qw!cOt3{B%@y zUDr}U^-1}yGL=7-rF7o}I>PCoJnjmozR0ii zxA~RiI8dNW=ABA0+8!qh< zMrHowR(WURugultk=)s^2yU0SY@1S9D;&kCx}_sLtjgTja*^IPJnKLFnzi@mM^*ak zS|mS;t1^B3VJrSf?wG3;TKXOHh{-1~ebV&p6~|h5EchSSD=s4@K0Y@oCN(oNB_=K| zKCW+KTzo=8W}Ksr(JJxhw&hz@I3G6d!S@zO%`N<2vz=G|x2uNk?#llc65d&awFvwx z|4Z>edQNE$lL%E_Hjiyyv4U1{fCZ*p3h#;;cx*-XKgq%neMQt1w5T z9+8nYCObVNZD`7 zCxtC0l9ueJ6|$?4SHteMk8ALAE$q@hT&0~F=Cp%Y zcH&dPxodheDby|4**V^v*IBInnKPM9?q*M?rWHDUK|oivc2aD2Q|1Nedh;5N3ew%u zWS3^4`cWFBak$4I^5Q!5ydIXoQn&$bgqvU)+zj(Q4@}$lTC203s%Q4tx8 zw2Xwl=@|*hF_}qey<(E4=H$er^omP~NlQ#oElTa3n2_wLV78#cEyQsv+y=M99bo&b zJ2Bq{cf&n!FYIo4+=rk0VVCki35|#9pVT)8z}~;i@yxR823OCbUwNFx7Ong$W zzKNM}8R>me)79%@p=%Y=`W zwDTS>vsz=adXez{!gaF|HyU^1wIX(S%ar<>I6^tgjkAGAm>X~V2*=$)xLXvcCtv1y z%u8G06>fwl=wqHFT}ty)NUf}C=gHT0EOS=Wv1uN{=Al7K6n&Qyx07y7eh(`(U4PS` zo=Qqr?tb#ZA(Si@Pb@BA0lrfRj{-)uV7k25T^kM&?jI_SJ5)(4wGZGWhcM2+UFsYfe*t9s-G)PU;qU8zfJCvN78q+5&txqpj zIOv-Y=iYo=dlTy;P%h8G8qk=q7OC=j9_in8NaghccDq|gU&PN#VB7LK%-VG*L3OY4 z9CpfdKKiK+>KMsno0swX3akg|`YN(6b$yNSBI%l_I#q-6Q~l|Np2}C}U~>-j@^kM*Q=ukii}-Ty9K zRmikyEYX;eKJ9nKwsw!{ahWjm|={P!4zBx=(iYe=96v8QkK@CEAt4 z@iSe0;kdlu?4rWro!7Wc@7+5?-ET}rN@B7ezu0swle^@d9=~d%trwZAt}k}h?4Ha$ zR$guzt0fiZ=mBRc%fLJLex=1cW$4B~k4J^+g=Oq_&6@TV8_C%egu=*VnUGQz{H<{y z_DN`#ryh%RI$E&mi8H|X9&kcVrh1W7ba4uWk z1g7Q}vJy-YtL{77s|SiGu_B&}wW*&X&MpY%6!&yYQ*(;lRgg-Ym3i~oPP&*Cd;-Hp z2S!lfgNOPHIV?7tZf8~dD%?#joW@!!ekE8dze0`O(V&@OSuH){J5Rz!t30!OFi)et z&_j=t&5TFk$oBO6=9WA+3% z8YaS+oMJtHI%`#N%3?P<&E=Lpr(g=(tWRUHTDO>e8hAEZdPXYb2d9?yEOwW?@fN5K zxbZtK(6O+n=j6iDQl5V83f4P2Pzc2Cgn@M3N}WRKtCB`g%>3p!wII;VSyZQ|yV6c~ z&rwguiF7V0n>LMoT6sEVft$RUv;;R0EtXT+RoA!cVkFhpmtdWGX>nE68wpQa8Lh%H zJ37nY#we4k76$j&z@*-H8FSx*_d)l#8<85*KR|v6_8xZ==FPCX_e>w*=VP#IL4AU` zsw??wR_sk&c^G_ zG_a-S7337p^&a%xy#TdIQgM)XV!5ehQ>O-tsXkMRrv!q^8&$I~FXM|@m6u1Id@WEP z6F7-&a9y+N9DBh;@G^?GBv}O&KJq-X^${PN5S!=~Sb)j&pVBTqgU{g$*aE|Qny&)neU?3l>1W?_Gw3NBHL-=K1V6?OXBzn zzJ_n$Tkzscj0?qiR9OxS{GH_V=)Qmz2w@H(9EIs^i&a?Mf6Rb>{k*eaH`QBeVnO#n z*MhE$zf*$2T;3<<%yIg{lCmO=%TznLb9Tk3i*Te`E{hXHKMV1v}fZwBxj(OLM-D^&50;PW&xk57-l0LNv63y`VL;fwr(W>;vs!U)Z(mY>Vqtc}hyO`9*Od zcW2Z>4%*%0=uuZ?q#)O^fX9C5w?DK8l|cvam%#y?+cM~gIRKrYtR$E{nFXUu*{3Nx z$M#h$r|EXG+zHy^N=&Rsq+Xaz3l>KzHPW{PeN->%qTICGj?%c4g-JT2?}2a-bb*7R zD|CbIa0naK=|7gYXGFPfE?=WL^%kQ6C&y^_l!F!yRGpSV9M2o{ggyZHg!qkplVGwMq=n ziP~yZk z;U^vvK=CC)r1+As?*(4n@6|g}#iC2%{xV1Z{0(Cr+`RurTX4!{42uuheEx}a_a-fU zpf5Q2jO+)8L4UAq{&36#z_$5_t&$~D)3nQ7iJrxrM(Exi{PXSJN$XJ3XVbcw#g+!5 z{~$;Kr8O1OARRKmrZp4uV6bUTh*ZYjMoW29hI7xJlW!Z?Ki}|06?e1VxT!(%29&yHsC`Sai?Vz93A6 zX)qmZ`tmTJ05*O9>KG6yb(CFy(qq#$h{XbDpl?3RgaRl8tN(wX&ySFbT50$9bn_9k z)ARAw&$#{icJjKf7G&g*R9+W>`V!@}?bo++P5sP?Pz>fo&mE-_lRjZ1%(CLnvoZ9?i19`52*j-^XY#|+bW-1>q66t zx|>CPu+NLvFd^qr&Q*^#Q;$~5%(0n;QO~9xoCD{A>dbi%sm`2_{RQCFng8P$T^DH$ zy~Wt2_1Ewnhm#*|$OD@npR!2og{1K!xEPclm%yd47%l_bhAzi^1=u#U`{Ve3Cl3xH z-8K)lut@Her0XiU8k7guz_oB4Tn{!6mSA2AHV<}ptpAVXfKv~;k#3s@OIaZJ2GVsS z+yu+uX0Yk3-95p7BDJ+jLg>cfKam9Yx$zAxd!O=-VQcH{)1y+ggYxw+v;D-{v#z7K|!ZXE@5$PJKlUs*(7P9ZH{yn+rEQ5zZ32P zmCxM}seJCi{$B7>{QrgVCSrD|j-F2XZ65SzvGMyz*ZuGSC=VWl*#-UjHxu;izsiK?zr_)<}uRpI6MJL_ml7xJPj+rrh6skXTYZWKRK>MN;PG@kn*)@ zUiWI0d6smng4OUGtO1*@|L`~xA$7IV`kx&-B518=(LE^#wZjS2!PPQzoc@|SR;;Bw zo(I*(7a&q!q_N^f@N%FA&zp78{Z`z|N29CLpQ3mF*Q3l!=&}x8hWgL}8o(>C9$p3i zGiQMF*I?J@*qv;u{f|GfZ^gsvQNHg5B0aJyznh|KjB3A{mFpjy06 z9=!o?M$9X(^!x1mT)roM7x#6;DZ=~~e%}VqHB%PczjB<0z#|KjV_b_7&TPRFw}CkS zxjNq!^d}5`pO~9doSPkFM&4xpN0`AR4*TFgE4Y(&P@z?P{1EY5h(T|Q~#UYHx9u2`SeWx#bA8%k zR4)n(_y))yis!&l)AQ|=bpH)e=4bT(1%8F!;CJ`~{)FwY1O9??u!Z6L2fk}zWX&wf z*Ebr8@%q2fcb#uM^lg9WPgCEA8i~Ea+Q=9U3QwA;$E#>%1uMi92QBrm(*Mq9n0Q}G7Sl{5j2J-VC#d^S%fN`JN8fU zvbOKAoH-RQ-qM59`1UW9Gs^s(@5Oc_BNCkarViO0@8x{JUlNL6waXbR7ofZH={NG` zTFmyFti|s~nWm(p88r8$=kL?G+B#C}-0(tn(i=~@l-}n_>v=MBNVXu2dw6M;-O!Cu zW>3yrLNv638kA{#W$mce%7=)-E;!J3`5^-=&Srnx#)?O&A(`(p}8Gu?hCq|&|hc!VGh5Ykp2GH z>vlrs_Lw`s0ibhHQd(FX%q}RKIXPIIT{zXfPYUK{>w~lsn-THu0Oa1U(&|v@53+5@ zX155JdHQrDTWj1|b`DdM^?@*bq0<+kLks-as;&G{Idz9aK>2O+ zm|PM*}&ZNN`n{`TP<==Mt8XG^TOX1zTYYPHiHoL$XQ7`P($R zS9(akmD1r&N$Ddw?Bey>sLjz!^5&HNmPtxK$v@g%d!eNClx+7?!;>YYujI^U_Rg1- z-ja|1@5*B(rN3lJv*WaJnUfC5)RYu$kmjUE^1=0;w4s`lF3BZ(eX0%Fob*Y4Ft|t? zxjE^S{Oz;*+dAi@S8{usE40C!lWxg|DbWoibx8hxYRBK4na$*bGIgm(Gz?eoFrD)KgEDRK7a$`DJq?mA{hDG>k2hR6a}Qox1mQN#(a>qrE2_ zFR6T&YQRy^2gyb!>^MSF^-6O5nT6?+Di_K1FLfRusrn|Fd}`~y zk}4<3o00}3NU9!6zPo%$4@s4qcmydrGRDC2x(M-b_;UTr%#1gBnSy+$CEzX;5EMhh$(|%jil;@cPC8>HOdG({OKP9R9B>Ca0w;z#Iy^?I48})#s>X+o*^~T@j z*i+9W3-){U7D?4N$twrWyg^d+PI7CTm}@0f|0GWxvF8<%s)v%-FK>B?r0S#Oo9*Jx zmsGu!Y@S`PNK*Av@~+u0oF=JyD*0OWpaqhuuaXxAUO&k>r`}4YzjE>{N!4G;pU+Ay zmQ+2Kd}vn30!h_p$-Q50eS)Owwd5^p4wx#b`Yn0ltpg`Zs-8<;@I={hlB(~L_m5jS zQBw6@a?KkDjF(jXm%M24MWZFv4kVAN*LI|&+Jhq(-8ocJ?LxBu^`o;S)jlK_KG`Bu zQtd?YxXEv)N~*m`Zf||xKuNV5$srT3>MyDGBl+yJ7bi=q9Z3$@bY*WzwI|7kGww;0 zRJ)RFbJ1&Yl4@U)4UerKE2(xS`PvbK4wY1Ulk8sa+-{O;calxJe%wV;?N9QYX&DDf zsvSzMEm|IsRC|zOV z7z~FIFcOY}Q4lJhisyq+N%_;Imy0V2Q*}+@&;O2(^zlCiR4!v-9Qfr^@Rr^r)-6R=Se5Mww&La}pc}{(Q^kT={l9Do$cQ~ z5%gczYL^dcYpYSk>*h)(g4%%Xe<{^W1PymiS5JLOrjAU1#O)s&V$KEG-@lDNNj>xa znCE-uIn=L3GIL0(ew_&Fw^hF+E303+rbA)LU-54G+xCO&uLOUkp!mw5viN3k&DLk< z&!~!j2i%FxyUysenN<1QKgUJgS8CWNtfK6*nKC=54Q|k1S+kI*6@f#ohqW@KJHCzMNf=y5DKCauf_tzo| zPP*&98>*G6p)czGQB~dNl-a+!mwAFlSfIU$>z(Ge@2;G(fKyG9hAO1 zAX577#QrYu()V8)-ym%?eC|!@i~ ze?^%U=)4l10j2L*SOu%$Ik4$lgLy63^!=-2ccj!&Cm*G}Y}q|o9%Y_K-xuITcnQ{l z)&D;*R!2xht+e}lhUW;{>0Qp=kt*Dr(mcjdM1=jZSuSL4GgupU&$ zUWG{gpvL3Zz{`b7K6siPR>}y={K15-sW|91BNMncoY2ZZQkPiZFmPZ zfTv>>lc_yD6)SPBBp$sZ((zmU2IgJDcn{u(jqm|{2%BItd;}lEC-5oQnz6g>E-5*@ za}A$5?dZAlo#x*?TiRcQ|F(ivzR#(pa;j>S`Kgq%eaEu7UIVk4bJ@pX{zhhw#SI#m z&nVx|;S1OTUxF>~+VxYl)SFKx>tqeLq}|dw)PMb3TB~1sJIXDa0P;M_L`VBZ4a`@R z#n6UI>)ontp+&O(!PM1C(Z9V}L@Wlig>RMb^i%1`E6IBuLz*;j zCOZE{zW)wt3xB|$upM@QZ3};4E(crDA}^Lw2ZMmu&wh%IPTz{2^P8b>JN{aR`Va*T zpdmDZ#?S~8&Fus@Ib zTqH9`QVYJOr5@}7dqPWy23s#`*T2+7D*ojG`9F~a_dVdW=5Dzyqs)3xUOG1H(ZIB# zoc4m&ptjxy+QQzj57@Te4)eZX%k)3l)+6-DwhxWCDz3i^{YT0wf`Tdt+EX{m!nUnB zl+QZOb(FSjVD=;5_lNe-0S*9L4*%h{6(O;;(mFJd|64_*G5z5^-S%UU`IL!{9?|ZY z-jVVMfZ9(d=nMzKL15cY7t9BPt&i1@>2;CXif8sm(b=hA=zSl0cSVieE`yrV9 z&+LbCKAHQFDbNFR48($$hHB@Qd(A8K-e5lQ>KT4)LFl_5cS{MLo19}G6{M?Z|DPk zAz5(UOQ$=h#-AA4Yc64?@9oxuEtvb^=P*$C{o!!%hd+SxfiMVCAQfy~aQaf;Bu#%* z>Kj%QzS5whMe7D84ZrD-0huruhCmh^0Y}16u-^<7;b$1?_yMyi9NM?M@L8{(LX|y7+!g z<)}|T^~S6rG&U(u-WJ{b^t~6iaT`Mpg=ZVXWX%4`8r~SD_zF5~VwX*WlR>3~t%_P# z2~Y!5!_9>t*oHe5v%le1Ot)h9s*+$JUHhd3rnC2ordzp_p@#FhVUBl28=3j1VdXks zO{xp>rbmrsPnv1$R;CXZ+3`gC-@0y!1ALOmKFMsd>~u!9hl-_BbG8F!QLp-JD` z1g^q5ufT6zp|Nj*n1c19iJ+a0YL`i3p7w5c7@8~3Br&fo#QSC=rQ7dAoyODE>%gw{ z@_CvspQmHa3(IHQxOSP2RS~RH59Sb_?HspzVV~d&dj{tGO2YOjTk&aRSom*x;b&@7 zHs|Bq5^ZGSxvJs!rgc$CXBFnwszU;?*?4iRbP4sR2oE>l9h&~{Hj4H zPDWPlj!domVn~%LU8xjm5JuI`h@o$GRjO3yfa~$Ss|%rO7X*=*P+bsJyKpLF<(c8u z(g9}p=+eaO?1cCPfA!3o?yNANDwNH)-E3EsHQo82BstU&W#Qbh9?M|3WIy+YM}t_L zH{mIA!09Esgq&Uy9<*#iVgFY1u=Sy1e7s-lboU!wpW3nsg*|5f2ztlIRaEr&cwdvs znj(+7k+~0l!z*`}W8H12+8xPs!!zxx46oF>>sGZplIDgtvTAoHSa)-(c9(D6Ev?$! zOzZCXs@+Ys?zUC!F4wwi*P#J(cUre;yZ1ir_T6v)_8kuB80gfh^MMDo>T+<`Zru+# zv`4F$*q+hRanbI$6RCwFJDPKSi{n>ND!faf(aB>DM8xIV)pHW=iW!qo(d{cf{cA)%6rNNo_#^VpU zQ(oTjuktA66DD2L;pWPM-*w@OXUOG2<%zOe5pb^j>R#vf@CAoEX6IKY3%pCNH794e z;n^^CxudI}=dxO{#8J+cXLOY_j-UJr#ZQ0E=X zTb9>bDJKIvy4PD`j`DmHxGr%l_zQhyztE&Cd@j0qoNK+cd~3^)+w$@sUE38{xhCTH z#4!ytDNo@o?ZZOvihJGv>twl;>jMYMp5HqpWG*k?l)bUz`j+J#H+D2B7p#eH-l+aU zGLw^~oa}Y__MQVqK2`qX<}@#d>zS0dxiHd68VT~O7dTd;y ze1Tcu9dhNUN9tFujcz`EGiQHA4WQ20W5MPMbNR2EWj)Ymv%pcn?H`<|tUtdbla@Xo z-F%kcZYQ%>Gk3Dn$<8%)Af>8m;l{II+l8Dh1n*En7SgbN$6V?VkN-26dg@r|M{zIs%{jI8(7iU$72dBC>jLxJ=JFj2vftn3k{f_$J<#f> zuFW~5vLULJdtQoep0mkw7ZPM`c@zcdbnh!e*7P*nx3_n^ZbixR@<&K6fma0YIrWO% z+EZcgTNm9tldBzF7jDmrd4c1mqKXXE^_JxW6sR&f{2+U2i!VnvZ$6N2l3wXzsvI>4 z8(JqV&#Cr~Z|i-fd&XR~ZCgnbRfGd~Xtfz{UM9<{phhfwCAxVt1@0U)s-;-;x93W) z`e`pGNeNf#*|J>A75A|24=2ZX# zyK1uwI{%hJb7%o)fC(^WL4AmV2B4dVM$iPBLNn+F-60!H9B}m1T8ceOg85T>PR;E} z@A!Ij^N-qYRuOV+UZHZ(V~NnBA*cO*qe&lLV77N}YhD}Q)qIfjvzh$=JBp&=CiNYn#1RPZ+=Z~Y%Upfj`?%O zSEl3r*`~>)^G#gTI5Tg1rrCDq3=?>Az3Kha)#j7iJDB5gE;dc}|JcQsaX{$`rEgQ|zryXL>oweHB z(*AOje(win-|?@Rn+G&B4I7u6#8U>EXA`!VD@S~2o;W$d9DMjHbHI$PX31~InO+_H znFT+*XudzIgPDI(Cv(C<^UYfYf0($K*{1DbY389eYs{*BuQPYAe%ACDmO^t%am<12Rr8qnzS;g}-&Z!7CGq8E>H#O1 ztH$S;yZ#)^q5>OC=innIVawU(^A*RNt*c))`MsN(NdwY1M}IL6$4 z-vV>Mmp_;xN2HnPX>XW2KG*m><^UYo_%r*@U zZe#Y!yvD3PdbN4}xpXt^fs6Tj`bYC-+TEsm)@NqNThV+zzTF(Kc%3PF_bb;;7gu0^AdWR7Gr0c z;#)5=Grs6(;$OO)t$>a&DRVzIKW#eOY*~DT`S|nG%~kPNm_^^tFxlC?%@aHJG()al zZl*2EHzyXSn)4?mm?do=HBWxl)SMsR#r#^DY~mWWH+!_GXO3(#-Sqfwf*E|rMW*zN z`sS&(4>G@=e2Xa_!hJL;$O^bqmMG{oBdz{S0`8m(DnfjLL+W0y% zsliCI{IK`USF@foXYt8V){K))hpvyC%k#RMHS6v&cefd1F8cTcv*_KkO^@P{ropnO z&FsdD&5v3F0 zJz>_K{g{a!^ptu12WFC&bv0i<{EK)BYIX@@}Ze^-c~dH+)-xx`+dwkH{5A9 zwT)*A#to*$pYNI6VN=bw4IeSxgK=j3xD{q_c|UW)?~j?Xr++lvF5P7ATo`Ajp7)Ds z@KRgzN9RW7-A_B1!yjB`#_xZj>HTt3Gd%T6)B5RUCh@?B%(grBGb4hxnb)7W*)$*6 z(i}7AWpm)~v&_768!)S}*sQr_v?(n3*lhipnOH|mFst8t#N09UaWiZEdh=TS4(9&v zW6XJ1J!4kgJ=>f)W4GUMQb~nL*9Sb+|;EX z(@3s1caOf*JUqUE=^nG(9J%lm^Iqdy&8$22H1m$W!R)=)A!g$vG3JlYc9^r@|I3{H z^bYgstbEhx_U&fC!Xk4uUUT8h0(054WD~#Q z7<2kNLrfftp{xHjzi3hE)Sb| zXD6A4KmKJNTXDUae!>WI#38qt5vwPfnBSf>?@YYX{IvEmv&XRWO`zW`X4&{`(`MlP zW?|pU%(_55bK(bQnioEN*sM7IXEQ9=%G7H-!<={DUFLz6ubQLA4lxJ(xu?0Tc$K-S z(P3ugpkAiY#(8FQ#yT^+`yb}*7oRst#oIrc&huY1 zg%|fSO*TJg%HKKP6rAv}xo^iBb7_-j%=}5EX2#tQn3oH#GXsj}nKi9WFu$(9-E=S6 zWFC6qar1~d%6$C59;Wp1gUn|qA8$%GoMZNA+rymNu7x>e>S9x```%{BNwdt39Z#Ea z8C%V}jNRY7+uEG__+w`E728exZC%ax1*^>B=X#qBw~aH)Z+pnBU$>{(>+Bd)I6ler z7XlE-+kwZ-jN|Sw0}i>?w8~y*b~Hc2eDu?w=7Pg!nvc)yVZMCxbhCZ=?PlwD zubJ3hFPNUmIi_!?bhH1v&zn=XH#2`8af0dDeuEjk{B$$?s`Jd4&g;w}V>g&yqo9dD*xaE-Yn zaVs-j9yK`=t}!2tf6^Rv+8VQAK(zVb#33f8{RigWb~l(#Ju=PFKfYt$J?Tnw{g{AXagDZ{&*t7^&hFgX^u4>YnRe+$Gj_`z=6?tGH7|Z~jX7$MW#+2H`_1j|>}m2E z4l%(8wwVQsZZ(UBUSWQJt+(lP$%AIz|Bf(MuJ2?9E!k)`4jyYhD(z>ceVuClx^9y> z?#*m7Z`v~R_+2NNvA^7C4qbGhNqKy%IhrM=I{m-i&IU@V;>zPS!|*ZmC^HCVL^O>$ zqI_t7PxpcjEh9pf0176U%?6t3hH2^P9{Xb$kOTxo5KvU)OQIMxF-MY^4Z39gnAU-_ zuItfJIa&4y5fkHL)Py}^qANM>@7Am8=|Pel&wf0(UH^CAty{OMZq=)LujbX5wJLJW zYPJ5?@2KD0e^5>Pbdvh)YwxO&Ltaz|Zn#}-`LD6+_2&+#AqQSjZ+zp&>Y4*<)vc2k zs8MStsBufTsM^?VYRvST)yi=r)a0(AYTd<~Rp-R@>e2mgtKYnFv)cbR&#Q}1{8s&O z5k7Etmr6V|R$Xz=Pt`NaUFzX$53A8fT2%6_BWmZCbJU}k-m6+)zC=}juvaxd;8ruX zzo^<*wW#kbzfcYNxljGzqdU~Ful(6O3sx-}$O~(rpAGE2+re$i?z8|W^zHh22qyLUKz8|R9)Dm^_#PzD;hOerlH!)xD zK341c`$?aU`a}fI(2cD**rzhe2nS57ivQAL09ZmsL4mCp6*NhU1Ld@9y|(VinVUekSl zxaBoh?)T>WlcrxKF27*K*kuz3esh@r;_u%w@tJ=;zHHsVc5{B0*vE0)4ADO}A;a^Y zS0=yrr|DO!=wrXFb=6Gjx9?X?*(EV06g>>9GFz{rtKX}2_19Al9y5Y(7(dqSc6;V} zI@KlVLMnBsqn^)3G1uVft(-DNmqX?sdg37=*M|(|dOyB7SD~t1Oh~E+@Z(YgB#y+4 z{`CD?*C=?-fkKHOQsAsz?6{2vM$I&`Rte(!g+t~2eK7N}Oy*I`y* z(VK+BWv-8E|L)6Nx+gT3&QU7|b1)-au#Z>pppMLh)-CgE!6+G<$U*tzs zRQ9XtKVaaXY7=X)tLBU|ht$>$J>`wa1s6I3f9?7gN&Npnmi$x=>^H!;HXf;p6lIZl zZV_IQxo(kXE=4|wR>bd%ly?zlEmGY@9IuGO6>+X29#zEOi#Ss8ENB=s9O4tn^2eP; z>Y;cJGy*yo;@ME~Jm`Gr%MexZGait1lRld@s&3NnCjD;G?CX&X;fs2bwe5Wj}_HLs&nwYI8O2~~=975ozD!_BX>WAPtpK++7+`9FS+ z&~Vw9T*GCactftCrJ*5}N~?x3JX`sQhoa{UZJtosH13*VSNJ`H&mA!3Z?A3~GVRPe zzSM8hc@vXkLtp;N_>mRvhD%2csu_Ov1((%^uXF{x_3?b6;Oc3?CnBX?4p|AU7*>;>j%Eux6!0>5AE&LQN5gL}xOW&nXse6%y=Sk#VPD z*+mJx7)U;-tQRQinC*Oxn{S*g%)7M>9c)mbX0mEAvykOQvXVlwktt-6T2RO>WlbZW ze$E!!vB{lVn#(8J+%3Ei@0Ehnp2^0t$<$JJAsy>r`wX3F`|0UiBC%La&SrAC@$rNt z=T0Tli-{kLkI$ruq;u$~R?Rk-x-`&CnN2pI@5Wv!(>zr`IkA0BZ_(wGZJ3uS~! zyh+}cCbe2?4-07{eW=^B^CIkWX1HOUrT$nI1o)2{s^9cwdgAwqNTkG5EPsXNn7 zMy339=1m=tQ(YzTgtO=-lf~9jwO$pApL$vRs--oVirXDilnB}4(e7FD$lkQY3-~NN zo=WkRr%>|0I4+{gc1fmy@=QxhA**KR*`>v_v6c+W*mM=LD6%7&DdgPLaY7nPpO)1U zP*}D1L^YZ3@y8b2Y#4GPkVhdRD<+#d%!or2PLr(Xly%lb9Oj>5g z0i+F2tmj-_=j6KR^sLi$&?Q^M|CYay@g?CsiODcmc*R4TbWn_)|oT&}F+E9cTO zxDn{4+sbBNbZpn*uQZn4PVCx3hI>Jxvn7#;J6WOMOcvUxllXKh7zewib$wq-I2O7bk`cemTd#m7L)SplpqbEY=msbTC7?ynVyF$0S;7iv zHMAbu0Ey+>pzlHtK|g?g1pN$p0_uXEgAPFd480C9lGP*1{||eYlGdp<-wE$@cK;B5 zx$RGJeyaVi!kafQ&Ha8}dzM>+Tkc`3TU_$LvMAjyKn%>Amt!F#yRWp+vh!CvOXPM= zVac6Y`xq%nEMYUil9-aEjZY&dhm`YPaCGGzb*_UI9*@WC@p=57fG6k)c^W-oPsHo- zdc8ic-y84-y&-R-H|&l0JS?sB`TV|sFX#*T8hv43#P9KY{XW0nAMgkLA%CMk?2iOI z0dK$;@CO2cU?3D|41@!bpeN`J`hxyoAQ%jWf{np&FcR{Fydhu69}0wmp-`wX6b?li zJ&oQ*U!%V<&=_nCH8wVe8zW&)*c*Z zBO0hiu}P-_4~++tuLopj)9nW$9y8Gq%JT>@)LB^)UzgcNtqc(6)%?VUHTqwVy{(Y3rx zcNJwTcHawG-CM!#d35JJJL+7K`7iPS`V~5PdinFAAy{s-r@%bks*r^yLR=Z0?=cd*04hkrWQ`R*^9W^LM*9=f{Nw7U4>gI7QKhKbkH z6rb?s%uU;VQ3@S>Jta9C&+1{Z)O~fXf2cTWHFlM*=%_tiHU*=(%cfxXCCg2`E}Mdr z@=+Xu9R%4U3;FLUDjZHQOHb*uEn`4{8 zO;-&5w};w~bWMG08T)3uWy2=JFNNlMla;gaRCMe8b*>RM>$dVr=a^nZU{+-B3mwls z@J^Qro0dHLz=+F_9P1kG9kgfUH{Nb)3V!RKrqw)OD#25(+jylAXL_K{Rf+RNp9G9J z-X;5{?AY-yO-@doeN^;$^c_dQxll9$*6oUD*t)yUHQ{*IBhNhdrHf}jQ4%ocUDpv) zUTerJ7><>$Pv+5BLgt7hM)m3rtS<7o!Yuzqrev=TWaV22AeeRV&L`+?^gTm_CB z+#j4WSmZm)NHsDYqP;3 zzpqUGSebnNdPk4Q2f*^uNAf%0V$$7gu;lS+i{Za-u;i=$b|-EX{D8rdm+2P6?-Xpx z)5E|_WBBt87X2G7hX1s|qJQGI9eL3|*I?1_{*J?!`kD?N%(PPM-%VN?!+*(O(SO2X z_!n((WJLcgi{YmX7X521hW|LY#@K(zV)%nLI(*R|uo!;aV9~$XV)%Ou7XAAzhJPGf zYxLJ|>c!0li~e?tk>6#o=zqat_+r245&cIEU&^QEj(%#m=m(46YK%4Yuj>N=xK_`6D&F5F^d_F9b0A&cRE zY%%=WdmOu^tG)_q6B+aPwY`t@q%U#V;nQ1{aPP=ZWR92kwDxUA9)7@L z6ihGS3MHdJDN2dE*%YhXEglC)AAhpWbxh8(*TQCiq0O3krA)ae9GhywJifNkr^aKDHxe?rdUgS(Q^u4JW=u`e9`G( zN2hqG#63Z}8Y44uhrz-p@0KrlxAGIM4DziOBfrsN_>Yz_Jsb9m*GpPO_!=W~xP%>h z#JjaU@HG~Dc3Mo&ZoFP(Bwyn7HeU{Q^oZA68I92+d3Po}Jx}wdAazfF6=%(rRx}aX^7bfti;=cjYb@4|4 literal 0 HcmV?d00001 diff --git a/jsm/libs/stats.module.js b/jsm/libs/stats.module.js new file mode 100644 index 0000000..0d372ba --- /dev/null +++ b/jsm/libs/stats.module.js @@ -0,0 +1,167 @@ +var Stats = function () { + + var mode = 0; + + var container = document.createElement( 'div' ); + container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000'; + container.addEventListener( 'click', function ( event ) { + + event.preventDefault(); + showPanel( ++ mode % container.children.length ); + + }, false ); + + // + + function addPanel( panel ) { + + container.appendChild( panel.dom ); + return panel; + + } + + function showPanel( id ) { + + for ( var i = 0; i < container.children.length; i ++ ) { + + container.children[ i ].style.display = i === id ? 'block' : 'none'; + + } + + mode = id; + + } + + // + + var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0; + + var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) ); + var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) ); + + if ( self.performance && self.performance.memory ) { + + var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) ); + + } + + showPanel( 0 ); + + return { + + REVISION: 16, + + dom: container, + + addPanel: addPanel, + showPanel: showPanel, + + begin: function () { + + beginTime = ( performance || Date ).now(); + + }, + + end: function () { + + frames ++; + + var time = ( performance || Date ).now(); + + msPanel.update( time - beginTime, 200 ); + + if ( time >= prevTime + 1000 ) { + + fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 ); + + prevTime = time; + frames = 0; + + if ( memPanel ) { + + var memory = performance.memory; + memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 ); + + } + + } + + return time; + + }, + + update: function () { + + beginTime = this.end(); + + }, + + // Backwards Compatibility + + domElement: container, + setMode: showPanel + + }; + +}; + +Stats.Panel = function ( name, fg, bg ) { + + var min = Infinity, max = 0, round = Math.round; + var PR = round( window.devicePixelRatio || 1 ); + + var WIDTH = 80 * PR, HEIGHT = 48 * PR, + TEXT_X = 3 * PR, TEXT_Y = 2 * PR, + GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR, + GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR; + + var canvas = document.createElement( 'canvas' ); + canvas.width = WIDTH; + canvas.height = HEIGHT; + canvas.style.cssText = 'width:80px;height:48px'; + + var context = canvas.getContext( '2d' ); + context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif'; + context.textBaseline = 'top'; + + context.fillStyle = bg; + context.fillRect( 0, 0, WIDTH, HEIGHT ); + + context.fillStyle = fg; + context.fillText( name, TEXT_X, TEXT_Y ); + context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT ); + + context.fillStyle = bg; + context.globalAlpha = 0.9; + context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT ); + + return { + + dom: canvas, + + update: function ( value, maxValue ) { + + min = Math.min( min, value ); + max = Math.max( max, value ); + + context.fillStyle = bg; + context.globalAlpha = 1; + context.fillRect( 0, 0, WIDTH, GRAPH_Y ); + context.fillStyle = fg; + context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y ); + + context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT ); + + context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT ); + + context.fillStyle = bg; + context.globalAlpha = 0.9; + context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) ); + + } + + }; + +}; + +export default Stats; diff --git a/jsm/libs/tween.module.min.js b/jsm/libs/tween.module.min.js new file mode 100644 index 0000000..c29d8f5 --- /dev/null +++ b/jsm/libs/tween.module.min.js @@ -0,0 +1,3 @@ +// tween.js 17.3.5 - https://github.com/tweenjs/tween.js +var _Group=function(){this._tweens={},this._tweensAddedDuringUpdate={}};_Group.prototype={getAll:function(){return Object.keys(this._tweens).map(function(t){return this._tweens[t]}.bind(this))},removeAll:function(){this._tweens={}},add:function(t){this._tweens[t.getId()]=t,this._tweensAddedDuringUpdate[t.getId()]=t},remove:function(t){delete this._tweens[t.getId()],delete this._tweensAddedDuringUpdate[t.getId()]},update:function(t,n){var e=Object.keys(this._tweens);if(0===e.length)return!1;for(t=void 0!==t?t:TWEEN.now();0, + * linewidth: , + * dashed: , + * dashScale: , + * dashSize: , + * dashOffset: , + * gapSize: , + * resolution: , // to be set by renderer + * } + */ + +import { + ShaderLib, + ShaderMaterial, + UniformsLib, + UniformsUtils, + Vector2 +} from 'three'; + + +UniformsLib.line = { + + worldUnits: { value: 1 }, + linewidth: { value: 1 }, + resolution: { value: new Vector2( 1, 1 ) }, + dashOffset: { value: 0 }, + dashScale: { value: 1 }, + dashSize: { value: 1 }, + gapSize: { value: 1 } // todo FIX - maybe change to totalSize + +}; + +ShaderLib[ 'line' ] = { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.fog, + UniformsLib.line + ] ), + + vertexShader: + /* glsl */` + #include + #include + #include + #include + #include + + uniform float linewidth; + uniform vec2 resolution; + + attribute vec3 instanceStart; + attribute vec3 instanceEnd; + + attribute vec3 instanceColorStart; + attribute vec3 instanceColorEnd; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + + varying vec2 vUv; + + #endif + + #else + + varying vec2 vUv; + + #endif + + #ifdef USE_DASH + + uniform float dashScale; + attribute float instanceDistanceStart; + attribute float instanceDistanceEnd; + varying float vLineDistance; + + #endif + + void trimSegment( const in vec4 start, inout vec4 end ) { + + // trim end segment so it terminates between the camera plane and the near plane + + // conservative estimate of the near plane + float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column + float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column + float nearEstimate = - 0.5 * b / a; + + float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); + + end.xyz = mix( start.xyz, end.xyz, alpha ); + + } + + void main() { + + #ifdef USE_COLOR + + vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; + + #endif + + #ifdef USE_DASH + + vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; + vUv = uv; + + #endif + + float aspect = resolution.x / resolution.y; + + // camera space + vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); + vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); + + #ifdef WORLD_UNITS + + worldStart = start.xyz; + worldEnd = end.xyz; + + #else + + vUv = uv; + + #endif + + // special case for perspective projection, and segments that terminate either in, or behind, the camera plane + // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space + // but we need to perform ndc-space calculations in the shader, so we must address this issue directly + // perhaps there is a more elegant solution -- WestLangley + + bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column + + if ( perspective ) { + + if ( start.z < 0.0 && end.z >= 0.0 ) { + + trimSegment( start, end ); + + } else if ( end.z < 0.0 && start.z >= 0.0 ) { + + trimSegment( end, start ); + + } + + } + + // clip space + vec4 clipStart = projectionMatrix * start; + vec4 clipEnd = projectionMatrix * end; + + // ndc space + vec3 ndcStart = clipStart.xyz / clipStart.w; + vec3 ndcEnd = clipEnd.xyz / clipEnd.w; + + // direction + vec2 dir = ndcEnd.xy - ndcStart.xy; + + // account for clip-space aspect ratio + dir.x *= aspect; + dir = normalize( dir ); + + #ifdef WORLD_UNITS + + // get the offset direction as perpendicular to the view vector + vec3 worldDir = normalize( end.xyz - start.xyz ); + vec3 offset; + if ( position.y < 0.5 ) { + + offset = normalize( cross( start.xyz, worldDir ) ); + + } else { + + offset = normalize( cross( end.xyz, worldDir ) ); + + } + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); + + // don't extend the line if we're rendering dashes because we + // won't be rendering the endcaps + #ifndef USE_DASH + + // extend the line bounds to encompass endcaps + start.xyz += - worldDir * linewidth * 0.5; + end.xyz += worldDir * linewidth * 0.5; + + // shift the position of the quad so it hugs the forward edge of the line + offset.xy -= dir * forwardOffset; + offset.z += 0.5; + + #endif + + // endcaps + if ( position.y > 1.0 || position.y < 0.0 ) { + + offset.xy += dir * 2.0 * forwardOffset; + + } + + // adjust for linewidth + offset *= linewidth * 0.5; + + // set the world position + worldPos = ( position.y < 0.5 ) ? start : end; + worldPos.xyz += offset; + + // project the worldpos + vec4 clip = projectionMatrix * worldPos; + + // shift the depth of the projected points so the line + // segments overlap neatly + vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; + clip.z = clipPose.z * clip.w; + + #else + + vec2 offset = vec2( dir.y, - dir.x ); + // undo aspect ratio adjustment + dir.x /= aspect; + offset.x /= aspect; + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + // endcaps + if ( position.y < 0.0 ) { + + offset += - dir; + + } else if ( position.y > 1.0 ) { + + offset += dir; + + } + + // adjust for linewidth + offset *= linewidth; + + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset /= resolution.y; + + // select end + vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; + + // back to clip space + offset *= clip.w; + + clip.xy += offset; + + #endif + + gl_Position = clip; + + vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation + + #include + #include + #include + + } + `, + + fragmentShader: + /* glsl */` + uniform vec3 diffuse; + uniform float opacity; + uniform float linewidth; + + #ifdef USE_DASH + + uniform float dashOffset; + uniform float dashSize; + uniform float gapSize; + + #endif + + varying float vLineDistance; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + + varying vec2 vUv; + + #endif + + #else + + varying vec2 vUv; + + #endif + + #include + #include + #include + #include + #include + + vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { + + float mua; + float mub; + + vec3 p13 = p1 - p3; + vec3 p43 = p4 - p3; + + vec3 p21 = p2 - p1; + + float d1343 = dot( p13, p43 ); + float d4321 = dot( p43, p21 ); + float d1321 = dot( p13, p21 ); + float d4343 = dot( p43, p43 ); + float d2121 = dot( p21, p21 ); + + float denom = d2121 * d4343 - d4321 * d4321; + + float numer = d1343 * d4321 - d1321 * d4343; + + mua = numer / denom; + mua = clamp( mua, 0.0, 1.0 ); + mub = ( d1343 + d4321 * ( mua ) ) / d4343; + mub = clamp( mub, 0.0, 1.0 ); + + return vec2( mua, mub ); + + } + + void main() { + + #include + + #ifdef USE_DASH + + if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps + + if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX + + #endif + + float alpha = opacity; + + #ifdef WORLD_UNITS + + // Find the closest points on the view ray and the line segment + vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; + vec3 lineDir = worldEnd - worldStart; + vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); + + vec3 p1 = worldStart + lineDir * params.x; + vec3 p2 = rayEnd * params.y; + vec3 delta = p1 - p2; + float len = length( delta ); + float norm = len / linewidth; + + #ifndef USE_DASH + + #ifdef USE_ALPHA_TO_COVERAGE + + float dnorm = fwidth( norm ); + alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); + + #else + + if ( norm > 0.5 ) { + + discard; + + } + + #endif + + #endif + + #else + + #ifdef USE_ALPHA_TO_COVERAGE + + // artifacts appear on some hardware if a derivative is taken within a conditional + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; + float dlen = fwidth( len2 ); + + if ( abs( vUv.y ) > 1.0 ) { + + alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); + + } + + #else + + if ( abs( vUv.y ) > 1.0 ) { + + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; + + if ( len2 > 1.0 ) discard; + + } + + #endif + + #endif + + vec4 diffuseColor = vec4( diffuse, alpha ); + + #include + #include + + gl_FragColor = vec4( diffuseColor.rgb, alpha ); + + #include + #include + #include + #include + + } + ` +}; + +class LineMaterial extends ShaderMaterial { + + constructor( parameters ) { + + super( { + + type: 'LineMaterial', + + uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), + + vertexShader: ShaderLib[ 'line' ].vertexShader, + fragmentShader: ShaderLib[ 'line' ].fragmentShader, + + clipping: true // required for clipping support + + } ); + + Object.defineProperties( this, { + + color: { + + enumerable: true, + + get: function () { + + return this.uniforms.diffuse.value; + + }, + + set: function ( value ) { + + this.uniforms.diffuse.value = value; + + } + + }, + + worldUnits: { + + enumerable: true, + + get: function () { + + return 'WORLD_UNITS' in this.defines; + + }, + + set: function ( value ) { + + if ( value === true ) { + + this.defines.WORLD_UNITS = ''; + + } else { + + delete this.defines.WORLD_UNITS; + + } + + } + + }, + + linewidth: { + + enumerable: true, + + get: function () { + + return this.uniforms.linewidth.value; + + }, + + set: function ( value ) { + + this.uniforms.linewidth.value = value; + + } + + }, + + dashed: { + + enumerable: true, + + get: function () { + + return Boolean( 'USE_DASH' in this.defines ); + + }, + + set( value ) { + + if ( Boolean( value ) !== Boolean( 'USE_DASH' in this.defines ) ) { + + this.needsUpdate = true; + + } + + if ( value === true ) { + + this.defines.USE_DASH = ''; + + } else { + + delete this.defines.USE_DASH; + + } + + } + + }, + + dashScale: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashScale.value; + + }, + + set: function ( value ) { + + this.uniforms.dashScale.value = value; + + } + + }, + + dashSize: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashSize.value; + + }, + + set: function ( value ) { + + this.uniforms.dashSize.value = value; + + } + + }, + + dashOffset: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashOffset.value; + + }, + + set: function ( value ) { + + this.uniforms.dashOffset.value = value; + + } + + }, + + gapSize: { + + enumerable: true, + + get: function () { + + return this.uniforms.gapSize.value; + + }, + + set: function ( value ) { + + this.uniforms.gapSize.value = value; + + } + + }, + + opacity: { + + enumerable: true, + + get: function () { + + return this.uniforms.opacity.value; + + }, + + set: function ( value ) { + + this.uniforms.opacity.value = value; + + } + + }, + + resolution: { + + enumerable: true, + + get: function () { + + return this.uniforms.resolution.value; + + }, + + set: function ( value ) { + + this.uniforms.resolution.value.copy( value ); + + } + + }, + + alphaToCoverage: { + + enumerable: true, + + get: function () { + + return Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ); + + }, + + set: function ( value ) { + + if ( Boolean( value ) !== Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ) ) { + + this.needsUpdate = true; + + } + + if ( value === true ) { + + this.defines.USE_ALPHA_TO_COVERAGE = ''; + this.extensions.derivatives = true; + + } else { + + delete this.defines.USE_ALPHA_TO_COVERAGE; + this.extensions.derivatives = false; + + } + + } + + } + + } ); + + this.setValues( parameters ); + + } + +} + +LineMaterial.prototype.isLineMaterial = true; + +export { LineMaterial }; diff --git a/jsm/lines/LineSegments2.js b/jsm/lines/LineSegments2.js new file mode 100644 index 0000000..137745c --- /dev/null +++ b/jsm/lines/LineSegments2.js @@ -0,0 +1,355 @@ +import { + Box3, + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Line3, + MathUtils, + Matrix4, + Mesh, + Sphere, + Vector3, + Vector4 +} from 'three'; +import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; +import { LineMaterial } from '../lines/LineMaterial.js'; + +const _start = new Vector3(); +const _end = new Vector3(); + +const _start4 = new Vector4(); +const _end4 = new Vector4(); + +const _ssOrigin = new Vector4(); +const _ssOrigin3 = new Vector3(); +const _mvMatrix = new Matrix4(); +const _line = new Line3(); +const _closestPoint = new Vector3(); + +const _box = new Box3(); +const _sphere = new Sphere(); +const _clipToWorldVector = new Vector4(); + +let _ray, _instanceStart, _instanceEnd, _lineWidth; + +// Returns the margin required to expand by in world space given the distance from the camera, +// line width, resolution, and camera projection +function getWorldSpaceHalfWidth( camera, distance, resolution ) { + + // transform into clip space, adjust the x and y values by the pixel width offset, then + // transform back into world space to get world offset. Note clip space is [-1, 1] so full + // width does not need to be halved. + _clipToWorldVector.set( 0, 0, - distance, 1.0 ).applyMatrix4( camera.projectionMatrix ); + _clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w ); + _clipToWorldVector.x = _lineWidth / resolution.width; + _clipToWorldVector.y = _lineWidth / resolution.height; + _clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse ); + _clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w ); + + return Math.abs( Math.max( _clipToWorldVector.x, _clipToWorldVector.y ) ); + +} + +function raycastWorldUnits( lineSegments, intersects ) { + + for ( let i = 0, l = _instanceStart.count; i < l; i ++ ) { + + _line.start.fromBufferAttribute( _instanceStart, i ); + _line.end.fromBufferAttribute( _instanceEnd, i ); + + const pointOnLine = new Vector3(); + const point = new Vector3(); + + _ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine ); + const isInside = point.distanceTo( pointOnLine ) < _lineWidth * 0.5; + + if ( isInside ) { + + intersects.push( { + point, + pointOnLine, + distance: _ray.origin.distanceTo( point ), + object: lineSegments, + face: null, + faceIndex: i, + uv: null, + uv2: null, + } ); + + } + + } + +} + +function raycastScreenSpace( lineSegments, camera, intersects ) { + + const projectionMatrix = camera.projectionMatrix; + const material = lineSegments.material; + const resolution = material.resolution; + const matrixWorld = lineSegments.matrixWorld; + + const geometry = lineSegments.geometry; + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + + const near = - camera.near; + + // + + // pick a point 1 unit out along the ray to avoid the ray origin + // sitting at the camera origin which will cause "w" to be 0 when + // applying the projection matrix. + _ray.at( 1, _ssOrigin ); + + // ndc space [ - 1.0, 1.0 ] + _ssOrigin.w = 1; + _ssOrigin.applyMatrix4( camera.matrixWorldInverse ); + _ssOrigin.applyMatrix4( projectionMatrix ); + _ssOrigin.multiplyScalar( 1 / _ssOrigin.w ); + + // screen space + _ssOrigin.x *= resolution.x / 2; + _ssOrigin.y *= resolution.y / 2; + _ssOrigin.z = 0; + + _ssOrigin3.copy( _ssOrigin ); + + _mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld ); + + for ( let i = 0, l = instanceStart.count; i < l; i ++ ) { + + _start4.fromBufferAttribute( instanceStart, i ); + _end4.fromBufferAttribute( instanceEnd, i ); + + _start4.w = 1; + _end4.w = 1; + + // camera space + _start4.applyMatrix4( _mvMatrix ); + _end4.applyMatrix4( _mvMatrix ); + + // skip the segment if it's entirely behind the camera + const isBehindCameraNear = _start4.z > near && _end4.z > near; + if ( isBehindCameraNear ) { + + continue; + + } + + // trim the segment if it extends behind camera near + if ( _start4.z > near ) { + + const deltaDist = _start4.z - _end4.z; + const t = ( _start4.z - near ) / deltaDist; + _start4.lerp( _end4, t ); + + } else if ( _end4.z > near ) { + + const deltaDist = _end4.z - _start4.z; + const t = ( _end4.z - near ) / deltaDist; + _end4.lerp( _start4, t ); + + } + + // clip space + _start4.applyMatrix4( projectionMatrix ); + _end4.applyMatrix4( projectionMatrix ); + + // ndc space [ - 1.0, 1.0 ] + _start4.multiplyScalar( 1 / _start4.w ); + _end4.multiplyScalar( 1 / _end4.w ); + + // screen space + _start4.x *= resolution.x / 2; + _start4.y *= resolution.y / 2; + + _end4.x *= resolution.x / 2; + _end4.y *= resolution.y / 2; + + // create 2d segment + _line.start.copy( _start4 ); + _line.start.z = 0; + + _line.end.copy( _end4 ); + _line.end.z = 0; + + // get closest point on ray to segment + const param = _line.closestPointToPointParameter( _ssOrigin3, true ); + _line.at( param, _closestPoint ); + + // check if the intersection point is within clip space + const zPos = MathUtils.lerp( _start4.z, _end4.z, param ); + const isInClipSpace = zPos >= - 1 && zPos <= 1; + + const isInside = _ssOrigin3.distanceTo( _closestPoint ) < _lineWidth * 0.5; + + if ( isInClipSpace && isInside ) { + + _line.start.fromBufferAttribute( instanceStart, i ); + _line.end.fromBufferAttribute( instanceEnd, i ); + + _line.start.applyMatrix4( matrixWorld ); + _line.end.applyMatrix4( matrixWorld ); + + const pointOnLine = new Vector3(); + const point = new Vector3(); + + _ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine ); + + intersects.push( { + point: point, + pointOnLine: pointOnLine, + distance: _ray.origin.distanceTo( point ), + object: lineSegments, + face: null, + faceIndex: i, + uv: null, + uv2: null, + } ); + + } + + } + +} + +class LineSegments2 extends Mesh { + + constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) { + + super( geometry, material ); + + this.type = 'LineSegments2'; + + } + + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + + computeLineDistances() { + + const geometry = this.geometry; + + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const lineDistances = new Float32Array( 2 * instanceStart.count ); + + for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) { + + _start.fromBufferAttribute( instanceStart, i ); + _end.fromBufferAttribute( instanceEnd, i ); + + lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ]; + lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end ); + + } + + const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1 + + geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0 + geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1 + + return this; + + } + + raycast( raycaster, intersects ) { + + const worldUnits = this.material.worldUnits; + const camera = raycaster.camera; + + if ( camera === null && ! worldUnits ) { + + console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2 while worldUnits is set to false.' ); + + } + + const threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0; + + _ray = raycaster.ray; + + const matrixWorld = this.matrixWorld; + const geometry = this.geometry; + const material = this.material; + + _lineWidth = material.linewidth + threshold; + + _instanceStart = geometry.attributes.instanceStart; + _instanceEnd = geometry.attributes.instanceEnd; + + // check if we intersect the sphere bounds + if ( geometry.boundingSphere === null ) { + + geometry.computeBoundingSphere(); + + } + + _sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld ); + + // increase the sphere bounds by the worst case line screen space width + let sphereMargin; + if ( worldUnits ) { + + sphereMargin = _lineWidth * 0.5; + + } else { + + const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( _ray.origin ) ); + sphereMargin = getWorldSpaceHalfWidth( camera, distanceToSphere, material.resolution ); + + } + + _sphere.radius += sphereMargin; + + if ( _ray.intersectsSphere( _sphere ) === false ) { + + return; + + } + + // check if we intersect the box bounds + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + _box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld ); + + // increase the box bounds by the worst case line width + let boxMargin; + if ( worldUnits ) { + + boxMargin = _lineWidth * 0.5; + + } else { + + const distanceToBox = Math.max( camera.near, _box.distanceToPoint( _ray.origin ) ); + boxMargin = getWorldSpaceHalfWidth( camera, distanceToBox, material.resolution ); + + } + + _box.expandByScalar( boxMargin ); + + if ( _ray.intersectsBox( _box ) === false ) { + + return; + + } + + if ( worldUnits ) { + + raycastWorldUnits( this, intersects ); + + } else { + + raycastScreenSpace( this, camera, intersects ); + + } + + } + +} + +LineSegments2.prototype.isLineSegments2 = true; + +export { LineSegments2 }; diff --git a/jsm/lines/LineSegmentsGeometry.js b/jsm/lines/LineSegmentsGeometry.js new file mode 100644 index 0000000..bee5360 --- /dev/null +++ b/jsm/lines/LineSegmentsGeometry.js @@ -0,0 +1,250 @@ +import { + Box3, + Float32BufferAttribute, + InstancedBufferGeometry, + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Sphere, + Vector3, + WireframeGeometry +} from 'three'; + +const _box = new Box3(); +const _vector = new Vector3(); + +class LineSegmentsGeometry extends InstancedBufferGeometry { + + constructor() { + + super(); + + this.type = 'LineSegmentsGeometry'; + + const positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ]; + const uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ]; + const index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ]; + + this.setIndex( index ); + this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + applyMatrix4( matrix ) { + + const start = this.attributes.instanceStart; + const end = this.attributes.instanceEnd; + + if ( start !== undefined ) { + + start.applyMatrix4( matrix ); + + end.applyMatrix4( matrix ); + + start.needsUpdate = true; + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + return this; + + } + + setPositions( array ) { + + let lineSegments; + + if ( array instanceof Float32Array ) { + + lineSegments = array; + + } else if ( Array.isArray( array ) ) { + + lineSegments = new Float32Array( array ); + + } + + const instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz + + this.setAttribute( 'instanceStart', new InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz + this.setAttribute( 'instanceEnd', new InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz + + // + + this.computeBoundingBox(); + this.computeBoundingSphere(); + + return this; + + } + + setColors( array ) { + + let colors; + + if ( array instanceof Float32Array ) { + + colors = array; + + } else if ( Array.isArray( array ) ) { + + colors = new Float32Array( array ); + + } + + const instanceColorBuffer = new InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb + + this.setAttribute( 'instanceColorStart', new InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb + this.setAttribute( 'instanceColorEnd', new InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb + + return this; + + } + + fromWireframeGeometry( geometry ) { + + this.setPositions( geometry.attributes.position.array ); + + return this; + + } + + fromEdgesGeometry( geometry ) { + + this.setPositions( geometry.attributes.position.array ); + + return this; + + } + + fromMesh( mesh ) { + + this.fromWireframeGeometry( new WireframeGeometry( mesh.geometry ) ); + + // set colors, maybe + + return this; + + } + + fromLineSegments( lineSegments ) { + + const geometry = lineSegments.geometry; + + if ( geometry.isGeometry ) { + + console.error( 'THREE.LineSegmentsGeometry no longer supports Geometry. Use THREE.BufferGeometry instead.' ); + return; + + } else if ( geometry.isBufferGeometry ) { + + this.setPositions( geometry.attributes.position.array ); // assumes non-indexed + + } + + // set colors, maybe + + return this; + + } + + computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + const start = this.attributes.instanceStart; + const end = this.attributes.instanceEnd; + + if ( start !== undefined && end !== undefined ) { + + this.boundingBox.setFromBufferAttribute( start ); + + _box.setFromBufferAttribute( end ); + + this.boundingBox.union( _box ); + + } + + } + + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + if ( this.boundingBox === null ) { + + this.computeBoundingBox(); + + } + + const start = this.attributes.instanceStart; + const end = this.attributes.instanceEnd; + + if ( start !== undefined && end !== undefined ) { + + const center = this.boundingSphere.center; + + this.boundingBox.getCenter( center ); + + let maxRadiusSq = 0; + + for ( let i = 0, il = start.count; i < il; i ++ ) { + + _vector.fromBufferAttribute( start, i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) ); + + _vector.fromBufferAttribute( end, i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) ); + + } + + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + + if ( isNaN( this.boundingSphere.radius ) ) { + + console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this ); + + } + + } + + } + + toJSON() { + + // todo + + } + + applyMatrix( matrix ) { + + console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' ); + + return this.applyMatrix4( matrix ); + + } + +} + +LineSegmentsGeometry.prototype.isLineSegmentsGeometry = true; + +export { LineSegmentsGeometry }; diff --git a/jsm/lines/Wireframe.js b/jsm/lines/Wireframe.js new file mode 100644 index 0000000..91f8218 --- /dev/null +++ b/jsm/lines/Wireframe.js @@ -0,0 +1,56 @@ +import { + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Mesh, + Vector3 +} from 'three'; +import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; +import { LineMaterial } from '../lines/LineMaterial.js'; + +const _start = new Vector3(); +const _end = new Vector3(); + +class Wireframe extends Mesh { + + constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) { + + super( geometry, material ); + + this.type = 'Wireframe'; + + } + + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + + computeLineDistances() { + + const geometry = this.geometry; + + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const lineDistances = new Float32Array( 2 * instanceStart.count ); + + for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) { + + _start.fromBufferAttribute( instanceStart, i ); + _end.fromBufferAttribute( instanceEnd, i ); + + lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ]; + lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end ); + + } + + const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1 + + geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0 + geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1 + + return this; + + } + +} + +Wireframe.prototype.isWireframe = true; + +export { Wireframe }; diff --git a/jsm/lines/WireframeGeometry2.js b/jsm/lines/WireframeGeometry2.js new file mode 100644 index 0000000..57df177 --- /dev/null +++ b/jsm/lines/WireframeGeometry2.js @@ -0,0 +1,24 @@ +import { + WireframeGeometry +} from 'three'; +import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; + +class WireframeGeometry2 extends LineSegmentsGeometry { + + constructor( geometry ) { + + super(); + + this.type = 'WireframeGeometry2'; + + this.fromWireframeGeometry( new WireframeGeometry( geometry ) ); + + // set colors, maybe + + } + +} + +WireframeGeometry2.prototype.isWireframeGeometry2 = true; + +export { WireframeGeometry2 }; diff --git a/jsm/loaders/3DMLoader.js b/jsm/loaders/3DMLoader.js new file mode 100644 index 0000000..764cf64 --- /dev/null +++ b/jsm/loaders/3DMLoader.js @@ -0,0 +1,1494 @@ +import { + BufferGeometryLoader, + FileLoader, + Loader, + Object3D, + MeshStandardMaterial, + Mesh, + Color, + Points, + PointsMaterial, + Line, + LineBasicMaterial, + Matrix4, + DirectionalLight, + PointLight, + SpotLight, + RectAreaLight, + Vector3, + Sprite, + SpriteMaterial, + CanvasTexture, + LinearFilter, + ClampToEdgeWrapping, + TextureLoader +} from 'three'; + +const _taskCache = new WeakMap(); + +class Rhino3dmLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.libraryPath = ''; + this.libraryPending = null; + this.libraryBinary = null; + this.libraryConfig = {}; + + this.url = ''; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + this.workerConfig = {}; + + this.materials = []; + this.warnings = []; + + } + + setLibraryPath( path ) { + + this.libraryPath = path; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + + this.url = url; + + loader.load( url, ( buffer ) => { + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + return cachedTask.promise.then( onLoad ).catch( onError ); + + } + + this.decodeObjects( buffer, url ) + .then( result => { + + result.userData.warnings = this.warnings; + onLoad( result ); + + } ) + .catch( e => onError( e ) ); + + }, onProgress, onError ); + + } + + debug() { + + console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); + + } + + decodeObjects( buffer, url ) { + + let worker; + let taskID; + + const taskCost = buffer.byteLength; + + const objectPending = this._getWorker( taskCost ) + .then( ( _worker ) => { + + worker = _worker; + taskID = this.workerNextTaskID ++; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'decode', id: taskID, buffer }, [ buffer ] ); + + // this.debug(); + + } ); + + } ) + .then( ( message ) => this._createGeometry( message.data ) ) + .catch( e => { + + throw e; + + } ); + + // Remove task from the task list. + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + objectPending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + this._releaseTask( worker, taskID ); + + //this.debug(); + + } + + } ); + + // Cache the task result. + _taskCache.set( buffer, { + + url: url, + promise: objectPending + + } ); + + return objectPending; + + } + + parse( data, onLoad, onError ) { + + this.decodeObjects( data, '' ) + .then( result => { + + result.userData.warnings = this.warnings; + onLoad( result ); + + } ) + .catch( e => onError( e ) ); + + } + + _compareMaterials( material ) { + + const mat = {}; + mat.name = material.name; + mat.color = {}; + mat.color.r = material.color.r; + mat.color.g = material.color.g; + mat.color.b = material.color.b; + mat.type = material.type; + + for ( let i = 0; i < this.materials.length; i ++ ) { + + const m = this.materials[ i ]; + const _mat = {}; + _mat.name = m.name; + _mat.color = {}; + _mat.color.r = m.color.r; + _mat.color.g = m.color.g; + _mat.color.b = m.color.b; + _mat.type = m.type; + + if ( JSON.stringify( mat ) === JSON.stringify( _mat ) ) { + + return m; + + } + + } + + this.materials.push( material ); + + return material; + + } + + _createMaterial( material ) { + + if ( material === undefined ) { + + return new MeshStandardMaterial( { + color: new Color( 1, 1, 1 ), + metalness: 0.8, + name: 'default', + side: 2 + } ); + + } + + const _diffuseColor = material.diffuseColor; + + const diffusecolor = new Color( _diffuseColor.r / 255.0, _diffuseColor.g / 255.0, _diffuseColor.b / 255.0 ); + + if ( _diffuseColor.r === 0 && _diffuseColor.g === 0 && _diffuseColor.b === 0 ) { + + diffusecolor.r = 1; + diffusecolor.g = 1; + diffusecolor.b = 1; + + } + + // console.log( material ); + + const mat = new MeshStandardMaterial( { + color: diffusecolor, + name: material.name, + side: 2, + transparent: material.transparency > 0 ? true : false, + opacity: 1.0 - material.transparency + } ); + + const textureLoader = new TextureLoader(); + + for ( let i = 0; i < material.textures.length; i ++ ) { + + const texture = material.textures[ i ]; + + if ( texture.image !== null ) { + + const map = textureLoader.load( texture.image ); + + switch ( texture.type ) { + + case 'Diffuse': + + mat.map = map; + + break; + + case 'Bump': + + mat.bumpMap = map; + + break; + + case 'Transparency': + + mat.alphaMap = map; + mat.transparent = true; + + break; + + case 'Emap': + + mat.envMap = map; + + break; + + } + + map.wrapS = texture.wrapU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + map.wrapT = texture.wrapV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + map.repeat.set( texture.repeat[ 0 ], texture.repeat[ 1 ] ); + + } + + } + + return mat; + + } + + _createGeometry( data ) { + + // console.log(data); + + const object = new Object3D(); + const instanceDefinitionObjects = []; + const instanceDefinitions = []; + const instanceReferences = []; + + object.userData[ 'layers' ] = data.layers; + object.userData[ 'groups' ] = data.groups; + object.userData[ 'settings' ] = data.settings; + object.userData[ 'objectType' ] = 'File3dm'; + object.userData[ 'materials' ] = null; + object.name = this.url; + + let objects = data.objects; + const materials = data.materials; + + for ( let i = 0; i < objects.length; i ++ ) { + + const obj = objects[ i ]; + const attributes = obj.attributes; + + switch ( obj.objectType ) { + + case 'InstanceDefinition': + + instanceDefinitions.push( obj ); + + break; + + case 'InstanceReference': + + instanceReferences.push( obj ); + + break; + + default: + + let _object; + + if ( attributes.materialIndex >= 0 ) { + + const rMaterial = materials[ attributes.materialIndex ]; + let material = this._createMaterial( rMaterial ); + material = this._compareMaterials( material ); + _object = this._createObject( obj, material ); + + } else { + + const material = this._createMaterial(); + _object = this._createObject( obj, material ); + + } + + if ( _object === undefined ) { + + continue; + + } + + const layer = data.layers[ attributes.layerIndex ]; + + _object.visible = layer ? data.layers[ attributes.layerIndex ].visible : true; + + if ( attributes.isInstanceDefinitionObject ) { + + instanceDefinitionObjects.push( _object ); + + } else { + + object.add( _object ); + + } + + break; + + } + + } + + for ( let i = 0; i < instanceDefinitions.length; i ++ ) { + + const iDef = instanceDefinitions[ i ]; + + objects = []; + + for ( let j = 0; j < iDef.attributes.objectIds.length; j ++ ) { + + const objId = iDef.attributes.objectIds[ j ]; + + for ( let p = 0; p < instanceDefinitionObjects.length; p ++ ) { + + const idoId = instanceDefinitionObjects[ p ].userData.attributes.id; + + if ( objId === idoId ) { + + objects.push( instanceDefinitionObjects[ p ] ); + + } + + } + + } + + // Currently clones geometry and does not take advantage of instancing + + for ( let j = 0; j < instanceReferences.length; j ++ ) { + + const iRef = instanceReferences[ j ]; + + if ( iRef.geometry.parentIdefId === iDef.attributes.id ) { + + const iRefObject = new Object3D(); + const xf = iRef.geometry.xform.array; + + const matrix = new Matrix4(); + matrix.set( xf[ 0 ], xf[ 1 ], xf[ 2 ], xf[ 3 ], xf[ 4 ], xf[ 5 ], xf[ 6 ], xf[ 7 ], xf[ 8 ], xf[ 9 ], xf[ 10 ], xf[ 11 ], xf[ 12 ], xf[ 13 ], xf[ 14 ], xf[ 15 ] ); + + iRefObject.applyMatrix4( matrix ); + + for ( let p = 0; p < objects.length; p ++ ) { + + iRefObject.add( objects[ p ].clone( true ) ); + + } + + object.add( iRefObject ); + + } + + } + + } + + object.userData[ 'materials' ] = this.materials; + return object; + + } + + _createObject( obj, mat ) { + + const loader = new BufferGeometryLoader(); + + const attributes = obj.attributes; + + let geometry, material, _color, color; + + switch ( obj.objectType ) { + + case 'Point': + case 'PointSet': + + geometry = loader.parse( obj.geometry ); + + if ( geometry.attributes.hasOwnProperty( 'color' ) ) { + + material = new PointsMaterial( { vertexColors: true, sizeAttenuation: false, size: 2 } ); + + } else { + + _color = attributes.drawColor; + color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); + material = new PointsMaterial( { color: color, sizeAttenuation: false, size: 2 } ); + + } + + material = this._compareMaterials( material ); + + const points = new Points( geometry, material ); + points.userData[ 'attributes' ] = attributes; + points.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + points.name = attributes.name; + + } + + return points; + + case 'Mesh': + case 'Extrusion': + case 'SubD': + case 'Brep': + + if ( obj.geometry === null ) return; + + geometry = loader.parse( obj.geometry ); + + if ( geometry.attributes.hasOwnProperty( 'color' ) ) { + + mat.vertexColors = true; + + } + + if ( mat === null ) { + + mat = this._createMaterial(); + mat = this._compareMaterials( mat ); + + } + + const mesh = new Mesh( geometry, mat ); + mesh.castShadow = attributes.castsShadows; + mesh.receiveShadow = attributes.receivesShadows; + mesh.userData[ 'attributes' ] = attributes; + mesh.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + mesh.name = attributes.name; + + } + + return mesh; + + case 'Curve': + + geometry = loader.parse( obj.geometry ); + + _color = attributes.drawColor; + color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); + + material = new LineBasicMaterial( { color: color } ); + material = this._compareMaterials( material ); + + const lines = new Line( geometry, material ); + lines.userData[ 'attributes' ] = attributes; + lines.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + lines.name = attributes.name; + + } + + return lines; + + case 'TextDot': + + geometry = obj.geometry; + + const ctx = document.createElement( 'canvas' ).getContext( '2d' ); + const font = `${geometry.fontHeight}px ${geometry.fontFace}`; + ctx.font = font; + const width = ctx.measureText( geometry.text ).width + 10; + const height = geometry.fontHeight + 10; + + const r = window.devicePixelRatio; + + ctx.canvas.width = width * r; + ctx.canvas.height = height * r; + ctx.canvas.style.width = width + 'px'; + ctx.canvas.style.height = height + 'px'; + ctx.setTransform( r, 0, 0, r, 0, 0 ); + + ctx.font = font; + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + color = attributes.drawColor; + ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a})`; + ctx.fillRect( 0, 0, width, height ); + ctx.fillStyle = 'white'; + ctx.fillText( geometry.text, width / 2, height / 2 ); + + const texture = new CanvasTexture( ctx.canvas ); + texture.minFilter = LinearFilter; + texture.wrapS = ClampToEdgeWrapping; + texture.wrapT = ClampToEdgeWrapping; + + material = new SpriteMaterial( { map: texture, depthTest: false } ); + const sprite = new Sprite( material ); + sprite.position.set( geometry.point[ 0 ], geometry.point[ 1 ], geometry.point[ 2 ] ); + sprite.scale.set( width / 10, height / 10, 1.0 ); + + sprite.userData[ 'attributes' ] = attributes; + sprite.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + sprite.name = attributes.name; + + } + + return sprite; + + case 'Light': + + geometry = obj.geometry; + + let light; + + switch ( geometry.lightStyle.name ) { + + case 'LightStyle_WorldPoint': + + light = new PointLight(); + light.castShadow = attributes.castsShadows; + light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); + light.shadow.normalBias = 0.1; + + break; + + case 'LightStyle_WorldSpot': + + light = new SpotLight(); + light.castShadow = attributes.castsShadows; + light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); + light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ); + light.angle = geometry.spotAngleRadians; + light.shadow.normalBias = 0.1; + + break; + + case 'LightStyle_WorldRectangular': + + light = new RectAreaLight(); + const width = Math.abs( geometry.width[ 2 ] ); + const height = Math.abs( geometry.length[ 0 ] ); + light.position.set( geometry.location[ 0 ] - ( height / 2 ), geometry.location[ 1 ], geometry.location[ 2 ] - ( width / 2 ) ); + light.height = height; + light.width = width; + light.lookAt( new Vector3( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ) ); + + break; + + case 'LightStyle_WorldDirectional': + + light = new DirectionalLight(); + light.castShadow = attributes.castsShadows; + light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); + light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ); + light.shadow.normalBias = 0.1; + + break; + + case 'LightStyle_WorldLinear': + // not conversion exists, warning has already been printed to the console + break; + + default: + break; + + } + + if ( light ) { + + light.intensity = geometry.intensity; + _color = geometry.diffuse; + color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); + light.color = color; + light.userData[ 'attributes' ] = attributes; + light.userData[ 'objectType' ] = obj.objectType; + + } + + return light; + + } + + } + + _initLibrary() { + + if ( ! this.libraryPending ) { + + // Load rhino3dm wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.libraryPath ); + const jsContent = new Promise( ( resolve, reject ) => { + + jsLoader.load( 'rhino3dm.js', resolve, undefined, reject ); + + } ); + + // Load rhino3dm WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.libraryPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + const binaryContent = new Promise( ( resolve, reject ) => { + + binaryLoader.load( 'rhino3dm.wasm', resolve, undefined, reject ); + + } ); + + this.libraryPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { + + //this.libraryBinary = binaryContent; + this.libraryConfig.wasmBinary = binaryContent; + + const fn = Rhino3dmWorker.toString(); + + const body = [ + '/* rhino3dm.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + + } ); + + } + + return this.libraryPending; + + } + + _getWorker( taskCost ) { + + return this._initLibrary().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskCosts = {}; + worker._taskLoad = 0; + + worker.postMessage( { + type: 'init', + libraryConfig: this.libraryConfig + } ); + + worker.onmessage = e => { + + const message = e.data; + + switch ( message.type ) { + + case 'warning': + this.warnings.push( message.data ); + console.warn( message.data ); + break; + + case 'decode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.Rhino3dmLoader: Unexpected message, "' + message.type + '"' ); + + } + + }; + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + + worker._taskLoad += taskCost; + + return worker; + + } ); + + } + + _releaseTask( worker, taskID ) { + + worker._taskLoad -= worker._taskCosts[ taskID ]; + delete worker._callbacks[ taskID ]; + delete worker._taskCosts[ taskID ]; + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; ++ i ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + return this; + + } + +} + +/* WEB WORKER */ + +function Rhino3dmWorker() { + + let libraryPending; + let libraryConfig; + let rhino; + let taskID; + + onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + + // console.log(message) + libraryConfig = message.libraryConfig; + const wasmBinary = libraryConfig.wasmBinary; + let RhinoModule; + libraryPending = new Promise( function ( resolve ) { + + /* Like Basis Loader */ + RhinoModule = { wasmBinary, onRuntimeInitialized: resolve }; + + rhino3dm( RhinoModule ); // eslint-disable-line no-undef + + } ).then( () => { + + rhino = RhinoModule; + + } ); + + break; + + case 'decode': + + taskID = message.id; + const buffer = message.buffer; + libraryPending.then( () => { + + try { + + const data = decodeObjects( rhino, buffer ); + self.postMessage( { type: 'decode', id: message.id, data } ); + + } catch ( error ) { + + self.postMessage( { type: 'error', id: message.id, error } ); + + } + + } ); + + break; + + } + + }; + + function decodeObjects( rhino, buffer ) { + + const arr = new Uint8Array( buffer ); + const doc = rhino.File3dm.fromByteArray( arr ); + + const objects = []; + const materials = []; + const layers = []; + const views = []; + const namedViews = []; + const groups = []; + const strings = []; + + //Handle objects + + const objs = doc.objects(); + const cnt = objs.count; + + for ( let i = 0; i < cnt; i ++ ) { + + const _object = objs.get( i ); + + const object = extractObjectData( _object, doc ); + + _object.delete(); + + if ( object ) { + + objects.push( object ); + + } + + } + + // Handle instance definitions + // console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` ); + + for ( let i = 0; i < doc.instanceDefinitions().count(); i ++ ) { + + const idef = doc.instanceDefinitions().get( i ); + const idefAttributes = extractProperties( idef ); + idefAttributes.objectIds = idef.getObjectIds(); + + objects.push( { geometry: null, attributes: idefAttributes, objectType: 'InstanceDefinition' } ); + + } + + // Handle materials + + const textureTypes = [ + // rhino.TextureType.Bitmap, + rhino.TextureType.Diffuse, + rhino.TextureType.Bump, + rhino.TextureType.Transparency, + rhino.TextureType.Opacity, + rhino.TextureType.Emap + ]; + + const pbrTextureTypes = [ + rhino.TextureType.PBR_BaseColor, + rhino.TextureType.PBR_Subsurface, + rhino.TextureType.PBR_SubsurfaceScattering, + rhino.TextureType.PBR_SubsurfaceScatteringRadius, + rhino.TextureType.PBR_Metallic, + rhino.TextureType.PBR_Specular, + rhino.TextureType.PBR_SpecularTint, + rhino.TextureType.PBR_Roughness, + rhino.TextureType.PBR_Anisotropic, + rhino.TextureType.PBR_Anisotropic_Rotation, + rhino.TextureType.PBR_Sheen, + rhino.TextureType.PBR_SheenTint, + rhino.TextureType.PBR_Clearcoat, + rhino.TextureType.PBR_ClearcoatBump, + rhino.TextureType.PBR_ClearcoatRoughness, + rhino.TextureType.PBR_OpacityIor, + rhino.TextureType.PBR_OpacityRoughness, + rhino.TextureType.PBR_Emission, + rhino.TextureType.PBR_AmbientOcclusion, + rhino.TextureType.PBR_Displacement + ]; + + for ( let i = 0; i < doc.materials().count(); i ++ ) { + + const _material = doc.materials().get( i ); + const _pbrMaterial = _material.physicallyBased(); + + let material = extractProperties( _material ); + + const textures = []; + + for ( let j = 0; j < textureTypes.length; j ++ ) { + + const _texture = _material.getTexture( textureTypes[ j ] ); + if ( _texture ) { + + let textureType = textureTypes[ j ].constructor.name; + textureType = textureType.substring( 12, textureType.length ); + const texture = { type: textureType }; + + const image = doc.getEmbeddedFileAsBase64( _texture.fileName ); + + texture.wrapU = _texture.wrapU; + texture.wrapV = _texture.wrapV; + texture.wrapW = _texture.wrapW; + const uvw = _texture.uvwTransform.toFloatArray( true ); + texture.repeat = [ uvw[ 0 ], uvw[ 5 ] ]; + + if ( image ) { + + texture.image = 'data:image/png;base64,' + image; + + } else { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: Image for ${textureType} texture not embedded in file.`, + type: 'missing resource' + } + + } ); + + texture.image = null; + + } + + textures.push( texture ); + + _texture.delete(); + + } + + } + + material.textures = textures; + + if ( _pbrMaterial.supported ) { + + for ( let j = 0; j < pbrTextureTypes.length; j ++ ) { + + const _texture = _material.getTexture( pbrTextureTypes[ j ] ); + if ( _texture ) { + + const image = doc.getEmbeddedFileAsBase64( _texture.fileName ); + let textureType = pbrTextureTypes[ j ].constructor.name; + textureType = textureType.substring( 12, textureType.length ); + const texture = { type: textureType, image: 'data:image/png;base64,' + image }; + textures.push( texture ); + + _texture.delete(); + + } + + } + + const pbMaterialProperties = extractProperties( _material.physicallyBased() ); + + material = Object.assign( pbMaterialProperties, material ); + + } + + materials.push( material ); + + _material.delete(); + _pbrMaterial.delete(); + + } + + // Handle layers + + for ( let i = 0; i < doc.layers().count(); i ++ ) { + + const _layer = doc.layers().get( i ); + const layer = extractProperties( _layer ); + + layers.push( layer ); + + _layer.delete(); + + } + + // Handle views + + for ( let i = 0; i < doc.views().count(); i ++ ) { + + const _view = doc.views().get( i ); + const view = extractProperties( _view ); + + views.push( view ); + + _view.delete(); + + } + + // Handle named views + + for ( let i = 0; i < doc.namedViews().count(); i ++ ) { + + const _namedView = doc.namedViews().get( i ); + const namedView = extractProperties( _namedView ); + + namedViews.push( namedView ); + + _namedView.delete(); + + } + + // Handle groups + + for ( let i = 0; i < doc.groups().count(); i ++ ) { + + const _group = doc.groups().get( i ); + const group = extractProperties( _group ); + + groups.push( group ); + + _group.delete(); + + } + + // Handle settings + + const settings = extractProperties( doc.settings() ); + + //TODO: Handle other document stuff like dimstyles, instance definitions, bitmaps etc. + + // Handle dimstyles + // console.log( `Dimstyle Count: ${doc.dimstyles().count()}` ); + + // Handle bitmaps + // console.log( `Bitmap Count: ${doc.bitmaps().count()}` ); + + // Handle strings + // console.log( `Document Strings Count: ${doc.strings().count()}` ); + // Note: doc.strings().documentUserTextCount() counts any doc.strings defined in a section + //console.log( `Document User Text Count: ${doc.strings().documentUserTextCount()}` ); + + const strings_count = doc.strings().count(); + + for ( let i = 0; i < strings_count; i ++ ) { + + strings.push( doc.strings().get( i ) ); + + } + + doc.delete(); + + return { objects, materials, layers, views, namedViews, groups, strings, settings }; + + } + + function extractObjectData( object, doc ) { + + const _geometry = object.geometry(); + const _attributes = object.attributes(); + let objectType = _geometry.objectType; + let geometry, attributes, position, data, mesh; + + // skip instance definition objects + //if( _attributes.isInstanceDefinitionObject ) { continue; } + + // TODO: handle other geometry types + switch ( objectType ) { + + case rhino.ObjectType.Curve: + + const pts = curveToPoints( _geometry, 100 ); + + position = {}; + attributes = {}; + data = {}; + + position.itemSize = 3; + position.type = 'Float32Array'; + position.array = []; + + for ( let j = 0; j < pts.length; j ++ ) { + + position.array.push( pts[ j ][ 0 ] ); + position.array.push( pts[ j ][ 1 ] ); + position.array.push( pts[ j ][ 2 ] ); + + } + + attributes.position = position; + data.attributes = attributes; + + geometry = { data }; + + break; + + case rhino.ObjectType.Point: + + const pt = _geometry.location; + + position = {}; + const color = {}; + attributes = {}; + data = {}; + + position.itemSize = 3; + position.type = 'Float32Array'; + position.array = [ pt[ 0 ], pt[ 1 ], pt[ 2 ] ]; + + const _color = _attributes.drawColor( doc ); + + color.itemSize = 3; + color.type = 'Float32Array'; + color.array = [ _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ]; + + attributes.position = position; + attributes.color = color; + data.attributes = attributes; + + geometry = { data }; + + break; + + case rhino.ObjectType.PointSet: + case rhino.ObjectType.Mesh: + + geometry = _geometry.toThreejsJSON(); + + break; + + case rhino.ObjectType.Brep: + + const faces = _geometry.faces(); + mesh = new rhino.Mesh(); + + for ( let faceIndex = 0; faceIndex < faces.count; faceIndex ++ ) { + + const face = faces.get( faceIndex ); + const _mesh = face.getMesh( rhino.MeshType.Any ); + + if ( _mesh ) { + + mesh.append( _mesh ); + _mesh.delete(); + + } + + face.delete(); + + } + + if ( mesh.faces().count > 0 ) { + + mesh.compact(); + geometry = mesh.toThreejsJSON(); + faces.delete(); + + } + + mesh.delete(); + + break; + + case rhino.ObjectType.Extrusion: + + mesh = _geometry.getMesh( rhino.MeshType.Any ); + + if ( mesh ) { + + geometry = mesh.toThreejsJSON(); + mesh.delete(); + + } + + break; + + case rhino.ObjectType.TextDot: + + geometry = extractProperties( _geometry ); + + break; + + case rhino.ObjectType.Light: + + geometry = extractProperties( _geometry ); + + if ( geometry.lightStyle.name === 'LightStyle_WorldLinear' ) { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: No conversion exists for ${objectType.constructor.name} ${geometry.lightStyle.name}`, + type: 'no conversion', + guid: _attributes.id + } + + } ); + + } + + break; + + case rhino.ObjectType.InstanceReference: + + geometry = extractProperties( _geometry ); + geometry.xform = extractProperties( _geometry.xform ); + geometry.xform.array = _geometry.xform.toFloatArray( true ); + + break; + + case rhino.ObjectType.SubD: + + // TODO: precalculate resulting vertices and faces and warn on excessive results + _geometry.subdivide( 3 ); + mesh = rhino.Mesh.createFromSubDControlNet( _geometry ); + if ( mesh ) { + + geometry = mesh.toThreejsJSON(); + mesh.delete(); + + } + + break; + + /* + case rhino.ObjectType.Annotation: + case rhino.ObjectType.Hatch: + case rhino.ObjectType.ClipPlane: + */ + + default: + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: Conversion not implemented for ${objectType.constructor.name}`, + type: 'not implemented', + guid: _attributes.id + } + + } ); + + break; + + } + + if ( geometry ) { + + attributes = extractProperties( _attributes ); + attributes.geometry = extractProperties( _geometry ); + + if ( _attributes.groupCount > 0 ) { + + attributes.groupIds = _attributes.getGroupList(); + + } + + if ( _attributes.userStringCount > 0 ) { + + attributes.userStrings = _attributes.getUserStrings(); + + } + + if ( _geometry.userStringCount > 0 ) { + + attributes.geometry.userStrings = _geometry.getUserStrings(); + + } + + attributes.drawColor = _attributes.drawColor( doc ); + + objectType = objectType.constructor.name; + objectType = objectType.substring( 11, objectType.length ); + + return { geometry, attributes, objectType }; + + } else { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: ${objectType.constructor.name} has no associated mesh geometry.`, + type: 'missing mesh', + guid: _attributes.id + } + + } ); + + } + + } + + function extractProperties( object ) { + + const result = {}; + + for ( const property in object ) { + + const value = object[ property ]; + + if ( typeof value !== 'function' ) { + + if ( typeof value === 'object' && value !== null && value.hasOwnProperty( 'constructor' ) ) { + + result[ property ] = { name: value.constructor.name, value: value.value }; + + } else { + + result[ property ] = value; + + } + + } else { + + // these are functions that could be called to extract more data. + //console.log( `${property}: ${object[ property ].constructor.name}` ); + + } + + } + + return result; + + } + + function curveToPoints( curve, pointLimit ) { + + let pointCount = pointLimit; + let rc = []; + const ts = []; + + if ( curve instanceof rhino.LineCurve ) { + + return [ curve.pointAtStart, curve.pointAtEnd ]; + + } + + if ( curve instanceof rhino.PolylineCurve ) { + + pointCount = curve.pointCount; + for ( let i = 0; i < pointCount; i ++ ) { + + rc.push( curve.point( i ) ); + + } + + return rc; + + } + + if ( curve instanceof rhino.PolyCurve ) { + + const segmentCount = curve.segmentCount; + + for ( let i = 0; i < segmentCount; i ++ ) { + + const segment = curve.segmentCurve( i ); + const segmentArray = curveToPoints( segment, pointCount ); + rc = rc.concat( segmentArray ); + segment.delete(); + + } + + return rc; + + } + + if ( curve instanceof rhino.ArcCurve ) { + + pointCount = Math.floor( curve.angleDegrees / 5 ); + pointCount = pointCount < 2 ? 2 : pointCount; + // alternative to this hardcoded version: https://stackoverflow.com/a/18499923/2179399 + + } + + if ( curve instanceof rhino.NurbsCurve && curve.degree === 1 ) { + + const pLine = curve.tryGetPolyline(); + + for ( let i = 0; i < pLine.count; i ++ ) { + + rc.push( pLine.get( i ) ); + + } + + pLine.delete(); + + return rc; + + } + + const domain = curve.domain; + const divisions = pointCount - 1.0; + + for ( let j = 0; j < pointCount; j ++ ) { + + const t = domain[ 0 ] + ( j / divisions ) * ( domain[ 1 ] - domain[ 0 ] ); + + if ( t === domain[ 0 ] || t === domain[ 1 ] ) { + + ts.push( t ); + continue; + + } + + const tan = curve.tangentAt( t ); + const prevTan = curve.tangentAt( ts.slice( - 1 )[ 0 ] ); + + // Duplicated from THREE.Vector3 + // How to pass imports to worker? + + const tS = tan[ 0 ] * tan[ 0 ] + tan[ 1 ] * tan[ 1 ] + tan[ 2 ] * tan[ 2 ]; + const ptS = prevTan[ 0 ] * prevTan[ 0 ] + prevTan[ 1 ] * prevTan[ 1 ] + prevTan[ 2 ] * prevTan[ 2 ]; + + const denominator = Math.sqrt( tS * ptS ); + + let angle; + + if ( denominator === 0 ) { + + angle = Math.PI / 2; + + } else { + + const theta = ( tan.x * prevTan.x + tan.y * prevTan.y + tan.z * prevTan.z ) / denominator; + angle = Math.acos( Math.max( - 1, Math.min( 1, theta ) ) ); + + } + + if ( angle < 0.1 ) continue; + + ts.push( t ); + + } + + rc = ts.map( t => curve.pointAt( t ) ); + return rc; + + } + +} + +export { Rhino3dmLoader }; diff --git a/jsm/loaders/3MFLoader.js b/jsm/loaders/3MFLoader.js new file mode 100644 index 0000000..48da188 --- /dev/null +++ b/jsm/loaders/3MFLoader.js @@ -0,0 +1,1473 @@ +import { + BufferAttribute, + BufferGeometry, + ClampToEdgeWrapping, + Color, + FileLoader, + Float32BufferAttribute, + Group, + LinearFilter, + LinearMipmapLinearFilter, + Loader, + LoaderUtils, + Matrix4, + Mesh, + MeshPhongMaterial, + MeshStandardMaterial, + MirroredRepeatWrapping, + NearestFilter, + RepeatWrapping, + TextureLoader, + sRGBEncoding +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +/** + * + * 3D Manufacturing Format (3MF) specification: https://3mf.io/specification/ + * + * The following features from the core specification are supported: + * + * - 3D Models + * - Object Resources (Meshes and Components) + * - Material Resources (Base Materials) + * + * 3MF Materials and Properties Extension are only partially supported. + * + * - Texture 2D + * - Texture 2D Groups + * - Color Groups (Vertex Colors) + * - Metallic Display Properties (PBR) + */ + +class ThreeMFLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.availableExtensions = []; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( buffer ) { + + try { + + onLoad( scope.parse( buffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + const scope = this; + const textureLoader = new TextureLoader( this.manager ); + + function loadDocument( data ) { + + let zip = null; + let file = null; + + let relsName; + let modelRelsName; + const modelPartNames = []; + const texturesPartNames = []; + + let modelRels; + const modelParts = {}; + const printTicketParts = {}; + const texturesParts = {}; + + try { + + zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef + + } catch ( e ) { + + if ( e instanceof ReferenceError ) { + + console.error( 'THREE.3MFLoader: fflate missing and file is compressed.' ); + return null; + + } + + } + + for ( file in zip ) { + + if ( file.match( /\_rels\/.rels$/ ) ) { + + relsName = file; + + } else if ( file.match( /3D\/_rels\/.*\.model\.rels$/ ) ) { + + modelRelsName = file; + + } else if ( file.match( /^3D\/.*\.model$/ ) ) { + + modelPartNames.push( file ); + + } else if ( file.match( /^3D\/Textures?\/.*/ ) ) { + + texturesPartNames.push( file ); + + } + + } + + // + + const relsView = zip[ relsName ]; + const relsFileText = LoaderUtils.decodeText( relsView ); + const rels = parseRelsXml( relsFileText ); + + // + + if ( modelRelsName ) { + + const relsView = zip[ modelRelsName ]; + const relsFileText = LoaderUtils.decodeText( relsView ); + modelRels = parseRelsXml( relsFileText ); + + } + + // + + for ( let i = 0; i < modelPartNames.length; i ++ ) { + + const modelPart = modelPartNames[ i ]; + const view = zip[ modelPart ]; + + const fileText = LoaderUtils.decodeText( view ); + const xmlData = new DOMParser().parseFromString( fileText, 'application/xml' ); + + if ( xmlData.documentElement.nodeName.toLowerCase() !== 'model' ) { + + console.error( 'THREE.3MFLoader: Error loading 3MF - no 3MF document found: ', modelPart ); + + } + + const modelNode = xmlData.querySelector( 'model' ); + const extensions = {}; + + for ( let i = 0; i < modelNode.attributes.length; i ++ ) { + + const attr = modelNode.attributes[ i ]; + if ( attr.name.match( /^xmlns:(.+)$/ ) ) { + + extensions[ attr.value ] = RegExp.$1; + + } + + } + + const modelData = parseModelNode( modelNode ); + modelData[ 'xml' ] = modelNode; + + if ( 0 < Object.keys( extensions ).length ) { + + modelData[ 'extensions' ] = extensions; + + } + + modelParts[ modelPart ] = modelData; + + } + + // + + for ( let i = 0; i < texturesPartNames.length; i ++ ) { + + const texturesPartName = texturesPartNames[ i ]; + texturesParts[ texturesPartName ] = zip[ texturesPartName ].buffer; + + } + + return { + rels: rels, + modelRels: modelRels, + model: modelParts, + printTicket: printTicketParts, + texture: texturesParts + }; + + } + + function parseRelsXml( relsFileText ) { + + const relationships = []; + + const relsXmlData = new DOMParser().parseFromString( relsFileText, 'application/xml' ); + + const relsNodes = relsXmlData.querySelectorAll( 'Relationship' ); + + for ( let i = 0; i < relsNodes.length; i ++ ) { + + const relsNode = relsNodes[ i ]; + + const relationship = { + target: relsNode.getAttribute( 'Target' ), //required + id: relsNode.getAttribute( 'Id' ), //required + type: relsNode.getAttribute( 'Type' ) //required + }; + + relationships.push( relationship ); + + } + + return relationships; + + } + + function parseMetadataNodes( metadataNodes ) { + + const metadataData = {}; + + for ( let i = 0; i < metadataNodes.length; i ++ ) { + + const metadataNode = metadataNodes[ i ]; + const name = metadataNode.getAttribute( 'name' ); + const validNames = [ + 'Title', + 'Designer', + 'Description', + 'Copyright', + 'LicenseTerms', + 'Rating', + 'CreationDate', + 'ModificationDate' + ]; + + if ( 0 <= validNames.indexOf( name ) ) { + + metadataData[ name ] = metadataNode.textContent; + + } + + } + + return metadataData; + + } + + function parseBasematerialsNode( basematerialsNode ) { + + const basematerialsData = { + id: basematerialsNode.getAttribute( 'id' ), // required + basematerials: [] + }; + + const basematerialNodes = basematerialsNode.querySelectorAll( 'base' ); + + for ( let i = 0; i < basematerialNodes.length; i ++ ) { + + const basematerialNode = basematerialNodes[ i ]; + const basematerialData = parseBasematerialNode( basematerialNode ); + basematerialData.index = i; // the order and count of the material nodes form an implicit 0-based index + basematerialsData.basematerials.push( basematerialData ); + + } + + return basematerialsData; + + } + + function parseTexture2DNode( texture2DNode ) { + + const texture2dData = { + id: texture2DNode.getAttribute( 'id' ), // required + path: texture2DNode.getAttribute( 'path' ), // required + contenttype: texture2DNode.getAttribute( 'contenttype' ), // required + tilestyleu: texture2DNode.getAttribute( 'tilestyleu' ), + tilestylev: texture2DNode.getAttribute( 'tilestylev' ), + filter: texture2DNode.getAttribute( 'filter' ), + }; + + return texture2dData; + + } + + function parseTextures2DGroupNode( texture2DGroupNode ) { + + const texture2DGroupData = { + id: texture2DGroupNode.getAttribute( 'id' ), // required + texid: texture2DGroupNode.getAttribute( 'texid' ), // required + displaypropertiesid: texture2DGroupNode.getAttribute( 'displaypropertiesid' ) + }; + + const tex2coordNodes = texture2DGroupNode.querySelectorAll( 'tex2coord' ); + + const uvs = []; + + for ( let i = 0; i < tex2coordNodes.length; i ++ ) { + + const tex2coordNode = tex2coordNodes[ i ]; + const u = tex2coordNode.getAttribute( 'u' ); + const v = tex2coordNode.getAttribute( 'v' ); + + uvs.push( parseFloat( u ), parseFloat( v ) ); + + } + + texture2DGroupData[ 'uvs' ] = new Float32Array( uvs ); + + return texture2DGroupData; + + } + + function parseColorGroupNode( colorGroupNode ) { + + const colorGroupData = { + id: colorGroupNode.getAttribute( 'id' ), // required + displaypropertiesid: colorGroupNode.getAttribute( 'displaypropertiesid' ) + }; + + const colorNodes = colorGroupNode.querySelectorAll( 'color' ); + + const colors = []; + const colorObject = new Color(); + + for ( let i = 0; i < colorNodes.length; i ++ ) { + + const colorNode = colorNodes[ i ]; + const color = colorNode.getAttribute( 'color' ); + + colorObject.setStyle( color.substring( 0, 7 ) ); + colorObject.convertSRGBToLinear(); // color is in sRGB + + colors.push( colorObject.r, colorObject.g, colorObject.b ); + + } + + colorGroupData[ 'colors' ] = new Float32Array( colors ); + + return colorGroupData; + + } + + function parseMetallicDisplaypropertiesNode( metallicDisplaypropetiesNode ) { + + const metallicDisplaypropertiesData = { + id: metallicDisplaypropetiesNode.getAttribute( 'id' ) // required + }; + + const metallicNodes = metallicDisplaypropetiesNode.querySelectorAll( 'pbmetallic' ); + + const metallicData = []; + + for ( let i = 0; i < metallicNodes.length; i ++ ) { + + const metallicNode = metallicNodes[ i ]; + + metallicData.push( { + name: metallicNode.getAttribute( 'name' ), // required + metallicness: parseFloat( metallicNode.getAttribute( 'metallicness' ) ), // required + roughness: parseFloat( metallicNode.getAttribute( 'roughness' ) ) // required + } ); + + } + + metallicDisplaypropertiesData.data = metallicData; + + return metallicDisplaypropertiesData; + + } + + function parseBasematerialNode( basematerialNode ) { + + const basematerialData = {}; + + basematerialData[ 'name' ] = basematerialNode.getAttribute( 'name' ); // required + basematerialData[ 'displaycolor' ] = basematerialNode.getAttribute( 'displaycolor' ); // required + basematerialData[ 'displaypropertiesid' ] = basematerialNode.getAttribute( 'displaypropertiesid' ); + + return basematerialData; + + } + + function parseMeshNode( meshNode ) { + + const meshData = {}; + + const vertices = []; + const vertexNodes = meshNode.querySelectorAll( 'vertices vertex' ); + + for ( let i = 0; i < vertexNodes.length; i ++ ) { + + const vertexNode = vertexNodes[ i ]; + const x = vertexNode.getAttribute( 'x' ); + const y = vertexNode.getAttribute( 'y' ); + const z = vertexNode.getAttribute( 'z' ); + + vertices.push( parseFloat( x ), parseFloat( y ), parseFloat( z ) ); + + } + + meshData[ 'vertices' ] = new Float32Array( vertices ); + + const triangleProperties = []; + const triangles = []; + const triangleNodes = meshNode.querySelectorAll( 'triangles triangle' ); + + for ( let i = 0; i < triangleNodes.length; i ++ ) { + + const triangleNode = triangleNodes[ i ]; + const v1 = triangleNode.getAttribute( 'v1' ); + const v2 = triangleNode.getAttribute( 'v2' ); + const v3 = triangleNode.getAttribute( 'v3' ); + const p1 = triangleNode.getAttribute( 'p1' ); + const p2 = triangleNode.getAttribute( 'p2' ); + const p3 = triangleNode.getAttribute( 'p3' ); + const pid = triangleNode.getAttribute( 'pid' ); + + const triangleProperty = {}; + + triangleProperty[ 'v1' ] = parseInt( v1, 10 ); + triangleProperty[ 'v2' ] = parseInt( v2, 10 ); + triangleProperty[ 'v3' ] = parseInt( v3, 10 ); + + triangles.push( triangleProperty[ 'v1' ], triangleProperty[ 'v2' ], triangleProperty[ 'v3' ] ); + + // optional + + if ( p1 ) { + + triangleProperty[ 'p1' ] = parseInt( p1, 10 ); + + } + + if ( p2 ) { + + triangleProperty[ 'p2' ] = parseInt( p2, 10 ); + + } + + if ( p3 ) { + + triangleProperty[ 'p3' ] = parseInt( p3, 10 ); + + } + + if ( pid ) { + + triangleProperty[ 'pid' ] = pid; + + } + + if ( 0 < Object.keys( triangleProperty ).length ) { + + triangleProperties.push( triangleProperty ); + + } + + } + + meshData[ 'triangleProperties' ] = triangleProperties; + meshData[ 'triangles' ] = new Uint32Array( triangles ); + + return meshData; + + } + + function parseComponentsNode( componentsNode ) { + + const components = []; + + const componentNodes = componentsNode.querySelectorAll( 'component' ); + + for ( let i = 0; i < componentNodes.length; i ++ ) { + + const componentNode = componentNodes[ i ]; + const componentData = parseComponentNode( componentNode ); + components.push( componentData ); + + } + + return components; + + } + + function parseComponentNode( componentNode ) { + + const componentData = {}; + + componentData[ 'objectId' ] = componentNode.getAttribute( 'objectid' ); // required + + const transform = componentNode.getAttribute( 'transform' ); + + if ( transform ) { + + componentData[ 'transform' ] = parseTransform( transform ); + + } + + return componentData; + + } + + function parseTransform( transform ) { + + const t = []; + transform.split( ' ' ).forEach( function ( s ) { + + t.push( parseFloat( s ) ); + + } ); + + const matrix = new Matrix4(); + matrix.set( + t[ 0 ], t[ 3 ], t[ 6 ], t[ 9 ], + t[ 1 ], t[ 4 ], t[ 7 ], t[ 10 ], + t[ 2 ], t[ 5 ], t[ 8 ], t[ 11 ], + 0.0, 0.0, 0.0, 1.0 + ); + + return matrix; + + } + + function parseObjectNode( objectNode ) { + + const objectData = { + type: objectNode.getAttribute( 'type' ) + }; + + const id = objectNode.getAttribute( 'id' ); + + if ( id ) { + + objectData[ 'id' ] = id; + + } + + const pid = objectNode.getAttribute( 'pid' ); + + if ( pid ) { + + objectData[ 'pid' ] = pid; + + } + + const pindex = objectNode.getAttribute( 'pindex' ); + + if ( pindex ) { + + objectData[ 'pindex' ] = pindex; + + } + + const thumbnail = objectNode.getAttribute( 'thumbnail' ); + + if ( thumbnail ) { + + objectData[ 'thumbnail' ] = thumbnail; + + } + + const partnumber = objectNode.getAttribute( 'partnumber' ); + + if ( partnumber ) { + + objectData[ 'partnumber' ] = partnumber; + + } + + const name = objectNode.getAttribute( 'name' ); + + if ( name ) { + + objectData[ 'name' ] = name; + + } + + const meshNode = objectNode.querySelector( 'mesh' ); + + if ( meshNode ) { + + objectData[ 'mesh' ] = parseMeshNode( meshNode ); + + } + + const componentsNode = objectNode.querySelector( 'components' ); + + if ( componentsNode ) { + + objectData[ 'components' ] = parseComponentsNode( componentsNode ); + + } + + return objectData; + + } + + function parseResourcesNode( resourcesNode ) { + + const resourcesData = {}; + + resourcesData[ 'basematerials' ] = {}; + const basematerialsNodes = resourcesNode.querySelectorAll( 'basematerials' ); + + for ( let i = 0; i < basematerialsNodes.length; i ++ ) { + + const basematerialsNode = basematerialsNodes[ i ]; + const basematerialsData = parseBasematerialsNode( basematerialsNode ); + resourcesData[ 'basematerials' ][ basematerialsData[ 'id' ] ] = basematerialsData; + + } + + // + + resourcesData[ 'texture2d' ] = {}; + const textures2DNodes = resourcesNode.querySelectorAll( 'texture2d' ); + + for ( let i = 0; i < textures2DNodes.length; i ++ ) { + + const textures2DNode = textures2DNodes[ i ]; + const texture2DData = parseTexture2DNode( textures2DNode ); + resourcesData[ 'texture2d' ][ texture2DData[ 'id' ] ] = texture2DData; + + } + + // + + resourcesData[ 'colorgroup' ] = {}; + const colorGroupNodes = resourcesNode.querySelectorAll( 'colorgroup' ); + + for ( let i = 0; i < colorGroupNodes.length; i ++ ) { + + const colorGroupNode = colorGroupNodes[ i ]; + const colorGroupData = parseColorGroupNode( colorGroupNode ); + resourcesData[ 'colorgroup' ][ colorGroupData[ 'id' ] ] = colorGroupData; + + } + + // + + resourcesData[ 'pbmetallicdisplayproperties' ] = {}; + const pbmetallicdisplaypropertiesNodes = resourcesNode.querySelectorAll( 'pbmetallicdisplayproperties' ); + + for ( let i = 0; i < pbmetallicdisplaypropertiesNodes.length; i ++ ) { + + const pbmetallicdisplaypropertiesNode = pbmetallicdisplaypropertiesNodes[ i ]; + const pbmetallicdisplaypropertiesData = parseMetallicDisplaypropertiesNode( pbmetallicdisplaypropertiesNode ); + resourcesData[ 'pbmetallicdisplayproperties' ][ pbmetallicdisplaypropertiesData[ 'id' ] ] = pbmetallicdisplaypropertiesData; + + } + + // + + resourcesData[ 'texture2dgroup' ] = {}; + const textures2DGroupNodes = resourcesNode.querySelectorAll( 'texture2dgroup' ); + + for ( let i = 0; i < textures2DGroupNodes.length; i ++ ) { + + const textures2DGroupNode = textures2DGroupNodes[ i ]; + const textures2DGroupData = parseTextures2DGroupNode( textures2DGroupNode ); + resourcesData[ 'texture2dgroup' ][ textures2DGroupData[ 'id' ] ] = textures2DGroupData; + + } + + // + + resourcesData[ 'object' ] = {}; + const objectNodes = resourcesNode.querySelectorAll( 'object' ); + + for ( let i = 0; i < objectNodes.length; i ++ ) { + + const objectNode = objectNodes[ i ]; + const objectData = parseObjectNode( objectNode ); + resourcesData[ 'object' ][ objectData[ 'id' ] ] = objectData; + + } + + return resourcesData; + + } + + function parseBuildNode( buildNode ) { + + const buildData = []; + const itemNodes = buildNode.querySelectorAll( 'item' ); + + for ( let i = 0; i < itemNodes.length; i ++ ) { + + const itemNode = itemNodes[ i ]; + const buildItem = { + objectId: itemNode.getAttribute( 'objectid' ) + }; + const transform = itemNode.getAttribute( 'transform' ); + + if ( transform ) { + + buildItem[ 'transform' ] = parseTransform( transform ); + + } + + buildData.push( buildItem ); + + } + + return buildData; + + } + + function parseModelNode( modelNode ) { + + const modelData = { unit: modelNode.getAttribute( 'unit' ) || 'millimeter' }; + const metadataNodes = modelNode.querySelectorAll( 'metadata' ); + + if ( metadataNodes ) { + + modelData[ 'metadata' ] = parseMetadataNodes( metadataNodes ); + + } + + const resourcesNode = modelNode.querySelector( 'resources' ); + + if ( resourcesNode ) { + + modelData[ 'resources' ] = parseResourcesNode( resourcesNode ); + + } + + const buildNode = modelNode.querySelector( 'build' ); + + if ( buildNode ) { + + modelData[ 'build' ] = parseBuildNode( buildNode ); + + } + + return modelData; + + } + + function buildTexture( texture2dgroup, objects, modelData, textureData ) { + + const texid = texture2dgroup.texid; + const texture2ds = modelData.resources.texture2d; + const texture2d = texture2ds[ texid ]; + + if ( texture2d ) { + + const data = textureData[ texture2d.path ]; + const type = texture2d.contenttype; + + const blob = new Blob( [ data ], { type: type } ); + const sourceURI = URL.createObjectURL( blob ); + + const texture = textureLoader.load( sourceURI, function () { + + URL.revokeObjectURL( sourceURI ); + + } ); + + texture.encoding = sRGBEncoding; + + // texture parameters + + switch ( texture2d.tilestyleu ) { + + case 'wrap': + texture.wrapS = RepeatWrapping; + break; + + case 'mirror': + texture.wrapS = MirroredRepeatWrapping; + break; + + case 'none': + case 'clamp': + texture.wrapS = ClampToEdgeWrapping; + break; + + default: + texture.wrapS = RepeatWrapping; + + } + + switch ( texture2d.tilestylev ) { + + case 'wrap': + texture.wrapT = RepeatWrapping; + break; + + case 'mirror': + texture.wrapT = MirroredRepeatWrapping; + break; + + case 'none': + case 'clamp': + texture.wrapT = ClampToEdgeWrapping; + break; + + default: + texture.wrapT = RepeatWrapping; + + } + + switch ( texture2d.filter ) { + + case 'auto': + texture.magFilter = LinearFilter; + texture.minFilter = LinearMipmapLinearFilter; + break; + + case 'linear': + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + break; + + case 'nearest': + texture.magFilter = NearestFilter; + texture.minFilter = NearestFilter; + break; + + default: + texture.magFilter = LinearFilter; + texture.minFilter = LinearMipmapLinearFilter; + + } + + return texture; + + } else { + + return null; + + } + + } + + function buildBasematerialsMeshes( basematerials, triangleProperties, meshData, objects, modelData, textureData, objectData ) { + + const objectPindex = objectData.pindex; + + const materialMap = {}; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + const pindex = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectPindex; + + if ( materialMap[ pindex ] === undefined ) materialMap[ pindex ] = []; + + materialMap[ pindex ].push( triangleProperty ); + + } + + // + + const keys = Object.keys( materialMap ); + const meshes = []; + + for ( let i = 0, l = keys.length; i < l; i ++ ) { + + const materialIndex = keys[ i ]; + const trianglePropertiesProps = materialMap[ materialIndex ]; + const basematerialData = basematerials.basematerials[ materialIndex ]; + const material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial ); + + // + + const geometry = new BufferGeometry(); + + const positionData = []; + + const vertices = meshData.vertices; + + for ( let j = 0, jl = trianglePropertiesProps.length; j < jl; j ++ ) { + + const triangleProperty = trianglePropertiesProps[ j ]; + + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] ); + + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) ); + + // + + const mesh = new Mesh( geometry, material ); + meshes.push( mesh ); + + } + + return meshes; + + } + + function buildTexturedMesh( texture2dgroup, triangleProperties, meshData, objects, modelData, textureData, objectData ) { + + // geometry + + const geometry = new BufferGeometry(); + + const positionData = []; + const uvData = []; + + const vertices = meshData.vertices; + const uvs = texture2dgroup.uvs; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] ); + + // + + uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] ); + uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] ); + + uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 0 ] ); + uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 1 ] ); + + uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 0 ] ); + uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 1 ] ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) ); + geometry.setAttribute( 'uv', new Float32BufferAttribute( uvData, 2 ) ); + + // material + + const texture = getBuild( texture2dgroup, objects, modelData, textureData, objectData, buildTexture ); + + const material = new MeshPhongMaterial( { map: texture, flatShading: true } ); + + // mesh + + const mesh = new Mesh( geometry, material ); + + return mesh; + + } + + function buildVertexColorMesh( colorgroup, triangleProperties, meshData, objectData ) { + + // geometry + + const geometry = new BufferGeometry(); + + const positionData = []; + const colorData = []; + + const vertices = meshData.vertices; + const colors = colorgroup.colors; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + + const v1 = triangleProperty.v1; + const v2 = triangleProperty.v2; + const v3 = triangleProperty.v3; + + positionData.push( vertices[ ( v1 * 3 ) + 0 ] ); + positionData.push( vertices[ ( v1 * 3 ) + 1 ] ); + positionData.push( vertices[ ( v1 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( v2 * 3 ) + 0 ] ); + positionData.push( vertices[ ( v2 * 3 ) + 1 ] ); + positionData.push( vertices[ ( v2 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( v3 * 3 ) + 0 ] ); + positionData.push( vertices[ ( v3 * 3 ) + 1 ] ); + positionData.push( vertices[ ( v3 * 3 ) + 2 ] ); + + // + + const p1 = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectData.pindex; + const p2 = ( triangleProperty.p2 !== undefined ) ? triangleProperty.p2 : p1; + const p3 = ( triangleProperty.p3 !== undefined ) ? triangleProperty.p3 : p1; + + colorData.push( colors[ ( p1 * 3 ) + 0 ] ); + colorData.push( colors[ ( p1 * 3 ) + 1 ] ); + colorData.push( colors[ ( p1 * 3 ) + 2 ] ); + + colorData.push( colors[ ( p2 * 3 ) + 0 ] ); + colorData.push( colors[ ( p2 * 3 ) + 1 ] ); + colorData.push( colors[ ( p2 * 3 ) + 2 ] ); + + colorData.push( colors[ ( p3 * 3 ) + 0 ] ); + colorData.push( colors[ ( p3 * 3 ) + 1 ] ); + colorData.push( colors[ ( p3 * 3 ) + 2 ] ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colorData, 3 ) ); + + // material + + const material = new MeshPhongMaterial( { vertexColors: true, flatShading: true } ); + + // mesh + + const mesh = new Mesh( geometry, material ); + + return mesh; + + } + + function buildDefaultMesh( meshData ) { + + const geometry = new BufferGeometry(); + geometry.setIndex( new BufferAttribute( meshData[ 'triangles' ], 1 ) ); + geometry.setAttribute( 'position', new BufferAttribute( meshData[ 'vertices' ], 3 ) ); + + const material = new MeshPhongMaterial( { color: 0xffffff, flatShading: true } ); + + const mesh = new Mesh( geometry, material ); + + return mesh; + + } + + function buildMeshes( resourceMap, meshData, objects, modelData, textureData, objectData ) { + + const keys = Object.keys( resourceMap ); + const meshes = []; + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + const resourceId = keys[ i ]; + const triangleProperties = resourceMap[ resourceId ]; + const resourceType = getResourceType( resourceId, modelData ); + + switch ( resourceType ) { + + case 'material': + const basematerials = modelData.resources.basematerials[ resourceId ]; + const newMeshes = buildBasematerialsMeshes( basematerials, triangleProperties, meshData, objects, modelData, textureData, objectData ); + + for ( let j = 0, jl = newMeshes.length; j < jl; j ++ ) { + + meshes.push( newMeshes[ j ] ); + + } + + break; + + case 'texture': + const texture2dgroup = modelData.resources.texture2dgroup[ resourceId ]; + meshes.push( buildTexturedMesh( texture2dgroup, triangleProperties, meshData, objects, modelData, textureData, objectData ) ); + break; + + case 'vertexColors': + const colorgroup = modelData.resources.colorgroup[ resourceId ]; + meshes.push( buildVertexColorMesh( colorgroup, triangleProperties, meshData, objectData ) ); + break; + + case 'default': + meshes.push( buildDefaultMesh( meshData ) ); + break; + + default: + console.error( 'THREE.3MFLoader: Unsupported resource type.' ); + + } + + } + + if ( objectData.name ) { + + for ( let i = 0; i < meshes.length; i ++ ) { + + meshes[ i ].name = objectData.name; + + } + + } + + return meshes; + + } + + function getResourceType( pid, modelData ) { + + if ( modelData.resources.texture2dgroup[ pid ] !== undefined ) { + + return 'texture'; + + } else if ( modelData.resources.basematerials[ pid ] !== undefined ) { + + return 'material'; + + } else if ( modelData.resources.colorgroup[ pid ] !== undefined ) { + + return 'vertexColors'; + + } else if ( pid === 'default' ) { + + return 'default'; + + } else { + + return undefined; + + } + + } + + function analyzeObject( meshData, objectData ) { + + const resourceMap = {}; + + const triangleProperties = meshData[ 'triangleProperties' ]; + + const objectPid = objectData.pid; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + let pid = ( triangleProperty.pid !== undefined ) ? triangleProperty.pid : objectPid; + + if ( pid === undefined ) pid = 'default'; + + if ( resourceMap[ pid ] === undefined ) resourceMap[ pid ] = []; + + resourceMap[ pid ].push( triangleProperty ); + + } + + return resourceMap; + + } + + function buildGroup( meshData, objects, modelData, textureData, objectData ) { + + const group = new Group(); + + const resourceMap = analyzeObject( meshData, objectData ); + const meshes = buildMeshes( resourceMap, meshData, objects, modelData, textureData, objectData ); + + for ( let i = 0, l = meshes.length; i < l; i ++ ) { + + group.add( meshes[ i ] ); + + } + + return group; + + } + + function applyExtensions( extensions, meshData, modelXml ) { + + if ( ! extensions ) { + + return; + + } + + const availableExtensions = []; + const keys = Object.keys( extensions ); + + for ( let i = 0; i < keys.length; i ++ ) { + + const ns = keys[ i ]; + + for ( let j = 0; j < scope.availableExtensions.length; j ++ ) { + + const extension = scope.availableExtensions[ j ]; + + if ( extension.ns === ns ) { + + availableExtensions.push( extension ); + + } + + } + + } + + for ( let i = 0; i < availableExtensions.length; i ++ ) { + + const extension = availableExtensions[ i ]; + extension.apply( modelXml, extensions[ extension[ 'ns' ] ], meshData ); + + } + + } + + function getBuild( data, objects, modelData, textureData, objectData, builder ) { + + if ( data.build !== undefined ) return data.build; + + data.build = builder( data, objects, modelData, textureData, objectData ); + + return data.build; + + } + + function buildBasematerial( materialData, objects, modelData ) { + + let material; + + const displaypropertiesid = materialData.displaypropertiesid; + const pbmetallicdisplayproperties = modelData.resources.pbmetallicdisplayproperties; + + if ( displaypropertiesid !== null && pbmetallicdisplayproperties[ displaypropertiesid ] !== undefined ) { + + // metallic display property, use StandardMaterial + + const pbmetallicdisplayproperty = pbmetallicdisplayproperties[ displaypropertiesid ]; + const metallicData = pbmetallicdisplayproperty.data[ materialData.index ]; + + material = new MeshStandardMaterial( { flatShading: true, roughness: metallicData.roughness, metalness: metallicData.metallicness } ); + + } else { + + // otherwise use PhongMaterial + + material = new MeshPhongMaterial( { flatShading: true } ); + + } + + material.name = materialData.name; + + // displaycolor MUST be specified with a value of a 6 or 8 digit hexadecimal number, e.g. "#RRGGBB" or "#RRGGBBAA" + + const displaycolor = materialData.displaycolor; + + const color = displaycolor.substring( 0, 7 ); + material.color.setStyle( color ); + material.color.convertSRGBToLinear(); // displaycolor is in sRGB + + // process alpha if set + + if ( displaycolor.length === 9 ) { + + material.opacity = parseInt( displaycolor.charAt( 7 ) + displaycolor.charAt( 8 ), 16 ) / 255; + + } + + return material; + + } + + function buildComposite( compositeData, objects, modelData, textureData ) { + + const composite = new Group(); + + for ( let j = 0; j < compositeData.length; j ++ ) { + + const component = compositeData[ j ]; + let build = objects[ component.objectId ]; + + if ( build === undefined ) { + + buildObject( component.objectId, objects, modelData, textureData ); + build = objects[ component.objectId ]; + + } + + const object3D = build.clone(); + + // apply component transform + + const transform = component.transform; + + if ( transform ) { + + object3D.applyMatrix4( transform ); + + } + + composite.add( object3D ); + + } + + return composite; + + } + + function buildObject( objectId, objects, modelData, textureData ) { + + const objectData = modelData[ 'resources' ][ 'object' ][ objectId ]; + + if ( objectData[ 'mesh' ] ) { + + const meshData = objectData[ 'mesh' ]; + + const extensions = modelData[ 'extensions' ]; + const modelXml = modelData[ 'xml' ]; + + applyExtensions( extensions, meshData, modelXml ); + + objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildGroup ); + + } else { + + const compositeData = objectData[ 'components' ]; + + objects[ objectData.id ] = getBuild( compositeData, objects, modelData, textureData, objectData, buildComposite ); + + } + + if ( objectData.name ) { + + objects[ objectData.id ].name = objectData.name; + + } + + } + + function buildObjects( data3mf ) { + + const modelsData = data3mf.model; + const modelRels = data3mf.modelRels; + const objects = {}; + const modelsKeys = Object.keys( modelsData ); + const textureData = {}; + + // evaluate model relationships to textures + + if ( modelRels ) { + + for ( let i = 0, l = modelRels.length; i < l; i ++ ) { + + const modelRel = modelRels[ i ]; + const textureKey = modelRel.target.substring( 1 ); + + if ( data3mf.texture[ textureKey ] ) { + + textureData[ modelRel.target ] = data3mf.texture[ textureKey ]; + + } + + } + + } + + // start build + + for ( let i = 0; i < modelsKeys.length; i ++ ) { + + const modelsKey = modelsKeys[ i ]; + const modelData = modelsData[ modelsKey ]; + + const objectIds = Object.keys( modelData[ 'resources' ][ 'object' ] ); + + for ( let j = 0; j < objectIds.length; j ++ ) { + + const objectId = objectIds[ j ]; + + buildObject( objectId, objects, modelData, textureData ); + + } + + } + + return objects; + + } + + function fetch3DModelPart( rels ) { + + for ( let i = 0; i < rels.length; i ++ ) { + + const rel = rels[ i ]; + const extension = rel.target.split( '.' ).pop(); + + if ( extension.toLowerCase() === 'model' ) return rel; + + } + + } + + function build( objects, data3mf ) { + + const group = new Group(); + + const relationship = fetch3DModelPart( data3mf[ 'rels' ] ); + const buildData = data3mf.model[ relationship[ 'target' ].substring( 1 ) ][ 'build' ]; + + for ( let i = 0; i < buildData.length; i ++ ) { + + const buildItem = buildData[ i ]; + const object3D = objects[ buildItem[ 'objectId' ] ].clone(); + + // apply transform + + const transform = buildItem[ 'transform' ]; + + if ( transform ) { + + object3D.applyMatrix4( transform ); + + } + + group.add( object3D ); + + } + + return group; + + } + + const data3mf = loadDocument( data ); + const objects = buildObjects( data3mf ); + + return build( objects, data3mf ); + + } + + addExtension( extension ) { + + this.availableExtensions.push( extension ); + + } + +} + +export { ThreeMFLoader }; diff --git a/jsm/loaders/AMFLoader.js b/jsm/loaders/AMFLoader.js new file mode 100644 index 0000000..1d870cc --- /dev/null +++ b/jsm/loaders/AMFLoader.js @@ -0,0 +1,518 @@ +import { + BufferGeometry, + Color, + FileLoader, + Float32BufferAttribute, + Group, + Loader, + LoaderUtils, + Mesh, + MeshPhongMaterial +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +/** + * Description: Early release of an AMF Loader following the pattern of the + * example loaders in the three.js project. + * + * Usage: + * const loader = new AMFLoader(); + * loader.load('/path/to/project.amf', function(objecttree) { + * scene.add(objecttree); + * }); + * + * Materials now supported, material colors supported + * Zip support, requires fflate + * No constellation support (yet)! + * + */ + +class AMFLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + function loadDocument( data ) { + + let view = new DataView( data ); + const magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) ); + + if ( magic === 'PK' ) { + + let zip = null; + let file = null; + + console.log( 'THREE.AMFLoader: Loading Zip' ); + + try { + + zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef + + } catch ( e ) { + + if ( e instanceof ReferenceError ) { + + console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' ); + return null; + + } + + } + + for ( file in zip ) { + + if ( file.toLowerCase().slice( - 4 ) === '.amf' ) { + + break; + + } + + } + + console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file ); + view = new DataView( zip[ file ].buffer ); + + } + + const fileText = LoaderUtils.decodeText( view ); + const xmlData = new DOMParser().parseFromString( fileText, 'application/xml' ); + + if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) { + + console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' ); + return null; + + } + + return xmlData; + + } + + function loadDocumentScale( node ) { + + let scale = 1.0; + let unit = 'millimeter'; + + if ( node.documentElement.attributes.unit !== undefined ) { + + unit = node.documentElement.attributes.unit.value.toLowerCase(); + + } + + const scaleUnits = { + millimeter: 1.0, + inch: 25.4, + feet: 304.8, + meter: 1000.0, + micron: 0.001 + }; + + if ( scaleUnits[ unit ] !== undefined ) { + + scale = scaleUnits[ unit ]; + + } + + console.log( 'THREE.AMFLoader: Unit scale: ' + scale ); + return scale; + + } + + function loadMaterials( node ) { + + let matName = 'AMF Material'; + const matId = node.attributes.id.textContent; + let color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; + + let loadedMaterial = null; + + for ( let i = 0; i < node.childNodes.length; i ++ ) { + + const matChildEl = node.childNodes[ i ]; + + if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) { + + if ( matChildEl.attributes.type.value === 'name' ) { + + matName = matChildEl.textContent; + + } + + } else if ( matChildEl.nodeName === 'color' ) { + + color = loadColor( matChildEl ); + + } + + } + + loadedMaterial = new MeshPhongMaterial( { + flatShading: true, + color: new Color( color.r, color.g, color.b ), + name: matName + } ); + + if ( color.a !== 1.0 ) { + + loadedMaterial.transparent = true; + loadedMaterial.opacity = color.a; + + } + + return { id: matId, material: loadedMaterial }; + + } + + function loadColor( node ) { + + const color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; + + for ( let i = 0; i < node.childNodes.length; i ++ ) { + + const matColor = node.childNodes[ i ]; + + if ( matColor.nodeName === 'r' ) { + + color.r = matColor.textContent; + + } else if ( matColor.nodeName === 'g' ) { + + color.g = matColor.textContent; + + } else if ( matColor.nodeName === 'b' ) { + + color.b = matColor.textContent; + + } else if ( matColor.nodeName === 'a' ) { + + color.a = matColor.textContent; + + } + + } + + return color; + + } + + function loadMeshVolume( node ) { + + const volume = { name: '', triangles: [], materialid: null }; + + let currVolumeNode = node.firstElementChild; + + if ( node.attributes.materialid !== undefined ) { + + volume.materialId = node.attributes.materialid.nodeValue; + + } + + while ( currVolumeNode ) { + + if ( currVolumeNode.nodeName === 'metadata' ) { + + if ( currVolumeNode.attributes.type !== undefined ) { + + if ( currVolumeNode.attributes.type.value === 'name' ) { + + volume.name = currVolumeNode.textContent; + + } + + } + + } else if ( currVolumeNode.nodeName === 'triangle' ) { + + const v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent; + const v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent; + const v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent; + + volume.triangles.push( v1, v2, v3 ); + + } + + currVolumeNode = currVolumeNode.nextElementSibling; + + } + + return volume; + + } + + function loadMeshVertices( node ) { + + const vertArray = []; + const normalArray = []; + let currVerticesNode = node.firstElementChild; + + while ( currVerticesNode ) { + + if ( currVerticesNode.nodeName === 'vertex' ) { + + let vNode = currVerticesNode.firstElementChild; + + while ( vNode ) { + + if ( vNode.nodeName === 'coordinates' ) { + + const x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent; + const y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent; + const z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent; + + vertArray.push( x, y, z ); + + } else if ( vNode.nodeName === 'normal' ) { + + const nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent; + const ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent; + const nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent; + + normalArray.push( nx, ny, nz ); + + } + + vNode = vNode.nextElementSibling; + + } + + } + + currVerticesNode = currVerticesNode.nextElementSibling; + + } + + return { 'vertices': vertArray, 'normals': normalArray }; + + } + + function loadObject( node ) { + + const objId = node.attributes.id.textContent; + const loadedObject = { name: 'amfobject', meshes: [] }; + let currColor = null; + let currObjNode = node.firstElementChild; + + while ( currObjNode ) { + + if ( currObjNode.nodeName === 'metadata' ) { + + if ( currObjNode.attributes.type !== undefined ) { + + if ( currObjNode.attributes.type.value === 'name' ) { + + loadedObject.name = currObjNode.textContent; + + } + + } + + } else if ( currObjNode.nodeName === 'color' ) { + + currColor = loadColor( currObjNode ); + + } else if ( currObjNode.nodeName === 'mesh' ) { + + let currMeshNode = currObjNode.firstElementChild; + const mesh = { vertices: [], normals: [], volumes: [], color: currColor }; + + while ( currMeshNode ) { + + if ( currMeshNode.nodeName === 'vertices' ) { + + const loadedVertices = loadMeshVertices( currMeshNode ); + + mesh.normals = mesh.normals.concat( loadedVertices.normals ); + mesh.vertices = mesh.vertices.concat( loadedVertices.vertices ); + + } else if ( currMeshNode.nodeName === 'volume' ) { + + mesh.volumes.push( loadMeshVolume( currMeshNode ) ); + + } + + currMeshNode = currMeshNode.nextElementSibling; + + } + + loadedObject.meshes.push( mesh ); + + } + + currObjNode = currObjNode.nextElementSibling; + + } + + return { 'id': objId, 'obj': loadedObject }; + + } + + const xmlData = loadDocument( data ); + let amfName = ''; + let amfAuthor = ''; + const amfScale = loadDocumentScale( xmlData ); + const amfMaterials = {}; + const amfObjects = {}; + const childNodes = xmlData.documentElement.childNodes; + + let i, j; + + for ( i = 0; i < childNodes.length; i ++ ) { + + const child = childNodes[ i ]; + + if ( child.nodeName === 'metadata' ) { + + if ( child.attributes.type !== undefined ) { + + if ( child.attributes.type.value === 'name' ) { + + amfName = child.textContent; + + } else if ( child.attributes.type.value === 'author' ) { + + amfAuthor = child.textContent; + + } + + } + + } else if ( child.nodeName === 'material' ) { + + const loadedMaterial = loadMaterials( child ); + + amfMaterials[ loadedMaterial.id ] = loadedMaterial.material; + + } else if ( child.nodeName === 'object' ) { + + const loadedObject = loadObject( child ); + + amfObjects[ loadedObject.id ] = loadedObject.obj; + + } + + } + + const sceneObject = new Group(); + const defaultMaterial = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } ); + + sceneObject.name = amfName; + sceneObject.userData.author = amfAuthor; + sceneObject.userData.loader = 'AMF'; + + for ( const id in amfObjects ) { + + const part = amfObjects[ id ]; + const meshes = part.meshes; + const newObject = new Group(); + newObject.name = part.name || ''; + + for ( i = 0; i < meshes.length; i ++ ) { + + let objDefaultMaterial = defaultMaterial; + const mesh = meshes[ i ]; + const vertices = new Float32BufferAttribute( mesh.vertices, 3 ); + let normals = null; + + if ( mesh.normals.length ) { + + normals = new Float32BufferAttribute( mesh.normals, 3 ); + + } + + if ( mesh.color ) { + + const color = mesh.color; + + objDefaultMaterial = defaultMaterial.clone(); + objDefaultMaterial.color = new Color( color.r, color.g, color.b ); + + if ( color.a !== 1.0 ) { + + objDefaultMaterial.transparent = true; + objDefaultMaterial.opacity = color.a; + + } + + } + + const volumes = mesh.volumes; + + for ( j = 0; j < volumes.length; j ++ ) { + + const volume = volumes[ j ]; + const newGeometry = new BufferGeometry(); + let material = objDefaultMaterial; + + newGeometry.setIndex( volume.triangles ); + newGeometry.setAttribute( 'position', vertices.clone() ); + + if ( normals ) { + + newGeometry.setAttribute( 'normal', normals.clone() ); + + } + + if ( amfMaterials[ volume.materialId ] !== undefined ) { + + material = amfMaterials[ volume.materialId ]; + + } + + newGeometry.scale( amfScale, amfScale, amfScale ); + newObject.add( new Mesh( newGeometry, material.clone() ) ); + + } + + } + + sceneObject.add( newObject ); + + } + + return sceneObject; + + } + +} + +export { AMFLoader }; diff --git a/jsm/loaders/BVHLoader.js b/jsm/loaders/BVHLoader.js new file mode 100644 index 0000000..d30d615 --- /dev/null +++ b/jsm/loaders/BVHLoader.js @@ -0,0 +1,437 @@ +import { + AnimationClip, + Bone, + FileLoader, + Loader, + Quaternion, + QuaternionKeyframeTrack, + Skeleton, + Vector3, + VectorKeyframeTrack +} from 'three'; + +/** + * Description: reads BVH files and outputs a single Skeleton and an AnimationClip + * + * Currently only supports bvh files containing a single root. + * + */ + +class BVHLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.animateBonePositions = true; + this.animateBoneRotations = true; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( text ) { + + /* + reads a string array (lines) from a BVH file + and outputs a skeleton structure including motion data + + returns thee root node: + { name: '', channels: [], children: [] } + */ + function readBvh( lines ) { + + // read model structure + + if ( nextLine( lines ) !== 'HIERARCHY' ) { + + console.error( 'THREE.BVHLoader: HIERARCHY expected.' ); + + } + + const list = []; // collects flat array of all bones + const root = readNode( lines, nextLine( lines ), list ); + + // read motion data + + if ( nextLine( lines ) !== 'MOTION' ) { + + console.error( 'THREE.BVHLoader: MOTION expected.' ); + + } + + // number of frames + + let tokens = nextLine( lines ).split( /[\s]+/ ); + const numFrames = parseInt( tokens[ 1 ] ); + + if ( isNaN( numFrames ) ) { + + console.error( 'THREE.BVHLoader: Failed to read number of frames.' ); + + } + + // frame time + + tokens = nextLine( lines ).split( /[\s]+/ ); + const frameTime = parseFloat( tokens[ 2 ] ); + + if ( isNaN( frameTime ) ) { + + console.error( 'THREE.BVHLoader: Failed to read frame time.' ); + + } + + // read frame data line by line + + for ( let i = 0; i < numFrames; i ++ ) { + + tokens = nextLine( lines ).split( /[\s]+/ ); + readFrameData( tokens, i * frameTime, root ); + + } + + return list; + + } + + /* + Recursively reads data from a single frame into the bone hierarchy. + The passed bone hierarchy has to be structured in the same order as the BVH file. + keyframe data is stored in bone.frames. + + - data: splitted string array (frame values), values are shift()ed so + this should be empty after parsing the whole hierarchy. + - frameTime: playback time for this keyframe. + - bone: the bone to read frame data from. + */ + function readFrameData( data, frameTime, bone ) { + + // end sites have no motion data + + if ( bone.type === 'ENDSITE' ) return; + + // add keyframe + + const keyframe = { + time: frameTime, + position: new Vector3(), + rotation: new Quaternion() + }; + + bone.frames.push( keyframe ); + + const quat = new Quaternion(); + + const vx = new Vector3( 1, 0, 0 ); + const vy = new Vector3( 0, 1, 0 ); + const vz = new Vector3( 0, 0, 1 ); + + // parse values for each channel in node + + for ( let i = 0; i < bone.channels.length; i ++ ) { + + switch ( bone.channels[ i ] ) { + + case 'Xposition': + keyframe.position.x = parseFloat( data.shift().trim() ); + break; + case 'Yposition': + keyframe.position.y = parseFloat( data.shift().trim() ); + break; + case 'Zposition': + keyframe.position.z = parseFloat( data.shift().trim() ); + break; + case 'Xrotation': + quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 ); + keyframe.rotation.multiply( quat ); + break; + case 'Yrotation': + quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 ); + keyframe.rotation.multiply( quat ); + break; + case 'Zrotation': + quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 ); + keyframe.rotation.multiply( quat ); + break; + default: + console.warn( 'THREE.BVHLoader: Invalid channel type.' ); + + } + + } + + // parse child nodes + + for ( let i = 0; i < bone.children.length; i ++ ) { + + readFrameData( data, frameTime, bone.children[ i ] ); + + } + + } + + /* + Recursively parses the HIERACHY section of the BVH file + + - lines: all lines of the file. lines are consumed as we go along. + - firstline: line containing the node type and name e.g. 'JOINT hip' + - list: collects a flat list of nodes + + returns: a BVH node including children + */ + function readNode( lines, firstline, list ) { + + const node = { name: '', type: '', frames: [] }; + list.push( node ); + + // parse node type and name + + let tokens = firstline.split( /[\s]+/ ); + + if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) { + + node.type = 'ENDSITE'; + node.name = 'ENDSITE'; // bvh end sites have no name + + } else { + + node.name = tokens[ 1 ]; + node.type = tokens[ 0 ].toUpperCase(); + + } + + if ( nextLine( lines ) !== '{' ) { + + console.error( 'THREE.BVHLoader: Expected opening { after type & name' ); + + } + + // parse OFFSET + + tokens = nextLine( lines ).split( /[\s]+/ ); + + if ( tokens[ 0 ] !== 'OFFSET' ) { + + console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] ); + + } + + if ( tokens.length !== 4 ) { + + console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' ); + + } + + const offset = new Vector3( + parseFloat( tokens[ 1 ] ), + parseFloat( tokens[ 2 ] ), + parseFloat( tokens[ 3 ] ) + ); + + if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) { + + console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' ); + + } + + node.offset = offset; + + // parse CHANNELS definitions + + if ( node.type !== 'ENDSITE' ) { + + tokens = nextLine( lines ).split( /[\s]+/ ); + + if ( tokens[ 0 ] !== 'CHANNELS' ) { + + console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' ); + + } + + const numChannels = parseInt( tokens[ 1 ] ); + node.channels = tokens.splice( 2, numChannels ); + node.children = []; + + } + + // read children + + while ( true ) { + + const line = nextLine( lines ); + + if ( line === '}' ) { + + return node; + + } else { + + node.children.push( readNode( lines, line, list ) ); + + } + + } + + } + + /* + recursively converts the internal bvh node structure to a Bone hierarchy + + source: the bvh root node + list: pass an empty array, collects a flat list of all converted THREE.Bones + + returns the root Bone + */ + function toTHREEBone( source, list ) { + + const bone = new Bone(); + list.push( bone ); + + bone.position.add( source.offset ); + bone.name = source.name; + + if ( source.type !== 'ENDSITE' ) { + + for ( let i = 0; i < source.children.length; i ++ ) { + + bone.add( toTHREEBone( source.children[ i ], list ) ); + + } + + } + + return bone; + + } + + /* + builds a AnimationClip from the keyframe data saved in each bone. + + bone: bvh root node + + returns: a AnimationClip containing position and quaternion tracks + */ + function toTHREEAnimation( bones ) { + + const tracks = []; + + // create a position and quaternion animation track for each node + + for ( let i = 0; i < bones.length; i ++ ) { + + const bone = bones[ i ]; + + if ( bone.type === 'ENDSITE' ) + continue; + + // track data + + const times = []; + const positions = []; + const rotations = []; + + for ( let j = 0; j < bone.frames.length; j ++ ) { + + const frame = bone.frames[ j ]; + + times.push( frame.time ); + + // the animation system animates the position property, + // so we have to add the joint offset to all values + + positions.push( frame.position.x + bone.offset.x ); + positions.push( frame.position.y + bone.offset.y ); + positions.push( frame.position.z + bone.offset.z ); + + rotations.push( frame.rotation.x ); + rotations.push( frame.rotation.y ); + rotations.push( frame.rotation.z ); + rotations.push( frame.rotation.w ); + + } + + if ( scope.animateBonePositions ) { + + tracks.push( new VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) ); + + } + + if ( scope.animateBoneRotations ) { + + tracks.push( new QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) ); + + } + + } + + return new AnimationClip( 'animation', - 1, tracks ); + + } + + /* + returns the next non-empty line in lines + */ + function nextLine( lines ) { + + let line; + // skip empty lines + while ( ( line = lines.shift().trim() ).length === 0 ) { } + + return line; + + } + + const scope = this; + + const lines = text.split( /[\r\n]+/g ); + + const bones = readBvh( lines ); + + const threeBones = []; + toTHREEBone( bones[ 0 ], threeBones ); + + const threeClip = toTHREEAnimation( bones ); + + return { + skeleton: new Skeleton( threeBones ), + clip: threeClip + }; + + } + +} + +export { BVHLoader }; diff --git a/jsm/loaders/BasisTextureLoader.js b/jsm/loaders/BasisTextureLoader.js new file mode 100644 index 0000000..b9ca9b7 --- /dev/null +++ b/jsm/loaders/BasisTextureLoader.js @@ -0,0 +1,790 @@ +import { + CompressedTexture, + FileLoader, + LinearFilter, + LinearMipmapLinearFilter, + Loader, + RGBAFormat, + RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format, + RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format, + UnsignedByteType +} from 'three'; + +/** + * Loader for Basis Universal GPU Texture Codec. + * + * Basis Universal is a "supercompressed" GPU texture and texture video + * compression system that outputs a highly compressed intermediate file format + * (.basis) that can be quickly transcoded to a wide variety of GPU texture + * compression formats. + * + * This loader parallelizes the transcoding process across a configurable number + * of web workers, before transferring the transcoded compressed texture back + * to the main thread. + */ + +const _taskCache = new WeakMap(); + +class BasisTextureLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.transcoderPath = ''; + this.transcoderBinary = null; + this.transcoderPending = null; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + this.workerConfig = null; + + console.warn( + + 'THREE.BasisTextureLoader: This loader is deprecated, and will be removed in a future release. ' + + 'Instead, use Basis Universal compression in KTX2 (.ktx2) files with THREE.KTX2Loader.' + + ); + + } + + setTranscoderPath( path ) { + + this.transcoderPath = path; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + detectSupport( renderer ) { + + this.workerConfig = { + astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), + etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), + dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), + bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), + pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) + || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) + }; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setResponseType( 'arraybuffer' ); + loader.setWithCredentials( this.withCredentials ); + + const texture = new CompressedTexture(); + + loader.load( url, ( buffer ) => { + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + return cachedTask.promise.then( onLoad ).catch( onError ); + + } + + this._createTexture( [ buffer ] ) + .then( function ( _texture ) { + + texture.copy( _texture ); + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } ) + .catch( onError ); + + }, onProgress, onError ); + + return texture; + + } + + /** Low-level transcoding API, exposed for use by KTX2Loader. */ + parseInternalAsync( options ) { + + const { levels } = options; + + const buffers = new Set(); + + for ( let i = 0; i < levels.length; i ++ ) { + + buffers.add( levels[ i ].data.buffer ); + + } + + return this._createTexture( Array.from( buffers ), { ...options, lowLevel: true } ); + + } + + /** + * @param {ArrayBuffer[]} buffers + * @param {object?} config + * @return {Promise} + */ + _createTexture( buffers, config = {} ) { + + let worker; + let taskID; + + const taskConfig = config; + let taskCost = 0; + + for ( let i = 0; i < buffers.length; i ++ ) { + + taskCost += buffers[ i ].byteLength; + + } + + const texturePending = this._allocateWorker( taskCost ) + .then( ( _worker ) => { + + worker = _worker; + taskID = this.workerNextTaskID ++; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'transcode', id: taskID, buffers: buffers, taskConfig: taskConfig }, buffers ); + + } ); + + } ) + .then( ( message ) => { + + const { mipmaps, width, height, format } = message; + + const texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + + return texture; + + } ); + + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + texturePending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + worker._taskLoad -= taskCost; + delete worker._callbacks[ taskID ]; + + } + + } ); + + // Cache the task result. + _taskCache.set( buffers[ 0 ], { promise: texturePending } ); + + return texturePending; + + } + + _initTranscoder() { + + if ( ! this.transcoderPending ) { + + // Load transcoder wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.transcoderPath ); + jsLoader.setWithCredentials( this.withCredentials ); + const jsContent = new Promise( ( resolve, reject ) => { + + jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject ); + + } ); + + // Load transcoder WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.transcoderPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + binaryLoader.setWithCredentials( this.withCredentials ); + const binaryContent = new Promise( ( resolve, reject ) => { + + binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject ); + + } ); + + this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { + + const fn = BasisTextureLoader.BasisWorker.toString(); + + const body = [ + '/* constants */', + 'let _EngineFormat = ' + JSON.stringify( BasisTextureLoader.EngineFormat ), + 'let _TranscoderFormat = ' + JSON.stringify( BasisTextureLoader.TranscoderFormat ), + 'let _BasisFormat = ' + JSON.stringify( BasisTextureLoader.BasisFormat ), + '/* basis_transcoder.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + this.transcoderBinary = binaryContent; + + } ); + + } + + return this.transcoderPending; + + } + + _allocateWorker( taskCost ) { + + return this._initTranscoder().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskLoad = 0; + + worker.postMessage( { + type: 'init', + config: this.workerConfig, + transcoderBinary: this.transcoderBinary, + } ); + + worker.onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'transcode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' ); + + } + + }; + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + + worker._taskLoad += taskCost; + + return worker; + + } ); + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; i ++ ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + return this; + + } + +} + +/* CONSTANTS */ + +BasisTextureLoader.BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, +}; + +BasisTextureLoader.TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, +}; + +BasisTextureLoader.EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, +}; + + +/* WEB WORKER */ + +BasisTextureLoader.BasisWorker = function () { + + let config; + let transcoderPending; + let BasisModule; + + const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + + onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; + + case 'transcode': + transcoderPending.then( () => { + + try { + + const { width, height, hasAlpha, mipmaps, format } = message.taskConfig.lowLevel + ? transcodeLowLevel( message.taskConfig ) + : transcode( message.buffers[ 0 ] ); + + const buffers = []; + + for ( let i = 0; i < mipmaps.length; ++ i ) { + + buffers.push( mipmaps[ i ].data.buffer ); + + } + + self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers ); + + } catch ( error ) { + + console.error( error ); + + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } + + } ); + break; + + } + + }; + + function init( wasmBinary ) { + + transcoderPending = new Promise( ( resolve ) => { + + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef + + } ).then( () => { + + BasisModule.initializeBasis(); + + } ); + + } + + function transcodeLowLevel( taskConfig ) { + + const { basisFormat, width, height, hasAlpha } = taskConfig; + + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + + const blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat ); + + assert( BasisModule.isFormatSupported( transcoderFormat ), 'THREE.BasisTextureLoader: Unsupported format.' ); + + const mipmaps = []; + + if ( basisFormat === BasisFormat.ETC1S ) { + + const transcoder = new BasisModule.LowLevelETC1SImageTranscoder(); + + const { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData; + + try { + + let ok; + + ok = transcoder.decodePalettes( endpointCount, endpointsData, selectorCount, selectorsData ); + + assert( ok, 'THREE.BasisTextureLoader: decodePalettes() failed.' ); + + ok = transcoder.decodeTables( tablesData ); + + assert( ok, 'THREE.BasisTextureLoader: decodeTables() failed.' ); + + for ( let i = 0; i < taskConfig.levels.length; i ++ ) { + + const level = taskConfig.levels[ i ]; + const imageDesc = taskConfig.globalData.imageDescs[ i ]; + + const dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height ); + const dst = new Uint8Array( dstByteLength ); + + ok = transcoder.transcodeImage( + transcoderFormat, + dst, dstByteLength / blockByteLength, + level.data, + getWidthInBlocks( transcoderFormat, level.width ), + getHeightInBlocks( transcoderFormat, level.height ), + level.width, level.height, level.index, + imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength, + imageDesc.alphaSliceByteOffset, imageDesc.alphaSliceByteLength, + imageDesc.imageFlags, + hasAlpha, + false, + 0, 0 + ); + + assert( ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.' ); + + mipmaps.push( { data: dst, width: level.width, height: level.height } ); + + } + + } finally { + + transcoder.delete(); + + } + + } else { + + for ( let i = 0; i < taskConfig.levels.length; i ++ ) { + + const level = taskConfig.levels[ i ]; + + const dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height ); + const dst = new Uint8Array( dstByteLength ); + + const ok = BasisModule.transcodeUASTCImage( + transcoderFormat, + dst, dstByteLength / blockByteLength, + level.data, + getWidthInBlocks( transcoderFormat, level.width ), + getHeightInBlocks( transcoderFormat, level.height ), + level.width, level.height, level.index, + 0, + level.data.byteLength, + 0, + hasAlpha, + false, + 0, 0, + - 1, - 1 + ); + + assert( ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.' ); + + mipmaps.push( { data: dst, width: level.width, height: level.height } ); + + } + + } + + return { width, height, hasAlpha, mipmaps, format: engineFormat }; + + } + + function transcode( buffer ) { + + const basisFile = new BasisModule.BasisFile( new Uint8Array( buffer ) ); + + const basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + const width = basisFile.getImageWidth( 0, 0 ); + const height = basisFile.getImageHeight( 0, 0 ); + const levels = basisFile.getNumLevels( 0 ); + const hasAlpha = basisFile.getHasAlpha(); + + function cleanup() { + + basisFile.close(); + basisFile.delete(); + + } + + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + + if ( ! width || ! height || ! levels ) { + + cleanup(); + throw new Error( 'THREE.BasisTextureLoader: Invalid texture' ); + + } + + if ( ! basisFile.startTranscoding() ) { + + cleanup(); + throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' ); + + } + + const mipmaps = []; + + for ( let mip = 0; mip < levels; mip ++ ) { + + const mipWidth = basisFile.getImageWidth( 0, mip ); + const mipHeight = basisFile.getImageHeight( 0, mip ); + const dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, transcoderFormat ) ); + + const status = basisFile.transcodeImage( + dst, + 0, + mip, + transcoderFormat, + 0, + hasAlpha + ); + + if ( ! status ) { + + cleanup(); + throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' ); + + } + + mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } ); + + } + + cleanup(); + + return { width, height, hasAlpha, mipmaps, format: engineFormat }; + + } + + // + + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], + engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ]; + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityETC1S - b.priorityETC1S; + + } ); + const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityUASTC - b.priorityUASTC; + + } ); + + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + + let transcoderFormat; + let engineFormat; + + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + + for ( let i = 0; i < options.length; i ++ ) { + + const opt = options[ i ]; + + if ( ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + + transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + + return { transcoderFormat, engineFormat }; + + } + + console.warn( 'THREE.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.' ); + + transcoderFormat = TranscoderFormat.RGBA32; + engineFormat = EngineFormat.RGBAFormat; + + return { transcoderFormat, engineFormat }; + + } + + function assert( ok, message ) { + + if ( ! ok ) throw new Error( message ); + + } + + function getWidthInBlocks( transcoderFormat, width ) { + + return Math.ceil( width / BasisModule.getFormatBlockWidth( transcoderFormat ) ); + + } + + function getHeightInBlocks( transcoderFormat, height ) { + + return Math.ceil( height / BasisModule.getFormatBlockHeight( transcoderFormat ) ); + + } + + function getTranscodedImageByteLength( transcoderFormat, width, height ) { + + const blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat ); + + if ( BasisModule.formatIsUncompressed( transcoderFormat ) ) { + + return width * height * blockByteLength; + + } + + if ( transcoderFormat === TranscoderFormat.PVRTC1_4_RGB + || transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA ) { + + // GL requires extra padding for very small textures: + // https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt + const paddedWidth = ( width + 3 ) & ~ 3; + const paddedHeight = ( height + 3 ) & ~ 3; + + return ( Math.max( 8, paddedWidth ) * Math.max( 8, paddedHeight ) * 4 + 7 ) / 8; + + } + + return ( getWidthInBlocks( transcoderFormat, width ) + * getHeightInBlocks( transcoderFormat, height ) + * blockByteLength ); + + } + + function isPowerOfTwo( value ) { + + if ( value <= 2 ) return true; + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + + } + +}; + +export { BasisTextureLoader }; diff --git a/jsm/loaders/ColladaLoader.js b/jsm/loaders/ColladaLoader.js new file mode 100644 index 0000000..1b3ef85 --- /dev/null +++ b/jsm/loaders/ColladaLoader.js @@ -0,0 +1,4091 @@ +import { + AmbientLight, + AnimationClip, + Bone, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DirectionalLight, + DoubleSide, + Euler, + FileLoader, + Float32BufferAttribute, + FrontSide, + Group, + Line, + LineBasicMaterial, + LineSegments, + Loader, + LoaderUtils, + MathUtils, + Matrix4, + Mesh, + MeshBasicMaterial, + MeshLambertMaterial, + MeshPhongMaterial, + OrthographicCamera, + PerspectiveCamera, + PointLight, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Scene, + Skeleton, + SkinnedMesh, + SpotLight, + TextureLoader, + Vector2, + Vector3, + VectorKeyframeTrack, + sRGBEncoding +} from 'three'; +import { TGALoader } from '../loaders/TGALoader.js'; + +class ColladaLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( scope.path === '' ) ? LoaderUtils.extractUrlBase( url ) : scope.path; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( text, path ) { + + function getElementsByTagName( xml, name ) { + + // Non recursive xml.getElementsByTagName() ... + + const array = []; + const childNodes = xml.childNodes; + + for ( let i = 0, l = childNodes.length; i < l; i ++ ) { + + const child = childNodes[ i ]; + + if ( child.nodeName === name ) { + + array.push( child ); + + } + + } + + return array; + + } + + function parseStrings( text ) { + + if ( text.length === 0 ) return []; + + const parts = text.trim().split( /\s+/ ); + const array = new Array( parts.length ); + + for ( let i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parts[ i ]; + + } + + return array; + + } + + function parseFloats( text ) { + + if ( text.length === 0 ) return []; + + const parts = text.trim().split( /\s+/ ); + const array = new Array( parts.length ); + + for ( let i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parseFloat( parts[ i ] ); + + } + + return array; + + } + + function parseInts( text ) { + + if ( text.length === 0 ) return []; + + const parts = text.trim().split( /\s+/ ); + const array = new Array( parts.length ); + + for ( let i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parseInt( parts[ i ] ); + + } + + return array; + + } + + function parseId( text ) { + + return text.substring( 1 ); + + } + + function generateId() { + + return 'three_default_' + ( count ++ ); + + } + + function isEmpty( object ) { + + return Object.keys( object ).length === 0; + + } + + // asset + + function parseAsset( xml ) { + + return { + unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ), + upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] ) + }; + + } + + function parseAssetUnit( xml ) { + + if ( ( xml !== undefined ) && ( xml.hasAttribute( 'meter' ) === true ) ) { + + return parseFloat( xml.getAttribute( 'meter' ) ); + + } else { + + return 1; // default 1 meter + + } + + } + + function parseAssetUpAxis( xml ) { + + return xml !== undefined ? xml.textContent : 'Y_UP'; + + } + + // library + + function parseLibrary( xml, libraryName, nodeName, parser ) { + + const library = getElementsByTagName( xml, libraryName )[ 0 ]; + + if ( library !== undefined ) { + + const elements = getElementsByTagName( library, nodeName ); + + for ( let i = 0; i < elements.length; i ++ ) { + + parser( elements[ i ] ); + + } + + } + + } + + function buildLibrary( data, builder ) { + + for ( const name in data ) { + + const object = data[ name ]; + object.build = builder( data[ name ] ); + + } + + } + + // get + + function getBuild( data, builder ) { + + if ( data.build !== undefined ) return data.build; + + data.build = builder( data ); + + return data.build; + + } + + // animation + + function parseAnimation( xml ) { + + const data = { + sources: {}, + samplers: {}, + channels: {} + }; + + let hasChildren = false; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + let id; + + switch ( child.nodeName ) { + + case 'source': + id = child.getAttribute( 'id' ); + data.sources[ id ] = parseSource( child ); + break; + + case 'sampler': + id = child.getAttribute( 'id' ); + data.samplers[ id ] = parseAnimationSampler( child ); + break; + + case 'channel': + id = child.getAttribute( 'target' ); + data.channels[ id ] = parseAnimationChannel( child ); + break; + + case 'animation': + // hierarchy of related animations + parseAnimation( child ); + hasChildren = true; + break; + + default: + console.log( child ); + + } + + } + + if ( hasChildren === false ) { + + // since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment + + library.animations[ xml.getAttribute( 'id' ) || MathUtils.generateUUID() ] = data; + + } + + } + + function parseAnimationSampler( xml ) { + + const data = { + inputs: {}, + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const id = parseId( child.getAttribute( 'source' ) ); + const semantic = child.getAttribute( 'semantic' ); + data.inputs[ semantic ] = id; + break; + + } + + } + + return data; + + } + + function parseAnimationChannel( xml ) { + + const data = {}; + + const target = xml.getAttribute( 'target' ); + + // parsing SID Addressing Syntax + + let parts = target.split( '/' ); + + const id = parts.shift(); + let sid = parts.shift(); + + // check selection syntax + + const arraySyntax = ( sid.indexOf( '(' ) !== - 1 ); + const memberSyntax = ( sid.indexOf( '.' ) !== - 1 ); + + if ( memberSyntax ) { + + // member selection access + + parts = sid.split( '.' ); + sid = parts.shift(); + data.member = parts.shift(); + + } else if ( arraySyntax ) { + + // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. + + const indices = sid.split( '(' ); + sid = indices.shift(); + + for ( let i = 0; i < indices.length; i ++ ) { + + indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) ); + + } + + data.indices = indices; + + } + + data.id = id; + data.sid = sid; + + data.arraySyntax = arraySyntax; + data.memberSyntax = memberSyntax; + + data.sampler = parseId( xml.getAttribute( 'source' ) ); + + return data; + + } + + function buildAnimation( data ) { + + const tracks = []; + + const channels = data.channels; + const samplers = data.samplers; + const sources = data.sources; + + for ( const target in channels ) { + + if ( channels.hasOwnProperty( target ) ) { + + const channel = channels[ target ]; + const sampler = samplers[ channel.sampler ]; + + const inputId = sampler.inputs.INPUT; + const outputId = sampler.inputs.OUTPUT; + + const inputSource = sources[ inputId ]; + const outputSource = sources[ outputId ]; + + const animation = buildAnimationChannel( channel, inputSource, outputSource ); + + createKeyframeTracks( animation, tracks ); + + } + + } + + return tracks; + + } + + function getAnimation( id ) { + + return getBuild( library.animations[ id ], buildAnimation ); + + } + + function buildAnimationChannel( channel, inputSource, outputSource ) { + + const node = library.nodes[ channel.id ]; + const object3D = getNode( node.id ); + + const transform = node.transforms[ channel.sid ]; + const defaultMatrix = node.matrix.clone().transpose(); + + let time, stride; + let i, il, j, jl; + + const data = {}; + + // the collada spec allows the animation of data in various ways. + // depending on the transform type (matrix, translate, rotate, scale), we execute different logic + + switch ( transform ) { + + case 'matrix': + + for ( i = 0, il = inputSource.array.length; i < il; i ++ ) { + + time = inputSource.array[ i ]; + stride = i * outputSource.stride; + + if ( data[ time ] === undefined ) data[ time ] = {}; + + if ( channel.arraySyntax === true ) { + + const value = outputSource.array[ stride ]; + const index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ]; + + data[ time ][ index ] = value; + + } else { + + for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) { + + data[ time ][ j ] = outputSource.array[ stride + j ]; + + } + + } + + } + + break; + + case 'translate': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + case 'rotate': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + case 'scale': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + } + + const keyframes = prepareAnimationData( data, defaultMatrix ); + + const animation = { + name: object3D.uuid, + keyframes: keyframes + }; + + return animation; + + } + + function prepareAnimationData( data, defaultMatrix ) { + + const keyframes = []; + + // transfer data into a sortable array + + for ( const time in data ) { + + keyframes.push( { time: parseFloat( time ), value: data[ time ] } ); + + } + + // ensure keyframes are sorted by time + + keyframes.sort( ascending ); + + // now we clean up all animation data, so we can use them for keyframe tracks + + for ( let i = 0; i < 16; i ++ ) { + + transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] ); + + } + + return keyframes; + + // array sort function + + function ascending( a, b ) { + + return a.time - b.time; + + } + + } + + const position = new Vector3(); + const scale = new Vector3(); + const quaternion = new Quaternion(); + + function createKeyframeTracks( animation, tracks ) { + + const keyframes = animation.keyframes; + const name = animation.name; + + const times = []; + const positionData = []; + const quaternionData = []; + const scaleData = []; + + for ( let i = 0, l = keyframes.length; i < l; i ++ ) { + + const keyframe = keyframes[ i ]; + + const time = keyframe.time; + const value = keyframe.value; + + matrix.fromArray( value ).transpose(); + matrix.decompose( position, quaternion, scale ); + + times.push( time ); + positionData.push( position.x, position.y, position.z ); + quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w ); + scaleData.push( scale.x, scale.y, scale.z ); + + } + + if ( positionData.length > 0 ) tracks.push( new VectorKeyframeTrack( name + '.position', times, positionData ) ); + if ( quaternionData.length > 0 ) tracks.push( new QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) ); + if ( scaleData.length > 0 ) tracks.push( new VectorKeyframeTrack( name + '.scale', times, scaleData ) ); + + return tracks; + + } + + function transformAnimationData( keyframes, property, defaultValue ) { + + let keyframe; + + let empty = true; + let i, l; + + // check, if values of a property are missing in our keyframes + + for ( i = 0, l = keyframes.length; i < l; i ++ ) { + + keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] === undefined ) { + + keyframe.value[ property ] = null; // mark as missing + + } else { + + empty = false; + + } + + } + + if ( empty === true ) { + + // no values at all, so we set a default value + + for ( i = 0, l = keyframes.length; i < l; i ++ ) { + + keyframe = keyframes[ i ]; + + keyframe.value[ property ] = defaultValue; + + } + + } else { + + // filling gaps + + createMissingKeyframes( keyframes, property ); + + } + + } + + function createMissingKeyframes( keyframes, property ) { + + let prev, next; + + for ( let i = 0, l = keyframes.length; i < l; i ++ ) { + + const keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] === null ) { + + prev = getPrev( keyframes, i, property ); + next = getNext( keyframes, i, property ); + + if ( prev === null ) { + + keyframe.value[ property ] = next.value[ property ]; + continue; + + } + + if ( next === null ) { + + keyframe.value[ property ] = prev.value[ property ]; + continue; + + } + + interpolate( keyframe, prev, next, property ); + + } + + } + + } + + function getPrev( keyframes, i, property ) { + + while ( i >= 0 ) { + + const keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] !== null ) return keyframe; + + i --; + + } + + return null; + + } + + function getNext( keyframes, i, property ) { + + while ( i < keyframes.length ) { + + const keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] !== null ) return keyframe; + + i ++; + + } + + return null; + + } + + function interpolate( key, prev, next, property ) { + + if ( ( next.time - prev.time ) === 0 ) { + + key.value[ property ] = prev.value[ property ]; + return; + + } + + key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ]; + + } + + // animation clips + + function parseAnimationClip( xml ) { + + const data = { + name: xml.getAttribute( 'id' ) || 'default', + start: parseFloat( xml.getAttribute( 'start' ) || 0 ), + end: parseFloat( xml.getAttribute( 'end' ) || 0 ), + animations: [] + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'instance_animation': + data.animations.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + } + + } + + library.clips[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildAnimationClip( data ) { + + const tracks = []; + + const name = data.name; + const duration = ( data.end - data.start ) || - 1; + const animations = data.animations; + + for ( let i = 0, il = animations.length; i < il; i ++ ) { + + const animationTracks = getAnimation( animations[ i ] ); + + for ( let j = 0, jl = animationTracks.length; j < jl; j ++ ) { + + tracks.push( animationTracks[ j ] ); + + } + + } + + return new AnimationClip( name, duration, tracks ); + + } + + function getAnimationClip( id ) { + + return getBuild( library.clips[ id ], buildAnimationClip ); + + } + + // controller + + function parseController( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'skin': + // there is exactly one skin per controller + data.id = parseId( child.getAttribute( 'source' ) ); + data.skin = parseSkin( child ); + break; + + case 'morph': + data.id = parseId( child.getAttribute( 'source' ) ); + console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' ); + break; + + } + + } + + library.controllers[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseSkin( xml ) { + + const data = { + sources: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_shape_matrix': + data.bindShapeMatrix = parseFloats( child.textContent ); + break; + + case 'source': + const id = child.getAttribute( 'id' ); + data.sources[ id ] = parseSource( child ); + break; + + case 'joints': + data.joints = parseJoints( child ); + break; + + case 'vertex_weights': + data.vertexWeights = parseVertexWeights( child ); + break; + + } + + } + + return data; + + } + + function parseJoints( xml ) { + + const data = { + inputs: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const semantic = child.getAttribute( 'semantic' ); + const id = parseId( child.getAttribute( 'source' ) ); + data.inputs[ semantic ] = id; + break; + + } + + } + + return data; + + } + + function parseVertexWeights( xml ) { + + const data = { + inputs: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const semantic = child.getAttribute( 'semantic' ); + const id = parseId( child.getAttribute( 'source' ) ); + const offset = parseInt( child.getAttribute( 'offset' ) ); + data.inputs[ semantic ] = { id: id, offset: offset }; + break; + + case 'vcount': + data.vcount = parseInts( child.textContent ); + break; + + case 'v': + data.v = parseInts( child.textContent ); + break; + + } + + } + + return data; + + } + + function buildController( data ) { + + const build = { + id: data.id + }; + + const geometry = library.geometries[ build.id ]; + + if ( data.skin !== undefined ) { + + build.skin = buildSkin( data.skin ); + + // we enhance the 'sources' property of the corresponding geometry with our skin data + + geometry.sources.skinIndices = build.skin.indices; + geometry.sources.skinWeights = build.skin.weights; + + } + + return build; + + } + + function buildSkin( data ) { + + const BONE_LIMIT = 4; + + const build = { + joints: [], // this must be an array to preserve the joint order + indices: { + array: [], + stride: BONE_LIMIT + }, + weights: { + array: [], + stride: BONE_LIMIT + } + }; + + const sources = data.sources; + const vertexWeights = data.vertexWeights; + + const vcount = vertexWeights.vcount; + const v = vertexWeights.v; + const jointOffset = vertexWeights.inputs.JOINT.offset; + const weightOffset = vertexWeights.inputs.WEIGHT.offset; + + const jointSource = data.sources[ data.joints.inputs.JOINT ]; + const inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ]; + + const weights = sources[ vertexWeights.inputs.WEIGHT.id ].array; + let stride = 0; + + let i, j, l; + + // procces skin data for each vertex + + for ( i = 0, l = vcount.length; i < l; i ++ ) { + + const jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex + const vertexSkinData = []; + + for ( j = 0; j < jointCount; j ++ ) { + + const skinIndex = v[ stride + jointOffset ]; + const weightId = v[ stride + weightOffset ]; + const skinWeight = weights[ weightId ]; + + vertexSkinData.push( { index: skinIndex, weight: skinWeight } ); + + stride += 2; + + } + + // we sort the joints in descending order based on the weights. + // this ensures, we only procced the most important joints of the vertex + + vertexSkinData.sort( descending ); + + // now we provide for each vertex a set of four index and weight values. + // the order of the skin data matches the order of vertices + + for ( j = 0; j < BONE_LIMIT; j ++ ) { + + const d = vertexSkinData[ j ]; + + if ( d !== undefined ) { + + build.indices.array.push( d.index ); + build.weights.array.push( d.weight ); + + } else { + + build.indices.array.push( 0 ); + build.weights.array.push( 0 ); + + } + + } + + } + + // setup bind matrix + + if ( data.bindShapeMatrix ) { + + build.bindMatrix = new Matrix4().fromArray( data.bindShapeMatrix ).transpose(); + + } else { + + build.bindMatrix = new Matrix4().identity(); + + } + + // process bones and inverse bind matrix data + + for ( i = 0, l = jointSource.array.length; i < l; i ++ ) { + + const name = jointSource.array[ i ]; + const boneInverse = new Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose(); + + build.joints.push( { name: name, boneInverse: boneInverse } ); + + } + + return build; + + // array sort function + + function descending( a, b ) { + + return b.weight - a.weight; + + } + + } + + function getController( id ) { + + return getBuild( library.controllers[ id ], buildController ); + + } + + // image + + function parseImage( xml ) { + + const data = { + init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent + }; + + library.images[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildImage( data ) { + + if ( data.build !== undefined ) return data.build; + + return data.init_from; + + } + + function getImage( id ) { + + const data = library.images[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildImage ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find image with ID:', id ); + + return null; + + } + + // effect + + function parseEffect( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'profile_COMMON': + data.profile = parseEffectProfileCOMMON( child ); + break; + + } + + } + + library.effects[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseEffectProfileCOMMON( xml ) { + + const data = { + surfaces: {}, + samplers: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'newparam': + parseEffectNewparam( child, data ); + break; + + case 'technique': + data.technique = parseEffectTechnique( child ); + break; + + case 'extra': + data.extra = parseEffectExtra( child ); + break; + + } + + } + + return data; + + } + + function parseEffectNewparam( xml, data ) { + + const sid = xml.getAttribute( 'sid' ); + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'surface': + data.surfaces[ sid ] = parseEffectSurface( child ); + break; + + case 'sampler2D': + data.samplers[ sid ] = parseEffectSampler( child ); + break; + + } + + } + + } + + function parseEffectSurface( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'init_from': + data.init_from = child.textContent; + break; + + } + + } + + return data; + + } + + function parseEffectSampler( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'source': + data.source = child.textContent; + break; + + } + + } + + return data; + + } + + function parseEffectTechnique( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'constant': + case 'lambert': + case 'blinn': + case 'phong': + data.type = child.nodeName; + data.parameters = parseEffectParameters( child ); + break; + + case 'extra': + data.extra = parseEffectExtra( child ); + break; + + } + + } + + return data; + + } + + function parseEffectParameters( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'emission': + case 'diffuse': + case 'specular': + case 'bump': + case 'ambient': + case 'shininess': + case 'transparency': + data[ child.nodeName ] = parseEffectParameter( child ); + break; + case 'transparent': + data[ child.nodeName ] = { + opaque: child.hasAttribute( 'opaque' ) ? child.getAttribute( 'opaque' ) : 'A_ONE', + data: parseEffectParameter( child ) + }; + break; + + } + + } + + return data; + + } + + function parseEffectParameter( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + data[ child.nodeName ] = parseFloats( child.textContent ); + break; + + case 'float': + data[ child.nodeName ] = parseFloat( child.textContent ); + break; + + case 'texture': + data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) }; + break; + + } + + } + + return data; + + } + + function parseEffectParameterTexture( xml ) { + + const data = { + technique: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'extra': + parseEffectParameterTextureExtra( child, data ); + break; + + } + + } + + return data; + + } + + function parseEffectParameterTextureExtra( xml, data ) { + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + parseEffectParameterTextureExtraTechnique( child, data ); + break; + + } + + } + + } + + function parseEffectParameterTextureExtraTechnique( xml, data ) { + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'repeatU': + case 'repeatV': + case 'offsetU': + case 'offsetV': + data.technique[ child.nodeName ] = parseFloat( child.textContent ); + break; + + case 'wrapU': + case 'wrapV': + + // some files have values for wrapU/wrapV which become NaN via parseInt + + if ( child.textContent.toUpperCase() === 'TRUE' ) { + + data.technique[ child.nodeName ] = 1; + + } else if ( child.textContent.toUpperCase() === 'FALSE' ) { + + data.technique[ child.nodeName ] = 0; + + } else { + + data.technique[ child.nodeName ] = parseInt( child.textContent ); + + } + + break; + + case 'bump': + data[ child.nodeName ] = parseEffectExtraTechniqueBump( child ); + break; + + } + + } + + } + + function parseEffectExtra( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + data.technique = parseEffectExtraTechnique( child ); + break; + + } + + } + + return data; + + } + + function parseEffectExtraTechnique( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'double_sided': + data[ child.nodeName ] = parseInt( child.textContent ); + break; + + case 'bump': + data[ child.nodeName ] = parseEffectExtraTechniqueBump( child ); + break; + + } + + } + + return data; + + } + + function parseEffectExtraTechniqueBump( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'texture': + data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), texcoord: child.getAttribute( 'texcoord' ), extra: parseEffectParameterTexture( child ) }; + break; + + } + + } + + return data; + + } + + function buildEffect( data ) { + + return data; + + } + + function getEffect( id ) { + + return getBuild( library.effects[ id ], buildEffect ); + + } + + // material + + function parseMaterial( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'instance_effect': + data.url = parseId( child.getAttribute( 'url' ) ); + break; + + } + + } + + library.materials[ xml.getAttribute( 'id' ) ] = data; + + } + + function getTextureLoader( image ) { + + let loader; + + let extension = image.slice( ( image.lastIndexOf( '.' ) - 1 >>> 0 ) + 2 ); // http://www.jstips.co/en/javascript/get-file-extension/ + extension = extension.toLowerCase(); + + switch ( extension ) { + + case 'tga': + loader = tgaLoader; + break; + + default: + loader = textureLoader; + + } + + return loader; + + } + + function buildMaterial( data ) { + + const effect = getEffect( data.url ); + const technique = effect.profile.technique; + + let material; + + switch ( technique.type ) { + + case 'phong': + case 'blinn': + material = new MeshPhongMaterial(); + break; + + case 'lambert': + material = new MeshLambertMaterial(); + break; + + default: + material = new MeshBasicMaterial(); + break; + + } + + material.name = data.name || ''; + + function getTexture( textureObject, encoding = null ) { + + const sampler = effect.profile.samplers[ textureObject.id ]; + let image = null; + + // get image + + if ( sampler !== undefined ) { + + const surface = effect.profile.surfaces[ sampler.source ]; + image = getImage( surface.init_from ); + + } else { + + console.warn( 'THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).' ); + image = getImage( textureObject.id ); + + } + + // create texture if image is avaiable + + if ( image !== null ) { + + const loader = getTextureLoader( image ); + + if ( loader !== undefined ) { + + const texture = loader.load( image ); + + const extra = textureObject.extra; + + if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) { + + const technique = extra.technique; + + texture.wrapS = technique.wrapU ? RepeatWrapping : ClampToEdgeWrapping; + texture.wrapT = technique.wrapV ? RepeatWrapping : ClampToEdgeWrapping; + + texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 ); + texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 ); + + } else { + + texture.wrapS = RepeatWrapping; + texture.wrapT = RepeatWrapping; + + } + + if ( encoding !== null ) { + + texture.encoding = encoding; + + } + + return texture; + + } else { + + console.warn( 'THREE.ColladaLoader: Loader for texture %s not found.', image ); + + return null; + + } + + } else { + + console.warn( 'THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id ); + + return null; + + } + + } + + const parameters = technique.parameters; + + for ( const key in parameters ) { + + const parameter = parameters[ key ]; + + switch ( key ) { + + case 'diffuse': + if ( parameter.color ) material.color.fromArray( parameter.color ); + if ( parameter.texture ) material.map = getTexture( parameter.texture, sRGBEncoding ); + break; + case 'specular': + if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color ); + if ( parameter.texture ) material.specularMap = getTexture( parameter.texture ); + break; + case 'bump': + if ( parameter.texture ) material.normalMap = getTexture( parameter.texture ); + break; + case 'ambient': + if ( parameter.texture ) material.lightMap = getTexture( parameter.texture, sRGBEncoding ); + break; + case 'shininess': + if ( parameter.float && material.shininess ) material.shininess = parameter.float; + break; + case 'emission': + if ( parameter.color && material.emissive ) material.emissive.fromArray( parameter.color ); + if ( parameter.texture ) material.emissiveMap = getTexture( parameter.texture, sRGBEncoding ); + break; + + } + + } + + material.color.convertSRGBToLinear(); + if ( material.specular ) material.specular.convertSRGBToLinear(); + if ( material.emissive ) material.emissive.convertSRGBToLinear(); + + // + + let transparent = parameters[ 'transparent' ]; + let transparency = parameters[ 'transparency' ]; + + // does not exist but + + if ( transparency === undefined && transparent ) { + + transparency = { + float: 1 + }; + + } + + // does not exist but + + if ( transparent === undefined && transparency ) { + + transparent = { + opaque: 'A_ONE', + data: { + color: [ 1, 1, 1, 1 ] + } }; + + } + + if ( transparent && transparency ) { + + // handle case if a texture exists but no color + + if ( transparent.data.texture ) { + + // we do not set an alpha map (see #13792) + + material.transparent = true; + + } else { + + const color = transparent.data.color; + + switch ( transparent.opaque ) { + + case 'A_ONE': + material.opacity = color[ 3 ] * transparency.float; + break; + case 'RGB_ZERO': + material.opacity = 1 - ( color[ 0 ] * transparency.float ); + break; + case 'A_ZERO': + material.opacity = 1 - ( color[ 3 ] * transparency.float ); + break; + case 'RGB_ONE': + material.opacity = color[ 0 ] * transparency.float; + break; + default: + console.warn( 'THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque ); + + } + + if ( material.opacity < 1 ) material.transparent = true; + + } + + } + + // + + + if ( technique.extra !== undefined && technique.extra.technique !== undefined ) { + + const techniques = technique.extra.technique; + + for ( const k in techniques ) { + + const v = techniques[ k ]; + + switch ( k ) { + + case 'double_sided': + material.side = ( v === 1 ? DoubleSide : FrontSide ); + break; + + case 'bump': + material.normalMap = getTexture( v.texture ); + material.normalScale = new Vector2( 1, 1 ); + break; + + } + + } + + } + + return material; + + } + + function getMaterial( id ) { + + return getBuild( library.materials[ id ], buildMaterial ); + + } + + // camera + + function parseCamera( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'optics': + data.optics = parseCameraOptics( child ); + break; + + } + + } + + library.cameras[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseCameraOptics( xml ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'technique_common': + return parseCameraTechnique( child ); + + } + + } + + return {}; + + } + + function parseCameraTechnique( xml ) { + + const data = {}; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'perspective': + case 'orthographic': + + data.technique = child.nodeName; + data.parameters = parseCameraParameters( child ); + + break; + + } + + } + + return data; + + } + + function parseCameraParameters( xml ) { + + const data = {}; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'xfov': + case 'yfov': + case 'xmag': + case 'ymag': + case 'znear': + case 'zfar': + case 'aspect_ratio': + data[ child.nodeName ] = parseFloat( child.textContent ); + break; + + } + + } + + return data; + + } + + function buildCamera( data ) { + + let camera; + + switch ( data.optics.technique ) { + + case 'perspective': + camera = new PerspectiveCamera( + data.optics.parameters.yfov, + data.optics.parameters.aspect_ratio, + data.optics.parameters.znear, + data.optics.parameters.zfar + ); + break; + + case 'orthographic': + let ymag = data.optics.parameters.ymag; + let xmag = data.optics.parameters.xmag; + const aspectRatio = data.optics.parameters.aspect_ratio; + + xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag; + ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag; + + xmag *= 0.5; + ymag *= 0.5; + + camera = new OrthographicCamera( + - xmag, xmag, ymag, - ymag, // left, right, top, bottom + data.optics.parameters.znear, + data.optics.parameters.zfar + ); + break; + + default: + camera = new PerspectiveCamera(); + break; + + } + + camera.name = data.name || ''; + + return camera; + + } + + function getCamera( id ) { + + const data = library.cameras[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildCamera ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find camera with ID:', id ); + + return null; + + } + + // light + + function parseLight( xml ) { + + let data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + data = parseLightTechnique( child ); + break; + + } + + } + + library.lights[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseLightTechnique( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'directional': + case 'point': + case 'spot': + case 'ambient': + + data.technique = child.nodeName; + data.parameters = parseLightParameters( child ); + + } + + } + + return data; + + } + + function parseLightParameters( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + const array = parseFloats( child.textContent ); + data.color = new Color().fromArray( array ).convertSRGBToLinear(); + break; + + case 'falloff_angle': + data.falloffAngle = parseFloat( child.textContent ); + break; + + case 'quadratic_attenuation': + const f = parseFloat( child.textContent ); + data.distance = f ? Math.sqrt( 1 / f ) : 0; + break; + + } + + } + + return data; + + } + + function buildLight( data ) { + + let light; + + switch ( data.technique ) { + + case 'directional': + light = new DirectionalLight(); + break; + + case 'point': + light = new PointLight(); + break; + + case 'spot': + light = new SpotLight(); + break; + + case 'ambient': + light = new AmbientLight(); + break; + + } + + if ( data.parameters.color ) light.color.copy( data.parameters.color ); + if ( data.parameters.distance ) light.distance = data.parameters.distance; + + return light; + + } + + function getLight( id ) { + + const data = library.lights[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildLight ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find light with ID:', id ); + + return null; + + } + + // geometry + + function parseGeometry( xml ) { + + const data = { + name: xml.getAttribute( 'name' ), + sources: {}, + vertices: {}, + primitives: [] + }; + + const mesh = getElementsByTagName( xml, 'mesh' )[ 0 ]; + + // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep + if ( mesh === undefined ) return; + + for ( let i = 0; i < mesh.childNodes.length; i ++ ) { + + const child = mesh.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + const id = child.getAttribute( 'id' ); + + switch ( child.nodeName ) { + + case 'source': + data.sources[ id ] = parseSource( child ); + break; + + case 'vertices': + // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; + data.vertices = parseGeometryVertices( child ); + break; + + case 'polygons': + console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName ); + break; + + case 'lines': + case 'linestrips': + case 'polylist': + case 'triangles': + data.primitives.push( parseGeometryPrimitive( child ) ); + break; + + default: + console.log( child ); + + } + + } + + library.geometries[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseSource( xml ) { + + const data = { + array: [], + stride: 3 + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'float_array': + data.array = parseFloats( child.textContent ); + break; + + case 'Name_array': + data.array = parseStrings( child.textContent ); + break; + + case 'technique_common': + const accessor = getElementsByTagName( child, 'accessor' )[ 0 ]; + + if ( accessor !== undefined ) { + + data.stride = parseInt( accessor.getAttribute( 'stride' ) ); + + } + + break; + + } + + } + + return data; + + } + + function parseGeometryVertices( xml ) { + + const data = {}; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) ); + + } + + return data; + + } + + function parseGeometryPrimitive( xml ) { + + const primitive = { + type: xml.nodeName, + material: xml.getAttribute( 'material' ), + count: parseInt( xml.getAttribute( 'count' ) ), + inputs: {}, + stride: 0, + hasUV: false + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const id = parseId( child.getAttribute( 'source' ) ); + const semantic = child.getAttribute( 'semantic' ); + const offset = parseInt( child.getAttribute( 'offset' ) ); + const set = parseInt( child.getAttribute( 'set' ) ); + const inputname = ( set > 0 ? semantic + set : semantic ); + primitive.inputs[ inputname ] = { id: id, offset: offset }; + primitive.stride = Math.max( primitive.stride, offset + 1 ); + if ( semantic === 'TEXCOORD' ) primitive.hasUV = true; + break; + + case 'vcount': + primitive.vcount = parseInts( child.textContent ); + break; + + case 'p': + primitive.p = parseInts( child.textContent ); + break; + + } + + } + + return primitive; + + } + + function groupPrimitives( primitives ) { + + const build = {}; + + for ( let i = 0; i < primitives.length; i ++ ) { + + const primitive = primitives[ i ]; + + if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = []; + + build[ primitive.type ].push( primitive ); + + } + + return build; + + } + + function checkUVCoordinates( primitives ) { + + let count = 0; + + for ( let i = 0, l = primitives.length; i < l; i ++ ) { + + const primitive = primitives[ i ]; + + if ( primitive.hasUV === true ) { + + count ++; + + } + + } + + if ( count > 0 && count < primitives.length ) { + + primitives.uvsNeedsFix = true; + + } + + } + + function buildGeometry( data ) { + + const build = {}; + + const sources = data.sources; + const vertices = data.vertices; + const primitives = data.primitives; + + if ( primitives.length === 0 ) return {}; + + // our goal is to create one buffer geometry for a single type of primitives + // first, we group all primitives by their type + + const groupedPrimitives = groupPrimitives( primitives ); + + for ( const type in groupedPrimitives ) { + + const primitiveType = groupedPrimitives[ type ]; + + // second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines) + + checkUVCoordinates( primitiveType ); + + // third, create a buffer geometry for each type of primitives + + build[ type ] = buildGeometryType( primitiveType, sources, vertices ); + + } + + return build; + + } + + function buildGeometryType( primitives, sources, vertices ) { + + const build = {}; + + const position = { array: [], stride: 0 }; + const normal = { array: [], stride: 0 }; + const uv = { array: [], stride: 0 }; + const uv2 = { array: [], stride: 0 }; + const color = { array: [], stride: 0 }; + + const skinIndex = { array: [], stride: 4 }; + const skinWeight = { array: [], stride: 4 }; + + const geometry = new BufferGeometry(); + + const materialKeys = []; + + let start = 0; + + for ( let p = 0; p < primitives.length; p ++ ) { + + const primitive = primitives[ p ]; + const inputs = primitive.inputs; + + // groups + + let count = 0; + + switch ( primitive.type ) { + + case 'lines': + case 'linestrips': + count = primitive.count * 2; + break; + + case 'triangles': + count = primitive.count * 3; + break; + + case 'polylist': + + for ( let g = 0; g < primitive.count; g ++ ) { + + const vc = primitive.vcount[ g ]; + + switch ( vc ) { + + case 3: + count += 3; // single triangle + break; + + case 4: + count += 6; // quad, subdivided into two triangles + break; + + default: + count += ( vc - 2 ) * 3; // polylist with more than four vertices + break; + + } + + } + + break; + + default: + console.warn( 'THREE.ColladaLoader: Unknow primitive type:', primitive.type ); + + } + + geometry.addGroup( start, count, p ); + start += count; + + // material + + if ( primitive.material ) { + + materialKeys.push( primitive.material ); + + } + + // geometry data + + for ( const name in inputs ) { + + const input = inputs[ name ]; + + switch ( name ) { + + case 'VERTEX': + for ( const key in vertices ) { + + const id = vertices[ key ]; + + switch ( key ) { + + case 'POSITION': + const prevLength = position.array.length; + buildGeometryData( primitive, sources[ id ], input.offset, position.array ); + position.stride = sources[ id ].stride; + + if ( sources.skinWeights && sources.skinIndices ) { + + buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array ); + buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array ); + + } + + // see #3803 + + if ( primitive.hasUV === false && primitives.uvsNeedsFix === true ) { + + const count = ( position.array.length - prevLength ) / position.stride; + + for ( let i = 0; i < count; i ++ ) { + + // fill missing uv coordinates + + uv.array.push( 0, 0 ); + + } + + } + + break; + + case 'NORMAL': + buildGeometryData( primitive, sources[ id ], input.offset, normal.array ); + normal.stride = sources[ id ].stride; + break; + + case 'COLOR': + buildGeometryData( primitive, sources[ id ], input.offset, color.array ); + color.stride = sources[ id ].stride; + break; + + case 'TEXCOORD': + buildGeometryData( primitive, sources[ id ], input.offset, uv.array ); + uv.stride = sources[ id ].stride; + break; + + case 'TEXCOORD1': + buildGeometryData( primitive, sources[ id ], input.offset, uv2.array ); + uv.stride = sources[ id ].stride; + break; + + default: + console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key ); + + } + + } + + break; + + case 'NORMAL': + buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array ); + normal.stride = sources[ input.id ].stride; + break; + + case 'COLOR': + buildGeometryData( primitive, sources[ input.id ], input.offset, color.array, true ); + color.stride = sources[ input.id ].stride; + break; + + case 'TEXCOORD': + buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array ); + uv.stride = sources[ input.id ].stride; + break; + + case 'TEXCOORD1': + buildGeometryData( primitive, sources[ input.id ], input.offset, uv2.array ); + uv2.stride = sources[ input.id ].stride; + break; + + } + + } + + } + + // build geometry + + if ( position.array.length > 0 ) geometry.setAttribute( 'position', new Float32BufferAttribute( position.array, position.stride ) ); + if ( normal.array.length > 0 ) geometry.setAttribute( 'normal', new Float32BufferAttribute( normal.array, normal.stride ) ); + if ( color.array.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( color.array, color.stride ) ); + if ( uv.array.length > 0 ) geometry.setAttribute( 'uv', new Float32BufferAttribute( uv.array, uv.stride ) ); + if ( uv2.array.length > 0 ) geometry.setAttribute( 'uv2', new Float32BufferAttribute( uv2.array, uv2.stride ) ); + + if ( skinIndex.array.length > 0 ) geometry.setAttribute( 'skinIndex', new Float32BufferAttribute( skinIndex.array, skinIndex.stride ) ); + if ( skinWeight.array.length > 0 ) geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeight.array, skinWeight.stride ) ); + + build.data = geometry; + build.type = primitives[ 0 ].type; + build.materialKeys = materialKeys; + + return build; + + } + + function buildGeometryData( primitive, source, offset, array, isColor = false ) { + + const indices = primitive.p; + const stride = primitive.stride; + const vcount = primitive.vcount; + + function pushVector( i ) { + + let index = indices[ i + offset ] * sourceStride; + const length = index + sourceStride; + + for ( ; index < length; index ++ ) { + + array.push( sourceArray[ index ] ); + + } + + if ( isColor ) { + + // convert the vertex colors from srgb to linear if present + const startIndex = array.length - sourceStride - 1; + tempColor.setRGB( + array[ startIndex + 0 ], + array[ startIndex + 1 ], + array[ startIndex + 2 ] + ).convertSRGBToLinear(); + + array[ startIndex + 0 ] = tempColor.r; + array[ startIndex + 1 ] = tempColor.g; + array[ startIndex + 2 ] = tempColor.b; + + } + + } + + const sourceArray = source.array; + const sourceStride = source.stride; + + if ( primitive.vcount !== undefined ) { + + let index = 0; + + for ( let i = 0, l = vcount.length; i < l; i ++ ) { + + const count = vcount[ i ]; + + if ( count === 4 ) { + + const a = index + stride * 0; + const b = index + stride * 1; + const c = index + stride * 2; + const d = index + stride * 3; + + pushVector( a ); pushVector( b ); pushVector( d ); + pushVector( b ); pushVector( c ); pushVector( d ); + + } else if ( count === 3 ) { + + const a = index + stride * 0; + const b = index + stride * 1; + const c = index + stride * 2; + + pushVector( a ); pushVector( b ); pushVector( c ); + + } else if ( count > 4 ) { + + for ( let k = 1, kl = ( count - 2 ); k <= kl; k ++ ) { + + const a = index + stride * 0; + const b = index + stride * k; + const c = index + stride * ( k + 1 ); + + pushVector( a ); pushVector( b ); pushVector( c ); + + } + + } + + index += stride * count; + + } + + } else { + + for ( let i = 0, l = indices.length; i < l; i += stride ) { + + pushVector( i ); + + } + + } + + } + + function getGeometry( id ) { + + return getBuild( library.geometries[ id ], buildGeometry ); + + } + + // kinematics + + function parseKinematicsModel( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) || '', + joints: {}, + links: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + parseKinematicsTechniqueCommon( child, data ); + break; + + } + + } + + library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildKinematicsModel( data ) { + + if ( data.build !== undefined ) return data.build; + + return data; + + } + + function getKinematicsModel( id ) { + + return getBuild( library.kinematicsModels[ id ], buildKinematicsModel ); + + } + + function parseKinematicsTechniqueCommon( xml, data ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'joint': + data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child ); + break; + + case 'link': + data.links.push( parseKinematicsLink( child ) ); + break; + + } + + } + + } + + function parseKinematicsJoint( xml ) { + + let data; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'prismatic': + case 'revolute': + data = parseKinematicsJointParameter( child ); + break; + + } + + } + + return data; + + } + + function parseKinematicsJointParameter( xml ) { + + const data = { + sid: xml.getAttribute( 'sid' ), + name: xml.getAttribute( 'name' ) || '', + axis: new Vector3(), + limits: { + min: 0, + max: 0 + }, + type: xml.nodeName, + static: false, + zeroPosition: 0, + middlePosition: 0 + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'axis': + const array = parseFloats( child.textContent ); + data.axis.fromArray( array ); + break; + case 'limits': + const max = child.getElementsByTagName( 'max' )[ 0 ]; + const min = child.getElementsByTagName( 'min' )[ 0 ]; + + data.limits.max = parseFloat( max.textContent ); + data.limits.min = parseFloat( min.textContent ); + break; + + } + + } + + // if min is equal to or greater than max, consider the joint static + + if ( data.limits.min >= data.limits.max ) { + + data.static = true; + + } + + // calculate middle position + + data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0; + + return data; + + } + + function parseKinematicsLink( xml ) { + + const data = { + sid: xml.getAttribute( 'sid' ), + name: xml.getAttribute( 'name' ) || '', + attachments: [], + transforms: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'attachment_full': + data.attachments.push( parseKinematicsAttachment( child ) ); + break; + + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push( parseKinematicsTransform( child ) ); + break; + + } + + } + + return data; + + } + + function parseKinematicsAttachment( xml ) { + + const data = { + joint: xml.getAttribute( 'joint' ).split( '/' ).pop(), + transforms: [], + links: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'link': + data.links.push( parseKinematicsLink( child ) ); + break; + + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push( parseKinematicsTransform( child ) ); + break; + + } + + } + + return data; + + } + + function parseKinematicsTransform( xml ) { + + const data = { + type: xml.nodeName + }; + + const array = parseFloats( xml.textContent ); + + switch ( data.type ) { + + case 'matrix': + data.obj = new Matrix4(); + data.obj.fromArray( array ).transpose(); + break; + + case 'translate': + data.obj = new Vector3(); + data.obj.fromArray( array ); + break; + + case 'rotate': + data.obj = new Vector3(); + data.obj.fromArray( array ); + data.angle = MathUtils.degToRad( array[ 3 ] ); + break; + + } + + return data; + + } + + // physics + + function parsePhysicsModel( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) || '', + rigidBodies: {} + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'rigid_body': + data.rigidBodies[ child.getAttribute( 'name' ) ] = {}; + parsePhysicsRigidBody( child, data.rigidBodies[ child.getAttribute( 'name' ) ] ); + break; + + } + + } + + library.physicsModels[ xml.getAttribute( 'id' ) ] = data; + + } + + function parsePhysicsRigidBody( xml, data ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + parsePhysicsTechniqueCommon( child, data ); + break; + + } + + } + + } + + function parsePhysicsTechniqueCommon( xml, data ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'inertia': + data.inertia = parseFloats( child.textContent ); + break; + + case 'mass': + data.mass = parseFloats( child.textContent )[ 0 ]; + break; + + } + + } + + } + + // scene + + function parseKinematicsScene( xml ) { + + const data = { + bindJointAxis: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_joint_axis': + data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) ); + break; + + } + + } + + library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data; + + } + + function parseKinematicsBindJointAxis( xml ) { + + const data = { + target: xml.getAttribute( 'target' ).split( '/' ).pop() + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'axis': + const param = child.getElementsByTagName( 'param' )[ 0 ]; + data.axis = param.textContent; + const tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ]; + data.jointIndex = tmpJointIndex.substring( 0, tmpJointIndex.length - 1 ); + break; + + } + + } + + return data; + + } + + function buildKinematicsScene( data ) { + + if ( data.build !== undefined ) return data.build; + + return data; + + } + + function getKinematicsScene( id ) { + + return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene ); + + } + + function setupKinematics() { + + const kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ]; + const kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ]; + const visualSceneId = Object.keys( library.visualScenes )[ 0 ]; + + if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return; + + const kinematicsModel = getKinematicsModel( kinematicsModelId ); + const kinematicsScene = getKinematicsScene( kinematicsSceneId ); + const visualScene = getVisualScene( visualSceneId ); + + const bindJointAxis = kinematicsScene.bindJointAxis; + const jointMap = {}; + + for ( let i = 0, l = bindJointAxis.length; i < l; i ++ ) { + + const axis = bindJointAxis[ i ]; + + // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' + + const targetElement = collada.querySelector( '[sid="' + axis.target + '"]' ); + + if ( targetElement ) { + + // get the parent of the transform element + + const parentVisualElement = targetElement.parentElement; + + // connect the joint of the kinematics model with the element in the visual scene + + connect( axis.jointIndex, parentVisualElement ); + + } + + } + + function connect( jointIndex, visualElement ) { + + const visualElementName = visualElement.getAttribute( 'name' ); + const joint = kinematicsModel.joints[ jointIndex ]; + + visualScene.traverse( function ( object ) { + + if ( object.name === visualElementName ) { + + jointMap[ jointIndex ] = { + object: object, + transforms: buildTransformList( visualElement ), + joint: joint, + position: joint.zeroPosition + }; + + } + + } ); + + } + + const m0 = new Matrix4(); + + kinematics = { + + joints: kinematicsModel && kinematicsModel.joints, + + getJointValue: function ( jointIndex ) { + + const jointData = jointMap[ jointIndex ]; + + if ( jointData ) { + + return jointData.position; + + } else { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' ); + + } + + }, + + setJointValue: function ( jointIndex, value ) { + + const jointData = jointMap[ jointIndex ]; + + if ( jointData ) { + + const joint = jointData.joint; + + if ( value > joint.limits.max || value < joint.limits.min ) { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' ); + + } else if ( joint.static ) { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' ); + + } else { + + const object = jointData.object; + const axis = joint.axis; + const transforms = jointData.transforms; + + matrix.identity(); + + // each update, we have to apply all transforms in the correct order + + for ( let i = 0; i < transforms.length; i ++ ) { + + const transform = transforms[ i ]; + + // if there is a connection of the transform node with a joint, apply the joint value + + if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) { + + switch ( joint.type ) { + + case 'revolute': + matrix.multiply( m0.makeRotationAxis( axis, MathUtils.degToRad( value ) ) ); + break; + + case 'prismatic': + matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) ); + break; + + default: + console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type ); + break; + + } + + } else { + + switch ( transform.type ) { + + case 'matrix': + matrix.multiply( transform.obj ); + break; + + case 'translate': + matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) ); + break; + + case 'scale': + matrix.scale( transform.obj ); + break; + + case 'rotate': + matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) ); + break; + + } + + } + + } + + object.matrix.copy( matrix ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + + jointMap[ jointIndex ].position = value; + + } + + } else { + + console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' ); + + } + + } + + }; + + } + + function buildTransformList( node ) { + + const transforms = []; + + const xml = collada.querySelector( '[id="' + node.id + '"]' ); + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + let array, vector; + + switch ( child.nodeName ) { + + case 'matrix': + array = parseFloats( child.textContent ); + const matrix = new Matrix4().fromArray( array ).transpose(); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: matrix + } ); + break; + + case 'translate': + case 'scale': + array = parseFloats( child.textContent ); + vector = new Vector3().fromArray( array ); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: vector + } ); + break; + + case 'rotate': + array = parseFloats( child.textContent ); + vector = new Vector3().fromArray( array ); + const angle = MathUtils.degToRad( array[ 3 ] ); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: vector, + angle: angle + } ); + break; + + } + + } + + return transforms; + + } + + // nodes + + function prepareNodes( xml ) { + + const elements = xml.getElementsByTagName( 'node' ); + + // ensure all node elements have id attributes + + for ( let i = 0; i < elements.length; i ++ ) { + + const element = elements[ i ]; + + if ( element.hasAttribute( 'id' ) === false ) { + + element.setAttribute( 'id', generateId() ); + + } + + } + + } + + const matrix = new Matrix4(); + const vector = new Vector3(); + + function parseNode( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) || '', + type: xml.getAttribute( 'type' ), + id: xml.getAttribute( 'id' ), + sid: xml.getAttribute( 'sid' ), + matrix: new Matrix4(), + nodes: [], + instanceCameras: [], + instanceControllers: [], + instanceLights: [], + instanceGeometries: [], + instanceNodes: [], + transforms: {} + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + let array; + + switch ( child.nodeName ) { + + case 'node': + data.nodes.push( child.getAttribute( 'id' ) ); + parseNode( child ); + break; + + case 'instance_camera': + data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'instance_controller': + data.instanceControllers.push( parseNodeInstance( child ) ); + break; + + case 'instance_light': + data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'instance_geometry': + data.instanceGeometries.push( parseNodeInstance( child ) ); + break; + + case 'instance_node': + data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'matrix': + array = parseFloats( child.textContent ); + data.matrix.multiply( matrix.fromArray( array ).transpose() ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'translate': + array = parseFloats( child.textContent ); + vector.fromArray( array ); + data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'rotate': + array = parseFloats( child.textContent ); + const angle = MathUtils.degToRad( array[ 3 ] ); + data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'scale': + array = parseFloats( child.textContent ); + data.matrix.scale( vector.fromArray( array ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'extra': + break; + + default: + console.log( child ); + + } + + } + + if ( hasNode( data.id ) ) { + + console.warn( 'THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id ); + + } else { + + library.nodes[ data.id ] = data; + + } + + return data; + + } + + function parseNodeInstance( xml ) { + + const data = { + id: parseId( xml.getAttribute( 'url' ) ), + materials: {}, + skeletons: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'bind_material': + const instances = child.getElementsByTagName( 'instance_material' ); + + for ( let j = 0; j < instances.length; j ++ ) { + + const instance = instances[ j ]; + const symbol = instance.getAttribute( 'symbol' ); + const target = instance.getAttribute( 'target' ); + + data.materials[ symbol ] = parseId( target ); + + } + + break; + + case 'skeleton': + data.skeletons.push( parseId( child.textContent ) ); + break; + + default: + break; + + } + + } + + return data; + + } + + function buildSkeleton( skeletons, joints ) { + + const boneData = []; + const sortedBoneData = []; + + let i, j, data; + + // a skeleton can have multiple root bones. collada expresses this + // situtation with multiple "skeleton" tags per controller instance + + for ( i = 0; i < skeletons.length; i ++ ) { + + const skeleton = skeletons[ i ]; + + let root; + + if ( hasNode( skeleton ) ) { + + root = getNode( skeleton ); + buildBoneHierarchy( root, joints, boneData ); + + } else if ( hasVisualScene( skeleton ) ) { + + // handle case where the skeleton refers to the visual scene (#13335) + + const visualScene = library.visualScenes[ skeleton ]; + const children = visualScene.children; + + for ( let j = 0; j < children.length; j ++ ) { + + const child = children[ j ]; + + if ( child.type === 'JOINT' ) { + + const root = getNode( child.id ); + buildBoneHierarchy( root, joints, boneData ); + + } + + } + + } else { + + console.error( 'THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton ); + + } + + } + + // sort bone data (the order is defined in the corresponding controller) + + for ( i = 0; i < joints.length; i ++ ) { + + for ( j = 0; j < boneData.length; j ++ ) { + + data = boneData[ j ]; + + if ( data.bone.name === joints[ i ].name ) { + + sortedBoneData[ i ] = data; + data.processed = true; + break; + + } + + } + + } + + // add unprocessed bone data at the end of the list + + for ( i = 0; i < boneData.length; i ++ ) { + + data = boneData[ i ]; + + if ( data.processed === false ) { + + sortedBoneData.push( data ); + data.processed = true; + + } + + } + + // setup arrays for skeleton creation + + const bones = []; + const boneInverses = []; + + for ( i = 0; i < sortedBoneData.length; i ++ ) { + + data = sortedBoneData[ i ]; + + bones.push( data.bone ); + boneInverses.push( data.boneInverse ); + + } + + return new Skeleton( bones, boneInverses ); + + } + + function buildBoneHierarchy( root, joints, boneData ) { + + // setup bone data from visual scene + + root.traverse( function ( object ) { + + if ( object.isBone === true ) { + + let boneInverse; + + // retrieve the boneInverse from the controller data + + for ( let i = 0; i < joints.length; i ++ ) { + + const joint = joints[ i ]; + + if ( joint.name === object.name ) { + + boneInverse = joint.boneInverse; + break; + + } + + } + + if ( boneInverse === undefined ) { + + // Unfortunately, there can be joints in the visual scene that are not part of the + // corresponding controller. In this case, we have to create a dummy boneInverse matrix + // for the respective bone. This bone won't affect any vertices, because there are no skin indices + // and weights defined for it. But we still have to add the bone to the sorted bone list in order to + // ensure a correct animation of the model. + + boneInverse = new Matrix4(); + + } + + boneData.push( { bone: object, boneInverse: boneInverse, processed: false } ); + + } + + } ); + + } + + function buildNode( data ) { + + const objects = []; + + const matrix = data.matrix; + const nodes = data.nodes; + const type = data.type; + const instanceCameras = data.instanceCameras; + const instanceControllers = data.instanceControllers; + const instanceLights = data.instanceLights; + const instanceGeometries = data.instanceGeometries; + const instanceNodes = data.instanceNodes; + + // nodes + + for ( let i = 0, l = nodes.length; i < l; i ++ ) { + + objects.push( getNode( nodes[ i ] ) ); + + } + + // instance cameras + + for ( let i = 0, l = instanceCameras.length; i < l; i ++ ) { + + const instanceCamera = getCamera( instanceCameras[ i ] ); + + if ( instanceCamera !== null ) { + + objects.push( instanceCamera.clone() ); + + } + + } + + // instance controllers + + for ( let i = 0, l = instanceControllers.length; i < l; i ++ ) { + + const instance = instanceControllers[ i ]; + const controller = getController( instance.id ); + const geometries = getGeometry( controller.id ); + const newObjects = buildObjects( geometries, instance.materials ); + + const skeletons = instance.skeletons; + const joints = controller.skin.joints; + + const skeleton = buildSkeleton( skeletons, joints ); + + for ( let j = 0, jl = newObjects.length; j < jl; j ++ ) { + + const object = newObjects[ j ]; + + if ( object.isSkinnedMesh ) { + + object.bind( skeleton, controller.skin.bindMatrix ); + object.normalizeSkinWeights(); + + } + + objects.push( object ); + + } + + } + + // instance lights + + for ( let i = 0, l = instanceLights.length; i < l; i ++ ) { + + const instanceLight = getLight( instanceLights[ i ] ); + + if ( instanceLight !== null ) { + + objects.push( instanceLight.clone() ); + + } + + } + + // instance geometries + + for ( let i = 0, l = instanceGeometries.length; i < l; i ++ ) { + + const instance = instanceGeometries[ i ]; + + // a single geometry instance in collada can lead to multiple object3Ds. + // this is the case when primitives are combined like triangles and lines + + const geometries = getGeometry( instance.id ); + const newObjects = buildObjects( geometries, instance.materials ); + + for ( let j = 0, jl = newObjects.length; j < jl; j ++ ) { + + objects.push( newObjects[ j ] ); + + } + + } + + // instance nodes + + for ( let i = 0, l = instanceNodes.length; i < l; i ++ ) { + + objects.push( getNode( instanceNodes[ i ] ).clone() ); + + } + + let object; + + if ( nodes.length === 0 && objects.length === 1 ) { + + object = objects[ 0 ]; + + } else { + + object = ( type === 'JOINT' ) ? new Bone() : new Group(); + + for ( let i = 0; i < objects.length; i ++ ) { + + object.add( objects[ i ] ); + + } + + } + + object.name = ( type === 'JOINT' ) ? data.sid : data.name; + object.matrix.copy( matrix ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + + return object; + + } + + const fallbackMaterial = new MeshBasicMaterial( { color: 0xff00ff } ); + + function resolveMaterialBinding( keys, instanceMaterials ) { + + const materials = []; + + for ( let i = 0, l = keys.length; i < l; i ++ ) { + + const id = instanceMaterials[ keys[ i ] ]; + + if ( id === undefined ) { + + console.warn( 'THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[ i ] ); + materials.push( fallbackMaterial ); + + } else { + + materials.push( getMaterial( id ) ); + + } + + } + + return materials; + + } + + function buildObjects( geometries, instanceMaterials ) { + + const objects = []; + + for ( const type in geometries ) { + + const geometry = geometries[ type ]; + + const materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials ); + + // handle case if no materials are defined + + if ( materials.length === 0 ) { + + if ( type === 'lines' || type === 'linestrips' ) { + + materials.push( new LineBasicMaterial() ); + + } else { + + materials.push( new MeshPhongMaterial() ); + + } + + } + + // regard skinning + + const skinning = ( geometry.data.attributes.skinIndex !== undefined ); + + // choose between a single or multi materials (material array) + + const material = ( materials.length === 1 ) ? materials[ 0 ] : materials; + + // now create a specific 3D object + + let object; + + switch ( type ) { + + case 'lines': + object = new LineSegments( geometry.data, material ); + break; + + case 'linestrips': + object = new Line( geometry.data, material ); + break; + + case 'triangles': + case 'polylist': + if ( skinning ) { + + object = new SkinnedMesh( geometry.data, material ); + + } else { + + object = new Mesh( geometry.data, material ); + + } + + break; + + } + + objects.push( object ); + + } + + return objects; + + } + + function hasNode( id ) { + + return library.nodes[ id ] !== undefined; + + } + + function getNode( id ) { + + return getBuild( library.nodes[ id ], buildNode ); + + } + + // visual scenes + + function parseVisualScene( xml ) { + + const data = { + name: xml.getAttribute( 'name' ), + children: [] + }; + + prepareNodes( xml ); + + const elements = getElementsByTagName( xml, 'node' ); + + for ( let i = 0; i < elements.length; i ++ ) { + + data.children.push( parseNode( elements[ i ] ) ); + + } + + library.visualScenes[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildVisualScene( data ) { + + const group = new Group(); + group.name = data.name; + + const children = data.children; + + for ( let i = 0; i < children.length; i ++ ) { + + const child = children[ i ]; + + group.add( getNode( child.id ) ); + + } + + return group; + + } + + function hasVisualScene( id ) { + + return library.visualScenes[ id ] !== undefined; + + } + + function getVisualScene( id ) { + + return getBuild( library.visualScenes[ id ], buildVisualScene ); + + } + + // scenes + + function parseScene( xml ) { + + const instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ]; + return getVisualScene( parseId( instance.getAttribute( 'url' ) ) ); + + } + + function setupAnimations() { + + const clips = library.clips; + + if ( isEmpty( clips ) === true ) { + + if ( isEmpty( library.animations ) === false ) { + + // if there are animations but no clips, we create a default clip for playback + + const tracks = []; + + for ( const id in library.animations ) { + + const animationTracks = getAnimation( id ); + + for ( let i = 0, l = animationTracks.length; i < l; i ++ ) { + + tracks.push( animationTracks[ i ] ); + + } + + } + + animations.push( new AnimationClip( 'default', - 1, tracks ) ); + + } + + } else { + + for ( const id in clips ) { + + animations.push( getAnimationClip( id ) ); + + } + + } + + } + + // convert the parser error element into text with each child elements text + // separated by new lines. + + function parserErrorToText( parserError ) { + + let result = ''; + const stack = [ parserError ]; + + while ( stack.length ) { + + const node = stack.shift(); + + if ( node.nodeType === Node.TEXT_NODE ) { + + result += node.textContent; + + } else { + + result += '\n'; + stack.push.apply( stack, node.childNodes ); + + } + + } + + return result.trim(); + + } + + if ( text.length === 0 ) { + + return { scene: new Scene() }; + + } + + const xml = new DOMParser().parseFromString( text, 'application/xml' ); + + const collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ]; + + const parserError = xml.getElementsByTagName( 'parsererror' )[ 0 ]; + if ( parserError !== undefined ) { + + // Chrome will return parser error with a div in it + + const errorElement = getElementsByTagName( parserError, 'div' )[ 0 ]; + let errorText; + + if ( errorElement ) { + + errorText = errorElement.textContent; + + } else { + + errorText = parserErrorToText( parserError ); + + } + + console.error( 'THREE.ColladaLoader: Failed to parse collada file.\n', errorText ); + + return null; + + } + + // metadata + + const version = collada.getAttribute( 'version' ); + console.log( 'THREE.ColladaLoader: File version', version ); + + const asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] ); + const textureLoader = new TextureLoader( this.manager ); + textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + + let tgaLoader; + + if ( TGALoader ) { + + tgaLoader = new TGALoader( this.manager ); + tgaLoader.setPath( this.resourcePath || path ); + + } + + // + + const tempColor = new Color(); + const animations = []; + let kinematics = {}; + let count = 0; + + // + + const library = { + animations: {}, + clips: {}, + controllers: {}, + images: {}, + effects: {}, + materials: {}, + cameras: {}, + lights: {}, + geometries: {}, + nodes: {}, + visualScenes: {}, + kinematicsModels: {}, + physicsModels: {}, + kinematicsScenes: {} + }; + + parseLibrary( collada, 'library_animations', 'animation', parseAnimation ); + parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip ); + parseLibrary( collada, 'library_controllers', 'controller', parseController ); + parseLibrary( collada, 'library_images', 'image', parseImage ); + parseLibrary( collada, 'library_effects', 'effect', parseEffect ); + parseLibrary( collada, 'library_materials', 'material', parseMaterial ); + parseLibrary( collada, 'library_cameras', 'camera', parseCamera ); + parseLibrary( collada, 'library_lights', 'light', parseLight ); + parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry ); + parseLibrary( collada, 'library_nodes', 'node', parseNode ); + parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene ); + parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel ); + parseLibrary( collada, 'library_physics_models', 'physics_model', parsePhysicsModel ); + parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene ); + + buildLibrary( library.animations, buildAnimation ); + buildLibrary( library.clips, buildAnimationClip ); + buildLibrary( library.controllers, buildController ); + buildLibrary( library.images, buildImage ); + buildLibrary( library.effects, buildEffect ); + buildLibrary( library.materials, buildMaterial ); + buildLibrary( library.cameras, buildCamera ); + buildLibrary( library.lights, buildLight ); + buildLibrary( library.geometries, buildGeometry ); + buildLibrary( library.visualScenes, buildVisualScene ); + + setupAnimations(); + setupKinematics(); + + const scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] ); + scene.animations = animations; + + if ( asset.upAxis === 'Z_UP' ) { + + scene.quaternion.setFromEuler( new Euler( - Math.PI / 2, 0, 0 ) ); + + } + + scene.scale.multiplyScalar( asset.unit ); + + return { + get animations() { + + console.warn( 'THREE.ColladaLoader: Please access animations over scene.animations now.' ); + return animations; + + }, + kinematics: kinematics, + library: library, + scene: scene + }; + + } + +} + +export { ColladaLoader }; diff --git a/jsm/loaders/DDSLoader.js b/jsm/loaders/DDSLoader.js new file mode 100644 index 0000000..a08d71a --- /dev/null +++ b/jsm/loaders/DDSLoader.js @@ -0,0 +1,281 @@ +import { + CompressedTextureLoader, + RGBAFormat, + RGBA_S3TC_DXT3_Format, + RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format, + RGB_S3TC_DXT1_Format +} from 'three'; + +class DDSLoader extends CompressedTextureLoader { + + constructor( manager ) { + + super( manager ); + + } + + parse( buffer, loadMipmaps ) { + + const dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; + + // Adapted from @toji's DDS utils + // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js + + // All values and structures referenced from: + // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ + + const DDS_MAGIC = 0x20534444; + + // let DDSD_CAPS = 0x1; + // let DDSD_HEIGHT = 0x2; + // let DDSD_WIDTH = 0x4; + // let DDSD_PITCH = 0x8; + // let DDSD_PIXELFORMAT = 0x1000; + const DDSD_MIPMAPCOUNT = 0x20000; + // let DDSD_LINEARSIZE = 0x80000; + // let DDSD_DEPTH = 0x800000; + + // let DDSCAPS_COMPLEX = 0x8; + // let DDSCAPS_MIPMAP = 0x400000; + // let DDSCAPS_TEXTURE = 0x1000; + + const DDSCAPS2_CUBEMAP = 0x200; + const DDSCAPS2_CUBEMAP_POSITIVEX = 0x400; + const DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800; + const DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000; + const DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000; + const DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000; + const DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000; + // let DDSCAPS2_VOLUME = 0x200000; + + // let DDPF_ALPHAPIXELS = 0x1; + // let DDPF_ALPHA = 0x2; + const DDPF_FOURCC = 0x4; + // let DDPF_RGB = 0x40; + // let DDPF_YUV = 0x200; + // let DDPF_LUMINANCE = 0x20000; + + function fourCCToInt32( value ) { + + return value.charCodeAt( 0 ) + + ( value.charCodeAt( 1 ) << 8 ) + + ( value.charCodeAt( 2 ) << 16 ) + + ( value.charCodeAt( 3 ) << 24 ); + + } + + function int32ToFourCC( value ) { + + return String.fromCharCode( + value & 0xff, + ( value >> 8 ) & 0xff, + ( value >> 16 ) & 0xff, + ( value >> 24 ) & 0xff + ); + + } + + function loadARGBMip( buffer, dataOffset, width, height ) { + + const dataLength = width * height * 4; + const srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); + const byteArray = new Uint8Array( dataLength ); + let dst = 0; + let src = 0; + for ( let y = 0; y < height; y ++ ) { + + for ( let x = 0; x < width; x ++ ) { + + const b = srcBuffer[ src ]; src ++; + const g = srcBuffer[ src ]; src ++; + const r = srcBuffer[ src ]; src ++; + const a = srcBuffer[ src ]; src ++; + byteArray[ dst ] = r; dst ++; //r + byteArray[ dst ] = g; dst ++; //g + byteArray[ dst ] = b; dst ++; //b + byteArray[ dst ] = a; dst ++; //a + + } + + } + + return byteArray; + + } + + const FOURCC_DXT1 = fourCCToInt32( 'DXT1' ); + const FOURCC_DXT3 = fourCCToInt32( 'DXT3' ); + const FOURCC_DXT5 = fourCCToInt32( 'DXT5' ); + const FOURCC_ETC1 = fourCCToInt32( 'ETC1' ); + + const headerLengthInt = 31; // The header length in 32 bit ints + + // Offsets into the header array + + const off_magic = 0; + + const off_size = 1; + const off_flags = 2; + const off_height = 3; + const off_width = 4; + + const off_mipmapCount = 7; + + const off_pfFlags = 20; + const off_pfFourCC = 21; + const off_RGBBitCount = 22; + const off_RBitMask = 23; + const off_GBitMask = 24; + const off_BBitMask = 25; + const off_ABitMask = 26; + + // let off_caps = 27; + const off_caps2 = 28; + // let off_caps3 = 29; + // let off_caps4 = 30; + + // Parse header + + const header = new Int32Array( buffer, 0, headerLengthInt ); + + if ( header[ off_magic ] !== DDS_MAGIC ) { + + console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' ); + return dds; + + } + + if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) { + + console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' ); + return dds; + + } + + let blockBytes; + + const fourCC = header[ off_pfFourCC ]; + + let isRGBAUncompressed = false; + + switch ( fourCC ) { + + case FOURCC_DXT1: + + blockBytes = 8; + dds.format = RGB_S3TC_DXT1_Format; + break; + + case FOURCC_DXT3: + + blockBytes = 16; + dds.format = RGBA_S3TC_DXT3_Format; + break; + + case FOURCC_DXT5: + + blockBytes = 16; + dds.format = RGBA_S3TC_DXT5_Format; + break; + + case FOURCC_ETC1: + + blockBytes = 8; + dds.format = RGB_ETC1_Format; + break; + + default: + + if ( header[ off_RGBBitCount ] === 32 + && header[ off_RBitMask ] & 0xff0000 + && header[ off_GBitMask ] & 0xff00 + && header[ off_BBitMask ] & 0xff + && header[ off_ABitMask ] & 0xff000000 ) { + + isRGBAUncompressed = true; + blockBytes = 64; + dds.format = RGBAFormat; + + } else { + + console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) ); + return dds; + + } + + } + + dds.mipmapCount = 1; + + if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) { + + dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] ); + + } + + const caps2 = header[ off_caps2 ]; + dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false; + if ( dds.isCubemap && ( + ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ ) + ) ) { + + console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' ); + return dds; + + } + + dds.width = header[ off_width ]; + dds.height = header[ off_height ]; + + let dataOffset = header[ off_size ] + 4; + + // Extract mipmaps buffers + + const faces = dds.isCubemap ? 6 : 1; + + for ( let face = 0; face < faces; face ++ ) { + + let width = dds.width; + let height = dds.height; + + for ( let i = 0; i < dds.mipmapCount; i ++ ) { + + let byteArray, dataLength; + + if ( isRGBAUncompressed ) { + + byteArray = loadARGBMip( buffer, dataOffset, width, height ); + dataLength = byteArray.length; + + } else { + + dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; + byteArray = new Uint8Array( buffer, dataOffset, dataLength ); + + } + + const mipmap = { 'data': byteArray, 'width': width, 'height': height }; + dds.mipmaps.push( mipmap ); + + dataOffset += dataLength; + + width = Math.max( width >> 1, 1 ); + height = Math.max( height >> 1, 1 ); + + } + + } + + return dds; + + } + +} + +export { DDSLoader }; diff --git a/jsm/loaders/DRACOLoader.js b/jsm/loaders/DRACOLoader.js new file mode 100644 index 0000000..53afc5b --- /dev/null +++ b/jsm/loaders/DRACOLoader.js @@ -0,0 +1,587 @@ +import { + BufferAttribute, + BufferGeometry, + FileLoader, + Loader +} from 'three'; + +const _taskCache = new WeakMap(); + +class DRACOLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.decoderPath = ''; + this.decoderConfig = {}; + this.decoderBinary = null; + this.decoderPending = null; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + + this.defaultAttributeIDs = { + position: 'POSITION', + normal: 'NORMAL', + color: 'COLOR', + uv: 'TEX_COORD' + }; + this.defaultAttributeTypes = { + position: 'Float32Array', + normal: 'Float32Array', + color: 'Float32Array', + uv: 'Float32Array' + }; + + } + + setDecoderPath( path ) { + + this.decoderPath = path; + + return this; + + } + + setDecoderConfig( config ) { + + this.decoderConfig = config; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, ( buffer ) => { + + const taskConfig = { + attributeIDs: this.defaultAttributeIDs, + attributeTypes: this.defaultAttributeTypes, + useUniqueIDs: false + }; + + this.decodeGeometry( buffer, taskConfig ) + .then( onLoad ) + .catch( onError ); + + }, onProgress, onError ); + + } + + /** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */ + decodeDracoFile( buffer, callback, attributeIDs, attributeTypes ) { + + const taskConfig = { + attributeIDs: attributeIDs || this.defaultAttributeIDs, + attributeTypes: attributeTypes || this.defaultAttributeTypes, + useUniqueIDs: !! attributeIDs + }; + + this.decodeGeometry( buffer, taskConfig ).then( callback ); + + } + + decodeGeometry( buffer, taskConfig ) { + + // TODO: For backward-compatibility, support 'attributeTypes' objects containing + // references (rather than names) to typed array constructors. These must be + // serialized before sending them to the worker. + for ( const attribute in taskConfig.attributeTypes ) { + + const type = taskConfig.attributeTypes[ attribute ]; + + if ( type.BYTES_PER_ELEMENT !== undefined ) { + + taskConfig.attributeTypes[ attribute ] = type.name; + + } + + } + + // + + const taskKey = JSON.stringify( taskConfig ); + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + if ( cachedTask.key === taskKey ) { + + return cachedTask.promise; + + } else if ( buffer.byteLength === 0 ) { + + // Technically, it would be possible to wait for the previous task to complete, + // transfer the buffer back, and decode again with the second configuration. That + // is complex, and I don't know of any reason to decode a Draco buffer twice in + // different ways, so this is left unimplemented. + throw new Error( + + 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + + 'settings. Buffer has already been transferred.' + + ); + + } + + } + + // + + let worker; + const taskID = this.workerNextTaskID ++; + const taskCost = buffer.byteLength; + + // Obtain a worker and assign a task, and construct a geometry instance + // when the task completes. + const geometryPending = this._getWorker( taskID, taskCost ) + .then( ( _worker ) => { + + worker = _worker; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); + + // this.debug(); + + } ); + + } ) + .then( ( message ) => this._createGeometry( message.geometry ) ); + + // Remove task from the task list. + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + geometryPending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + this._releaseTask( worker, taskID ); + + // this.debug(); + + } + + } ); + + // Cache the task result. + _taskCache.set( buffer, { + + key: taskKey, + promise: geometryPending + + } ); + + return geometryPending; + + } + + _createGeometry( geometryData ) { + + const geometry = new BufferGeometry(); + + if ( geometryData.index ) { + + geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) ); + + } + + for ( let i = 0; i < geometryData.attributes.length; i ++ ) { + + const attribute = geometryData.attributes[ i ]; + const name = attribute.name; + const array = attribute.array; + const itemSize = attribute.itemSize; + + geometry.setAttribute( name, new BufferAttribute( array, itemSize ) ); + + } + + return geometry; + + } + + _loadLibrary( url, responseType ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.decoderPath ); + loader.setResponseType( responseType ); + loader.setWithCredentials( this.withCredentials ); + + return new Promise( ( resolve, reject ) => { + + loader.load( url, resolve, undefined, reject ); + + } ); + + } + + preload() { + + this._initDecoder(); + + return this; + + } + + _initDecoder() { + + if ( this.decoderPending ) return this.decoderPending; + + const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; + const librariesPending = []; + + if ( useJS ) { + + librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); + + } else { + + librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); + librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); + + } + + this.decoderPending = Promise.all( librariesPending ) + .then( ( libraries ) => { + + const jsContent = libraries[ 0 ]; + + if ( ! useJS ) { + + this.decoderConfig.wasmBinary = libraries[ 1 ]; + + } + + const fn = DRACOWorker.toString(); + + const body = [ + '/* draco decoder */', + jsContent, + '', + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + + } ); + + return this.decoderPending; + + } + + _getWorker( taskID, taskCost ) { + + return this._initDecoder().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskCosts = {}; + worker._taskLoad = 0; + + worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); + + worker.onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'decode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' ); + + } + + }; + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + worker._taskCosts[ taskID ] = taskCost; + worker._taskLoad += taskCost; + return worker; + + } ); + + } + + _releaseTask( worker, taskID ) { + + worker._taskLoad -= worker._taskCosts[ taskID ]; + delete worker._callbacks[ taskID ]; + delete worker._taskCosts[ taskID ]; + + } + + debug() { + + console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; ++ i ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + return this; + + } + +} + +/* WEB WORKER */ + +function DRACOWorker() { + + let decoderConfig; + let decoderPending; + + onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + decoderConfig = message.decoderConfig; + decoderPending = new Promise( function ( resolve/*, reject*/ ) { + + decoderConfig.onModuleLoaded = function ( draco ) { + + // Module is Promise-like. Wrap before resolving to avoid loop. + resolve( { draco: draco } ); + + }; + + DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef + + } ); + break; + + case 'decode': + const buffer = message.buffer; + const taskConfig = message.taskConfig; + decoderPending.then( ( module ) => { + + const draco = module.draco; + const decoder = new draco.Decoder(); + const decoderBuffer = new draco.DecoderBuffer(); + decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength ); + + try { + + const geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig ); + + const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); + + if ( geometry.index ) buffers.push( geometry.index.array.buffer ); + + self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); + + } catch ( error ) { + + console.error( error ); + + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } finally { + + draco.destroy( decoderBuffer ); + draco.destroy( decoder ); + + } + + } ); + break; + + } + + }; + + function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) { + + const attributeIDs = taskConfig.attributeIDs; + const attributeTypes = taskConfig.attributeTypes; + + let dracoGeometry; + let decodingStatus; + + const geometryType = decoder.GetEncodedGeometryType( decoderBuffer ); + + if ( geometryType === draco.TRIANGULAR_MESH ) { + + dracoGeometry = new draco.Mesh(); + decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry ); + + } else if ( geometryType === draco.POINT_CLOUD ) { + + dracoGeometry = new draco.PointCloud(); + decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry ); + + } else { + + throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); + + } + + if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { + + throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); + + } + + const geometry = { index: null, attributes: [] }; + + // Gather all vertex attributes. + for ( const attributeName in attributeIDs ) { + + const attributeType = self[ attributeTypes[ attributeName ] ]; + + let attribute; + let attributeID; + + // A Draco file may be created with default vertex attributes, whose attribute IDs + // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, + // a Draco file may contain a custom set of attributes, identified by known unique + // IDs. glTF files always do the latter, and `.drc` files typically do the former. + if ( taskConfig.useUniqueIDs ) { + + attributeID = attributeIDs[ attributeName ]; + attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); + + } else { + + attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); + + if ( attributeID === - 1 ) continue; + + attribute = decoder.GetAttribute( dracoGeometry, attributeID ); + + } + + geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) ); + + } + + // Add index. + if ( geometryType === draco.TRIANGULAR_MESH ) { + + geometry.index = decodeIndex( draco, decoder, dracoGeometry ); + + } + + draco.destroy( dracoGeometry ); + + return geometry; + + } + + function decodeIndex( draco, decoder, dracoGeometry ) { + + const numFaces = dracoGeometry.num_faces(); + const numIndices = numFaces * 3; + const byteLength = numIndices * 4; + + const ptr = draco._malloc( byteLength ); + decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr ); + const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice(); + draco._free( ptr ); + + return { array: index, itemSize: 1 }; + + } + + function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { + + const numComponents = attribute.num_components(); + const numPoints = dracoGeometry.num_points(); + const numValues = numPoints * numComponents; + const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; + const dataType = getDracoDataType( draco, attributeType ); + + const ptr = draco._malloc( byteLength ); + decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr ); + const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice(); + draco._free( ptr ); + + return { + name: attributeName, + array: array, + itemSize: numComponents + }; + + } + + function getDracoDataType( draco, attributeType ) { + + switch ( attributeType ) { + + case Float32Array: return draco.DT_FLOAT32; + case Int8Array: return draco.DT_INT8; + case Int16Array: return draco.DT_INT16; + case Int32Array: return draco.DT_INT32; + case Uint8Array: return draco.DT_UINT8; + case Uint16Array: return draco.DT_UINT16; + case Uint32Array: return draco.DT_UINT32; + + } + + } + +} + +export { DRACOLoader }; diff --git a/jsm/loaders/EXRLoader.js b/jsm/loaders/EXRLoader.js new file mode 100644 index 0000000..a4747ec --- /dev/null +++ b/jsm/loaders/EXRLoader.js @@ -0,0 +1,2320 @@ +import { + DataTextureLoader, + DataUtils, + FloatType, + HalfFloatType, + LinearEncoding, + LinearFilter, + RedFormat, + RGBAFormat +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +/** + * OpenEXR loader currently supports uncompressed, ZIP(S), RLE, PIZ and DWA/B compression. + * Supports reading as UnsignedByte, HalfFloat and Float type data texture. + * + * Referred to the original Industrial Light & Magic OpenEXR implementation and the TinyEXR / Syoyo Fujita + * implementation, so I have preserved their copyright notices. + */ + +// /* +// Copyright (c) 2014 - 2017, Syoyo Fujita +// 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 the Syoyo Fujita 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 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. +// */ + +// // TinyEXR contains some OpenEXR code, which is licensed under ------------ + +// /////////////////////////////////////////////////////////////////////////// +// // +// // Copyright (c) 2002, Industrial Light & Magic, a division of Lucas +// // Digital Ltd. LLC +// // +// // 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 Industrial Light & Magic 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. +// // +// /////////////////////////////////////////////////////////////////////////// + +// // End of OpenEXR license ------------------------------------------------- + +class EXRLoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + + } + + parse( buffer ) { + + const USHORT_RANGE = ( 1 << 16 ); + const BITMAP_SIZE = ( USHORT_RANGE >> 3 ); + + const HUF_ENCBITS = 16; // literal (value) bit length + const HUF_DECBITS = 14; // decoding bit size (>= 8) + + const HUF_ENCSIZE = ( 1 << HUF_ENCBITS ) + 1; // encoding table size + const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size + const HUF_DECMASK = HUF_DECSIZE - 1; + + const NBITS = 16; + const A_OFFSET = 1 << ( NBITS - 1 ); + const MOD_MASK = ( 1 << NBITS ) - 1; + + const SHORT_ZEROCODE_RUN = 59; + const LONG_ZEROCODE_RUN = 63; + const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN; + + const ULONG_SIZE = 8; + const FLOAT32_SIZE = 4; + const INT32_SIZE = 4; + const INT16_SIZE = 2; + const INT8_SIZE = 1; + + const STATIC_HUFFMAN = 0; + const DEFLATE = 1; + + const UNKNOWN = 0; + const LOSSY_DCT = 1; + const RLE = 2; + + const logBase = Math.pow( 2.7182818, 2.2 ); + + function reverseLutFromBitmap( bitmap, lut ) { + + let k = 0; + + for ( let i = 0; i < USHORT_RANGE; ++ i ) { + + if ( ( i == 0 ) || ( bitmap[ i >> 3 ] & ( 1 << ( i & 7 ) ) ) ) { + + lut[ k ++ ] = i; + + } + + } + + const n = k - 1; + + while ( k < USHORT_RANGE ) lut[ k ++ ] = 0; + + return n; + + } + + function hufClearDecTable( hdec ) { + + for ( let i = 0; i < HUF_DECSIZE; i ++ ) { + + hdec[ i ] = {}; + hdec[ i ].len = 0; + hdec[ i ].lit = 0; + hdec[ i ].p = null; + + } + + } + + const getBitsReturn = { l: 0, c: 0, lc: 0 }; + + function getBits( nBits, c, lc, uInt8Array, inOffset ) { + + while ( lc < nBits ) { + + c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset ); + lc += 8; + + } + + lc -= nBits; + + getBitsReturn.l = ( c >> lc ) & ( ( 1 << nBits ) - 1 ); + getBitsReturn.c = c; + getBitsReturn.lc = lc; + + } + + const hufTableBuffer = new Array( 59 ); + + function hufCanonicalCodeTable( hcode ) { + + for ( let i = 0; i <= 58; ++ i ) hufTableBuffer[ i ] = 0; + for ( let i = 0; i < HUF_ENCSIZE; ++ i ) hufTableBuffer[ hcode[ i ] ] += 1; + + let c = 0; + + for ( let i = 58; i > 0; -- i ) { + + const nc = ( ( c + hufTableBuffer[ i ] ) >> 1 ); + hufTableBuffer[ i ] = c; + c = nc; + + } + + for ( let i = 0; i < HUF_ENCSIZE; ++ i ) { + + const l = hcode[ i ]; + if ( l > 0 ) hcode[ i ] = l | ( hufTableBuffer[ l ] ++ << 6 ); + + } + + } + + function hufUnpackEncTable( uInt8Array, inOffset, ni, im, iM, hcode ) { + + const p = inOffset; + let c = 0; + let lc = 0; + + for ( ; im <= iM; im ++ ) { + + if ( p.value - inOffset.value > ni ) return false; + + getBits( 6, c, lc, uInt8Array, p ); + + const l = getBitsReturn.l; + c = getBitsReturn.c; + lc = getBitsReturn.lc; + + hcode[ im ] = l; + + if ( l == LONG_ZEROCODE_RUN ) { + + if ( p.value - inOffset.value > ni ) { + + throw new Error( 'Something wrong with hufUnpackEncTable' ); + + } + + getBits( 8, c, lc, uInt8Array, p ); + + let zerun = getBitsReturn.l + SHORTEST_LONG_RUN; + c = getBitsReturn.c; + lc = getBitsReturn.lc; + + if ( im + zerun > iM + 1 ) { + + throw new Error( 'Something wrong with hufUnpackEncTable' ); + + } + + while ( zerun -- ) hcode[ im ++ ] = 0; + + im --; + + } else if ( l >= SHORT_ZEROCODE_RUN ) { + + let zerun = l - SHORT_ZEROCODE_RUN + 2; + + if ( im + zerun > iM + 1 ) { + + throw new Error( 'Something wrong with hufUnpackEncTable' ); + + } + + while ( zerun -- ) hcode[ im ++ ] = 0; + + im --; + + } + + } + + hufCanonicalCodeTable( hcode ); + + } + + function hufLength( code ) { + + return code & 63; + + } + + function hufCode( code ) { + + return code >> 6; + + } + + function hufBuildDecTable( hcode, im, iM, hdecod ) { + + for ( ; im <= iM; im ++ ) { + + const c = hufCode( hcode[ im ] ); + const l = hufLength( hcode[ im ] ); + + if ( c >> l ) { + + throw new Error( 'Invalid table entry' ); + + } + + if ( l > HUF_DECBITS ) { + + const pl = hdecod[ ( c >> ( l - HUF_DECBITS ) ) ]; + + if ( pl.len ) { + + throw new Error( 'Invalid table entry' ); + + } + + pl.lit ++; + + if ( pl.p ) { + + const p = pl.p; + pl.p = new Array( pl.lit ); + + for ( let i = 0; i < pl.lit - 1; ++ i ) { + + pl.p[ i ] = p[ i ]; + + } + + } else { + + pl.p = new Array( 1 ); + + } + + pl.p[ pl.lit - 1 ] = im; + + } else if ( l ) { + + let plOffset = 0; + + for ( let i = 1 << ( HUF_DECBITS - l ); i > 0; i -- ) { + + const pl = hdecod[ ( c << ( HUF_DECBITS - l ) ) + plOffset ]; + + if ( pl.len || pl.p ) { + + throw new Error( 'Invalid table entry' ); + + } + + pl.len = l; + pl.lit = im; + + plOffset ++; + + } + + } + + } + + return true; + + } + + const getCharReturn = { c: 0, lc: 0 }; + + function getChar( c, lc, uInt8Array, inOffset ) { + + c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset ); + lc += 8; + + getCharReturn.c = c; + getCharReturn.lc = lc; + + } + + const getCodeReturn = { c: 0, lc: 0 }; + + function getCode( po, rlc, c, lc, uInt8Array, inOffset, outBuffer, outBufferOffset, outBufferEndOffset ) { + + if ( po == rlc ) { + + if ( lc < 8 ) { + + getChar( c, lc, uInt8Array, inOffset ); + c = getCharReturn.c; + lc = getCharReturn.lc; + + } + + lc -= 8; + + let cs = ( c >> lc ); + cs = new Uint8Array( [ cs ] )[ 0 ]; + + if ( outBufferOffset.value + cs > outBufferEndOffset ) { + + return false; + + } + + const s = outBuffer[ outBufferOffset.value - 1 ]; + + while ( cs -- > 0 ) { + + outBuffer[ outBufferOffset.value ++ ] = s; + + } + + } else if ( outBufferOffset.value < outBufferEndOffset ) { + + outBuffer[ outBufferOffset.value ++ ] = po; + + } else { + + return false; + + } + + getCodeReturn.c = c; + getCodeReturn.lc = lc; + + } + + function UInt16( value ) { + + return ( value & 0xFFFF ); + + } + + function Int16( value ) { + + const ref = UInt16( value ); + return ( ref > 0x7FFF ) ? ref - 0x10000 : ref; + + } + + const wdec14Return = { a: 0, b: 0 }; + + function wdec14( l, h ) { + + const ls = Int16( l ); + const hs = Int16( h ); + + const hi = hs; + const ai = ls + ( hi & 1 ) + ( hi >> 1 ); + + const as = ai; + const bs = ai - hi; + + wdec14Return.a = as; + wdec14Return.b = bs; + + } + + function wdec16( l, h ) { + + const m = UInt16( l ); + const d = UInt16( h ); + + const bb = ( m - ( d >> 1 ) ) & MOD_MASK; + const aa = ( d + bb - A_OFFSET ) & MOD_MASK; + + wdec14Return.a = aa; + wdec14Return.b = bb; + + } + + function wav2Decode( buffer, j, nx, ox, ny, oy, mx ) { + + const w14 = mx < ( 1 << 14 ); + const n = ( nx > ny ) ? ny : nx; + let p = 1; + let p2; + let py; + + while ( p <= n ) p <<= 1; + + p >>= 1; + p2 = p; + p >>= 1; + + while ( p >= 1 ) { + + py = 0; + const ey = py + oy * ( ny - p2 ); + const oy1 = oy * p; + const oy2 = oy * p2; + const ox1 = ox * p; + const ox2 = ox * p2; + let i00, i01, i10, i11; + + for ( ; py <= ey; py += oy2 ) { + + let px = py; + const ex = py + ox * ( nx - p2 ); + + for ( ; px <= ex; px += ox2 ) { + + const p01 = px + ox1; + const p10 = px + oy1; + const p11 = p10 + ox1; + + if ( w14 ) { + + wdec14( buffer[ px + j ], buffer[ p10 + j ] ); + + i00 = wdec14Return.a; + i10 = wdec14Return.b; + + wdec14( buffer[ p01 + j ], buffer[ p11 + j ] ); + + i01 = wdec14Return.a; + i11 = wdec14Return.b; + + wdec14( i00, i01 ); + + buffer[ px + j ] = wdec14Return.a; + buffer[ p01 + j ] = wdec14Return.b; + + wdec14( i10, i11 ); + + buffer[ p10 + j ] = wdec14Return.a; + buffer[ p11 + j ] = wdec14Return.b; + + } else { + + wdec16( buffer[ px + j ], buffer[ p10 + j ] ); + + i00 = wdec14Return.a; + i10 = wdec14Return.b; + + wdec16( buffer[ p01 + j ], buffer[ p11 + j ] ); + + i01 = wdec14Return.a; + i11 = wdec14Return.b; + + wdec16( i00, i01 ); + + buffer[ px + j ] = wdec14Return.a; + buffer[ p01 + j ] = wdec14Return.b; + + wdec16( i10, i11 ); + + buffer[ p10 + j ] = wdec14Return.a; + buffer[ p11 + j ] = wdec14Return.b; + + + } + + } + + if ( nx & p ) { + + const p10 = px + oy1; + + if ( w14 ) + wdec14( buffer[ px + j ], buffer[ p10 + j ] ); + else + wdec16( buffer[ px + j ], buffer[ p10 + j ] ); + + i00 = wdec14Return.a; + buffer[ p10 + j ] = wdec14Return.b; + + buffer[ px + j ] = i00; + + } + + } + + if ( ny & p ) { + + let px = py; + const ex = py + ox * ( nx - p2 ); + + for ( ; px <= ex; px += ox2 ) { + + const p01 = px + ox1; + + if ( w14 ) + wdec14( buffer[ px + j ], buffer[ p01 + j ] ); + else + wdec16( buffer[ px + j ], buffer[ p01 + j ] ); + + i00 = wdec14Return.a; + buffer[ p01 + j ] = wdec14Return.b; + + buffer[ px + j ] = i00; + + } + + } + + p2 = p; + p >>= 1; + + } + + return py; + + } + + function hufDecode( encodingTable, decodingTable, uInt8Array, inOffset, ni, rlc, no, outBuffer, outOffset ) { + + let c = 0; + let lc = 0; + const outBufferEndOffset = no; + const inOffsetEnd = Math.trunc( inOffset.value + ( ni + 7 ) / 8 ); + + while ( inOffset.value < inOffsetEnd ) { + + getChar( c, lc, uInt8Array, inOffset ); + + c = getCharReturn.c; + lc = getCharReturn.lc; + + while ( lc >= HUF_DECBITS ) { + + const index = ( c >> ( lc - HUF_DECBITS ) ) & HUF_DECMASK; + const pl = decodingTable[ index ]; + + if ( pl.len ) { + + lc -= pl.len; + + getCode( pl.lit, rlc, c, lc, uInt8Array, inOffset, outBuffer, outOffset, outBufferEndOffset ); + + c = getCodeReturn.c; + lc = getCodeReturn.lc; + + } else { + + if ( ! pl.p ) { + + throw new Error( 'hufDecode issues' ); + + } + + let j; + + for ( j = 0; j < pl.lit; j ++ ) { + + const l = hufLength( encodingTable[ pl.p[ j ] ] ); + + while ( lc < l && inOffset.value < inOffsetEnd ) { + + getChar( c, lc, uInt8Array, inOffset ); + + c = getCharReturn.c; + lc = getCharReturn.lc; + + } + + if ( lc >= l ) { + + if ( hufCode( encodingTable[ pl.p[ j ] ] ) == ( ( c >> ( lc - l ) ) & ( ( 1 << l ) - 1 ) ) ) { + + lc -= l; + + getCode( pl.p[ j ], rlc, c, lc, uInt8Array, inOffset, outBuffer, outOffset, outBufferEndOffset ); + + c = getCodeReturn.c; + lc = getCodeReturn.lc; + + break; + + } + + } + + } + + if ( j == pl.lit ) { + + throw new Error( 'hufDecode issues' ); + + } + + } + + } + + } + + const i = ( 8 - ni ) & 7; + + c >>= i; + lc -= i; + + while ( lc > 0 ) { + + const pl = decodingTable[ ( c << ( HUF_DECBITS - lc ) ) & HUF_DECMASK ]; + + if ( pl.len ) { + + lc -= pl.len; + + getCode( pl.lit, rlc, c, lc, uInt8Array, inOffset, outBuffer, outOffset, outBufferEndOffset ); + + c = getCodeReturn.c; + lc = getCodeReturn.lc; + + } else { + + throw new Error( 'hufDecode issues' ); + + } + + } + + return true; + + } + + function hufUncompress( uInt8Array, inDataView, inOffset, nCompressed, outBuffer, nRaw ) { + + const outOffset = { value: 0 }; + const initialInOffset = inOffset.value; + + const im = parseUint32( inDataView, inOffset ); + const iM = parseUint32( inDataView, inOffset ); + + inOffset.value += 4; + + const nBits = parseUint32( inDataView, inOffset ); + + inOffset.value += 4; + + if ( im < 0 || im >= HUF_ENCSIZE || iM < 0 || iM >= HUF_ENCSIZE ) { + + throw new Error( 'Something wrong with HUF_ENCSIZE' ); + + } + + const freq = new Array( HUF_ENCSIZE ); + const hdec = new Array( HUF_DECSIZE ); + + hufClearDecTable( hdec ); + + const ni = nCompressed - ( inOffset.value - initialInOffset ); + + hufUnpackEncTable( uInt8Array, inOffset, ni, im, iM, freq ); + + if ( nBits > 8 * ( nCompressed - ( inOffset.value - initialInOffset ) ) ) { + + throw new Error( 'Something wrong with hufUncompress' ); + + } + + hufBuildDecTable( freq, im, iM, hdec ); + + hufDecode( freq, hdec, uInt8Array, inOffset, nBits, iM, nRaw, outBuffer, outOffset ); + + } + + function applyLut( lut, data, nData ) { + + for ( let i = 0; i < nData; ++ i ) { + + data[ i ] = lut[ data[ i ] ]; + + } + + } + + function predictor( source ) { + + for ( let t = 1; t < source.length; t ++ ) { + + const d = source[ t - 1 ] + source[ t ] - 128; + source[ t ] = d; + + } + + } + + function interleaveScalar( source, out ) { + + let t1 = 0; + let t2 = Math.floor( ( source.length + 1 ) / 2 ); + let s = 0; + const stop = source.length - 1; + + while ( true ) { + + if ( s > stop ) break; + out[ s ++ ] = source[ t1 ++ ]; + + if ( s > stop ) break; + out[ s ++ ] = source[ t2 ++ ]; + + } + + } + + function decodeRunLength( source ) { + + let size = source.byteLength; + const out = new Array(); + let p = 0; + + const reader = new DataView( source ); + + while ( size > 0 ) { + + const l = reader.getInt8( p ++ ); + + if ( l < 0 ) { + + const count = - l; + size -= count + 1; + + for ( let i = 0; i < count; i ++ ) { + + out.push( reader.getUint8( p ++ ) ); + + } + + + } else { + + const count = l; + size -= 2; + + const value = reader.getUint8( p ++ ); + + for ( let i = 0; i < count + 1; i ++ ) { + + out.push( value ); + + } + + } + + } + + return out; + + } + + function lossyDctDecode( cscSet, rowPtrs, channelData, acBuffer, dcBuffer, outBuffer ) { + + let dataView = new DataView( outBuffer.buffer ); + + const width = channelData[ cscSet.idx[ 0 ] ].width; + const height = channelData[ cscSet.idx[ 0 ] ].height; + + const numComp = 3; + + const numFullBlocksX = Math.floor( width / 8.0 ); + const numBlocksX = Math.ceil( width / 8.0 ); + const numBlocksY = Math.ceil( height / 8.0 ); + const leftoverX = width - ( numBlocksX - 1 ) * 8; + const leftoverY = height - ( numBlocksY - 1 ) * 8; + + const currAcComp = { value: 0 }; + const currDcComp = new Array( numComp ); + const dctData = new Array( numComp ); + const halfZigBlock = new Array( numComp ); + const rowBlock = new Array( numComp ); + const rowOffsets = new Array( numComp ); + + for ( let comp = 0; comp < numComp; ++ comp ) { + + rowOffsets[ comp ] = rowPtrs[ cscSet.idx[ comp ] ]; + currDcComp[ comp ] = ( comp < 1 ) ? 0 : currDcComp[ comp - 1 ] + numBlocksX * numBlocksY; + dctData[ comp ] = new Float32Array( 64 ); + halfZigBlock[ comp ] = new Uint16Array( 64 ); + rowBlock[ comp ] = new Uint16Array( numBlocksX * 64 ); + + } + + for ( let blocky = 0; blocky < numBlocksY; ++ blocky ) { + + let maxY = 8; + + if ( blocky == numBlocksY - 1 ) + maxY = leftoverY; + + let maxX = 8; + + for ( let blockx = 0; blockx < numBlocksX; ++ blockx ) { + + if ( blockx == numBlocksX - 1 ) + maxX = leftoverX; + + for ( let comp = 0; comp < numComp; ++ comp ) { + + halfZigBlock[ comp ].fill( 0 ); + + // set block DC component + halfZigBlock[ comp ][ 0 ] = dcBuffer[ currDcComp[ comp ] ++ ]; + // set block AC components + unRleAC( currAcComp, acBuffer, halfZigBlock[ comp ] ); + + // UnZigZag block to float + unZigZag( halfZigBlock[ comp ], dctData[ comp ] ); + // decode float dct + dctInverse( dctData[ comp ] ); + + } + + if ( numComp == 3 ) { + + csc709Inverse( dctData ); + + } + + for ( let comp = 0; comp < numComp; ++ comp ) { + + convertToHalf( dctData[ comp ], rowBlock[ comp ], blockx * 64 ); + + } + + } // blockx + + let offset = 0; + + for ( let comp = 0; comp < numComp; ++ comp ) { + + const type = channelData[ cscSet.idx[ comp ] ].type; + + for ( let y = 8 * blocky; y < 8 * blocky + maxY; ++ y ) { + + offset = rowOffsets[ comp ][ y ]; + + for ( let blockx = 0; blockx < numFullBlocksX; ++ blockx ) { + + const src = blockx * 64 + ( ( y & 0x7 ) * 8 ); + + dataView.setUint16( offset + 0 * INT16_SIZE * type, rowBlock[ comp ][ src + 0 ], true ); + dataView.setUint16( offset + 1 * INT16_SIZE * type, rowBlock[ comp ][ src + 1 ], true ); + dataView.setUint16( offset + 2 * INT16_SIZE * type, rowBlock[ comp ][ src + 2 ], true ); + dataView.setUint16( offset + 3 * INT16_SIZE * type, rowBlock[ comp ][ src + 3 ], true ); + + dataView.setUint16( offset + 4 * INT16_SIZE * type, rowBlock[ comp ][ src + 4 ], true ); + dataView.setUint16( offset + 5 * INT16_SIZE * type, rowBlock[ comp ][ src + 5 ], true ); + dataView.setUint16( offset + 6 * INT16_SIZE * type, rowBlock[ comp ][ src + 6 ], true ); + dataView.setUint16( offset + 7 * INT16_SIZE * type, rowBlock[ comp ][ src + 7 ], true ); + + offset += 8 * INT16_SIZE * type; + + } + + } + + // handle partial X blocks + if ( numFullBlocksX != numBlocksX ) { + + for ( let y = 8 * blocky; y < 8 * blocky + maxY; ++ y ) { + + const offset = rowOffsets[ comp ][ y ] + 8 * numFullBlocksX * INT16_SIZE * type; + const src = numFullBlocksX * 64 + ( ( y & 0x7 ) * 8 ); + + for ( let x = 0; x < maxX; ++ x ) { + + dataView.setUint16( offset + x * INT16_SIZE * type, rowBlock[ comp ][ src + x ], true ); + + } + + } + + } + + } // comp + + } // blocky + + const halfRow = new Uint16Array( width ); + dataView = new DataView( outBuffer.buffer ); + + // convert channels back to float, if needed + for ( let comp = 0; comp < numComp; ++ comp ) { + + channelData[ cscSet.idx[ comp ] ].decoded = true; + const type = channelData[ cscSet.idx[ comp ] ].type; + + if ( channelData[ comp ].type != 2 ) continue; + + for ( let y = 0; y < height; ++ y ) { + + const offset = rowOffsets[ comp ][ y ]; + + for ( let x = 0; x < width; ++ x ) { + + halfRow[ x ] = dataView.getUint16( offset + x * INT16_SIZE * type, true ); + + } + + for ( let x = 0; x < width; ++ x ) { + + dataView.setFloat32( offset + x * INT16_SIZE * type, decodeFloat16( halfRow[ x ] ), true ); + + } + + } + + } + + } + + function unRleAC( currAcComp, acBuffer, halfZigBlock ) { + + let acValue; + let dctComp = 1; + + while ( dctComp < 64 ) { + + acValue = acBuffer[ currAcComp.value ]; + + if ( acValue == 0xff00 ) { + + dctComp = 64; + + } else if ( acValue >> 8 == 0xff ) { + + dctComp += acValue & 0xff; + + } else { + + halfZigBlock[ dctComp ] = acValue; + dctComp ++; + + } + + currAcComp.value ++; + + } + + } + + function unZigZag( src, dst ) { + + dst[ 0 ] = decodeFloat16( src[ 0 ] ); + dst[ 1 ] = decodeFloat16( src[ 1 ] ); + dst[ 2 ] = decodeFloat16( src[ 5 ] ); + dst[ 3 ] = decodeFloat16( src[ 6 ] ); + dst[ 4 ] = decodeFloat16( src[ 14 ] ); + dst[ 5 ] = decodeFloat16( src[ 15 ] ); + dst[ 6 ] = decodeFloat16( src[ 27 ] ); + dst[ 7 ] = decodeFloat16( src[ 28 ] ); + dst[ 8 ] = decodeFloat16( src[ 2 ] ); + dst[ 9 ] = decodeFloat16( src[ 4 ] ); + + dst[ 10 ] = decodeFloat16( src[ 7 ] ); + dst[ 11 ] = decodeFloat16( src[ 13 ] ); + dst[ 12 ] = decodeFloat16( src[ 16 ] ); + dst[ 13 ] = decodeFloat16( src[ 26 ] ); + dst[ 14 ] = decodeFloat16( src[ 29 ] ); + dst[ 15 ] = decodeFloat16( src[ 42 ] ); + dst[ 16 ] = decodeFloat16( src[ 3 ] ); + dst[ 17 ] = decodeFloat16( src[ 8 ] ); + dst[ 18 ] = decodeFloat16( src[ 12 ] ); + dst[ 19 ] = decodeFloat16( src[ 17 ] ); + + dst[ 20 ] = decodeFloat16( src[ 25 ] ); + dst[ 21 ] = decodeFloat16( src[ 30 ] ); + dst[ 22 ] = decodeFloat16( src[ 41 ] ); + dst[ 23 ] = decodeFloat16( src[ 43 ] ); + dst[ 24 ] = decodeFloat16( src[ 9 ] ); + dst[ 25 ] = decodeFloat16( src[ 11 ] ); + dst[ 26 ] = decodeFloat16( src[ 18 ] ); + dst[ 27 ] = decodeFloat16( src[ 24 ] ); + dst[ 28 ] = decodeFloat16( src[ 31 ] ); + dst[ 29 ] = decodeFloat16( src[ 40 ] ); + + dst[ 30 ] = decodeFloat16( src[ 44 ] ); + dst[ 31 ] = decodeFloat16( src[ 53 ] ); + dst[ 32 ] = decodeFloat16( src[ 10 ] ); + dst[ 33 ] = decodeFloat16( src[ 19 ] ); + dst[ 34 ] = decodeFloat16( src[ 23 ] ); + dst[ 35 ] = decodeFloat16( src[ 32 ] ); + dst[ 36 ] = decodeFloat16( src[ 39 ] ); + dst[ 37 ] = decodeFloat16( src[ 45 ] ); + dst[ 38 ] = decodeFloat16( src[ 52 ] ); + dst[ 39 ] = decodeFloat16( src[ 54 ] ); + + dst[ 40 ] = decodeFloat16( src[ 20 ] ); + dst[ 41 ] = decodeFloat16( src[ 22 ] ); + dst[ 42 ] = decodeFloat16( src[ 33 ] ); + dst[ 43 ] = decodeFloat16( src[ 38 ] ); + dst[ 44 ] = decodeFloat16( src[ 46 ] ); + dst[ 45 ] = decodeFloat16( src[ 51 ] ); + dst[ 46 ] = decodeFloat16( src[ 55 ] ); + dst[ 47 ] = decodeFloat16( src[ 60 ] ); + dst[ 48 ] = decodeFloat16( src[ 21 ] ); + dst[ 49 ] = decodeFloat16( src[ 34 ] ); + + dst[ 50 ] = decodeFloat16( src[ 37 ] ); + dst[ 51 ] = decodeFloat16( src[ 47 ] ); + dst[ 52 ] = decodeFloat16( src[ 50 ] ); + dst[ 53 ] = decodeFloat16( src[ 56 ] ); + dst[ 54 ] = decodeFloat16( src[ 59 ] ); + dst[ 55 ] = decodeFloat16( src[ 61 ] ); + dst[ 56 ] = decodeFloat16( src[ 35 ] ); + dst[ 57 ] = decodeFloat16( src[ 36 ] ); + dst[ 58 ] = decodeFloat16( src[ 48 ] ); + dst[ 59 ] = decodeFloat16( src[ 49 ] ); + + dst[ 60 ] = decodeFloat16( src[ 57 ] ); + dst[ 61 ] = decodeFloat16( src[ 58 ] ); + dst[ 62 ] = decodeFloat16( src[ 62 ] ); + dst[ 63 ] = decodeFloat16( src[ 63 ] ); + + } + + function dctInverse( data ) { + + const a = 0.5 * Math.cos( 3.14159 / 4.0 ); + const b = 0.5 * Math.cos( 3.14159 / 16.0 ); + const c = 0.5 * Math.cos( 3.14159 / 8.0 ); + const d = 0.5 * Math.cos( 3.0 * 3.14159 / 16.0 ); + const e = 0.5 * Math.cos( 5.0 * 3.14159 / 16.0 ); + const f = 0.5 * Math.cos( 3.0 * 3.14159 / 8.0 ); + const g = 0.5 * Math.cos( 7.0 * 3.14159 / 16.0 ); + + const alpha = new Array( 4 ); + const beta = new Array( 4 ); + const theta = new Array( 4 ); + const gamma = new Array( 4 ); + + for ( let row = 0; row < 8; ++ row ) { + + const rowPtr = row * 8; + + alpha[ 0 ] = c * data[ rowPtr + 2 ]; + alpha[ 1 ] = f * data[ rowPtr + 2 ]; + alpha[ 2 ] = c * data[ rowPtr + 6 ]; + alpha[ 3 ] = f * data[ rowPtr + 6 ]; + + beta[ 0 ] = b * data[ rowPtr + 1 ] + d * data[ rowPtr + 3 ] + e * data[ rowPtr + 5 ] + g * data[ rowPtr + 7 ]; + beta[ 1 ] = d * data[ rowPtr + 1 ] - g * data[ rowPtr + 3 ] - b * data[ rowPtr + 5 ] - e * data[ rowPtr + 7 ]; + beta[ 2 ] = e * data[ rowPtr + 1 ] - b * data[ rowPtr + 3 ] + g * data[ rowPtr + 5 ] + d * data[ rowPtr + 7 ]; + beta[ 3 ] = g * data[ rowPtr + 1 ] - e * data[ rowPtr + 3 ] + d * data[ rowPtr + 5 ] - b * data[ rowPtr + 7 ]; + + theta[ 0 ] = a * ( data[ rowPtr + 0 ] + data[ rowPtr + 4 ] ); + theta[ 3 ] = a * ( data[ rowPtr + 0 ] - data[ rowPtr + 4 ] ); + theta[ 1 ] = alpha[ 0 ] + alpha[ 3 ]; + theta[ 2 ] = alpha[ 1 ] - alpha[ 2 ]; + + gamma[ 0 ] = theta[ 0 ] + theta[ 1 ]; + gamma[ 1 ] = theta[ 3 ] + theta[ 2 ]; + gamma[ 2 ] = theta[ 3 ] - theta[ 2 ]; + gamma[ 3 ] = theta[ 0 ] - theta[ 1 ]; + + data[ rowPtr + 0 ] = gamma[ 0 ] + beta[ 0 ]; + data[ rowPtr + 1 ] = gamma[ 1 ] + beta[ 1 ]; + data[ rowPtr + 2 ] = gamma[ 2 ] + beta[ 2 ]; + data[ rowPtr + 3 ] = gamma[ 3 ] + beta[ 3 ]; + + data[ rowPtr + 4 ] = gamma[ 3 ] - beta[ 3 ]; + data[ rowPtr + 5 ] = gamma[ 2 ] - beta[ 2 ]; + data[ rowPtr + 6 ] = gamma[ 1 ] - beta[ 1 ]; + data[ rowPtr + 7 ] = gamma[ 0 ] - beta[ 0 ]; + + } + + for ( let column = 0; column < 8; ++ column ) { + + alpha[ 0 ] = c * data[ 16 + column ]; + alpha[ 1 ] = f * data[ 16 + column ]; + alpha[ 2 ] = c * data[ 48 + column ]; + alpha[ 3 ] = f * data[ 48 + column ]; + + beta[ 0 ] = b * data[ 8 + column ] + d * data[ 24 + column ] + e * data[ 40 + column ] + g * data[ 56 + column ]; + beta[ 1 ] = d * data[ 8 + column ] - g * data[ 24 + column ] - b * data[ 40 + column ] - e * data[ 56 + column ]; + beta[ 2 ] = e * data[ 8 + column ] - b * data[ 24 + column ] + g * data[ 40 + column ] + d * data[ 56 + column ]; + beta[ 3 ] = g * data[ 8 + column ] - e * data[ 24 + column ] + d * data[ 40 + column ] - b * data[ 56 + column ]; + + theta[ 0 ] = a * ( data[ column ] + data[ 32 + column ] ); + theta[ 3 ] = a * ( data[ column ] - data[ 32 + column ] ); + + theta[ 1 ] = alpha[ 0 ] + alpha[ 3 ]; + theta[ 2 ] = alpha[ 1 ] - alpha[ 2 ]; + + gamma[ 0 ] = theta[ 0 ] + theta[ 1 ]; + gamma[ 1 ] = theta[ 3 ] + theta[ 2 ]; + gamma[ 2 ] = theta[ 3 ] - theta[ 2 ]; + gamma[ 3 ] = theta[ 0 ] - theta[ 1 ]; + + data[ 0 + column ] = gamma[ 0 ] + beta[ 0 ]; + data[ 8 + column ] = gamma[ 1 ] + beta[ 1 ]; + data[ 16 + column ] = gamma[ 2 ] + beta[ 2 ]; + data[ 24 + column ] = gamma[ 3 ] + beta[ 3 ]; + + data[ 32 + column ] = gamma[ 3 ] - beta[ 3 ]; + data[ 40 + column ] = gamma[ 2 ] - beta[ 2 ]; + data[ 48 + column ] = gamma[ 1 ] - beta[ 1 ]; + data[ 56 + column ] = gamma[ 0 ] - beta[ 0 ]; + + } + + } + + function csc709Inverse( data ) { + + for ( let i = 0; i < 64; ++ i ) { + + const y = data[ 0 ][ i ]; + const cb = data[ 1 ][ i ]; + const cr = data[ 2 ][ i ]; + + data[ 0 ][ i ] = y + 1.5747 * cr; + data[ 1 ][ i ] = y - 0.1873 * cb - 0.4682 * cr; + data[ 2 ][ i ] = y + 1.8556 * cb; + + } + + } + + function convertToHalf( src, dst, idx ) { + + for ( let i = 0; i < 64; ++ i ) { + + dst[ idx + i ] = DataUtils.toHalfFloat( toLinear( src[ i ] ) ); + + } + + } + + function toLinear( float ) { + + if ( float <= 1 ) { + + return Math.sign( float ) * Math.pow( Math.abs( float ), 2.2 ); + + } else { + + return Math.sign( float ) * Math.pow( logBase, Math.abs( float ) - 1.0 ); + + } + + } + + function uncompressRAW( info ) { + + return new DataView( info.array.buffer, info.offset.value, info.size ); + + } + + function uncompressRLE( info ) { + + const compressed = info.viewer.buffer.slice( info.offset.value, info.offset.value + info.size ); + + const rawBuffer = new Uint8Array( decodeRunLength( compressed ) ); + const tmpBuffer = new Uint8Array( rawBuffer.length ); + + predictor( rawBuffer ); // revert predictor + + interleaveScalar( rawBuffer, tmpBuffer ); // interleave pixels + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressZIP( info ) { + + const compressed = info.array.slice( info.offset.value, info.offset.value + info.size ); + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.EXRLoader: External library fflate.min.js required.' ); + + } + + const rawBuffer = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + const tmpBuffer = new Uint8Array( rawBuffer.length ); + + predictor( rawBuffer ); // revert predictor + + interleaveScalar( rawBuffer, tmpBuffer ); // interleave pixels + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressPIZ( info ) { + + const inDataView = info.viewer; + const inOffset = { value: info.offset.value }; + + const outBuffer = new Uint16Array( info.width * info.scanlineBlockSize * ( info.channels * info.type ) ); + const bitmap = new Uint8Array( BITMAP_SIZE ); + + // Setup channel info + let outBufferEnd = 0; + const pizChannelData = new Array( info.channels ); + for ( let i = 0; i < info.channels; i ++ ) { + + pizChannelData[ i ] = {}; + pizChannelData[ i ][ 'start' ] = outBufferEnd; + pizChannelData[ i ][ 'end' ] = pizChannelData[ i ][ 'start' ]; + pizChannelData[ i ][ 'nx' ] = info.width; + pizChannelData[ i ][ 'ny' ] = info.lines; + pizChannelData[ i ][ 'size' ] = info.type; + + outBufferEnd += pizChannelData[ i ].nx * pizChannelData[ i ].ny * pizChannelData[ i ].size; + + } + + // Read range compression data + + const minNonZero = parseUint16( inDataView, inOffset ); + const maxNonZero = parseUint16( inDataView, inOffset ); + + if ( maxNonZero >= BITMAP_SIZE ) { + + throw new Error( 'Something is wrong with PIZ_COMPRESSION BITMAP_SIZE' ); + + } + + if ( minNonZero <= maxNonZero ) { + + for ( let i = 0; i < maxNonZero - minNonZero + 1; i ++ ) { + + bitmap[ i + minNonZero ] = parseUint8( inDataView, inOffset ); + + } + + } + + // Reverse LUT + const lut = new Uint16Array( USHORT_RANGE ); + const maxValue = reverseLutFromBitmap( bitmap, lut ); + + const length = parseUint32( inDataView, inOffset ); + + // Huffman decoding + hufUncompress( info.array, inDataView, inOffset, length, outBuffer, outBufferEnd ); + + // Wavelet decoding + for ( let i = 0; i < info.channels; ++ i ) { + + const cd = pizChannelData[ i ]; + + for ( let j = 0; j < pizChannelData[ i ].size; ++ j ) { + + wav2Decode( + outBuffer, + cd.start + j, + cd.nx, + cd.size, + cd.ny, + cd.nx * cd.size, + maxValue + ); + + } + + } + + // Expand the pixel data to their original range + applyLut( lut, outBuffer, outBufferEnd ); + + // Rearrange the pixel data into the format expected by the caller. + let tmpOffset = 0; + const tmpBuffer = new Uint8Array( outBuffer.buffer.byteLength ); + for ( let y = 0; y < info.lines; y ++ ) { + + for ( let c = 0; c < info.channels; c ++ ) { + + const cd = pizChannelData[ c ]; + + const n = cd.nx * cd.size; + const cp = new Uint8Array( outBuffer.buffer, cd.end * INT16_SIZE, n * INT16_SIZE ); + + tmpBuffer.set( cp, tmpOffset ); + tmpOffset += n * INT16_SIZE; + cd.end += n; + + } + + } + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressPXR( info ) { + + const compressed = info.array.slice( info.offset.value, info.offset.value + info.size ); + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.EXRLoader: External library fflate.min.js required.' ); + + } + + const rawBuffer = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + + const sz = info.lines * info.channels * info.width; + const tmpBuffer = ( info.type == 1 ) ? new Uint16Array( sz ) : new Uint32Array( sz ); + + let tmpBufferEnd = 0; + let writePtr = 0; + const ptr = new Array( 4 ); + + for ( let y = 0; y < info.lines; y ++ ) { + + for ( let c = 0; c < info.channels; c ++ ) { + + let pixel = 0; + + switch ( info.type ) { + + case 1: + + ptr[ 0 ] = tmpBufferEnd; + ptr[ 1 ] = ptr[ 0 ] + info.width; + tmpBufferEnd = ptr[ 1 ] + info.width; + + for ( let j = 0; j < info.width; ++ j ) { + + const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 8 ) | rawBuffer[ ptr[ 1 ] ++ ]; + + pixel += diff; + + tmpBuffer[ writePtr ] = pixel; + writePtr ++; + + } + + break; + + case 2: + + ptr[ 0 ] = tmpBufferEnd; + ptr[ 1 ] = ptr[ 0 ] + info.width; + ptr[ 2 ] = ptr[ 1 ] + info.width; + tmpBufferEnd = ptr[ 2 ] + info.width; + + for ( let j = 0; j < info.width; ++ j ) { + + const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 24 ) | ( rawBuffer[ ptr[ 1 ] ++ ] << 16 ) | ( rawBuffer[ ptr[ 2 ] ++ ] << 8 ); + + pixel += diff; + + tmpBuffer[ writePtr ] = pixel; + writePtr ++; + + } + + break; + + } + + } + + } + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressDWA( info ) { + + const inDataView = info.viewer; + const inOffset = { value: info.offset.value }; + const outBuffer = new Uint8Array( info.width * info.lines * ( info.channels * info.type * INT16_SIZE ) ); + + // Read compression header information + const dwaHeader = { + + version: parseInt64( inDataView, inOffset ), + unknownUncompressedSize: parseInt64( inDataView, inOffset ), + unknownCompressedSize: parseInt64( inDataView, inOffset ), + acCompressedSize: parseInt64( inDataView, inOffset ), + dcCompressedSize: parseInt64( inDataView, inOffset ), + rleCompressedSize: parseInt64( inDataView, inOffset ), + rleUncompressedSize: parseInt64( inDataView, inOffset ), + rleRawSize: parseInt64( inDataView, inOffset ), + totalAcUncompressedCount: parseInt64( inDataView, inOffset ), + totalDcUncompressedCount: parseInt64( inDataView, inOffset ), + acCompression: parseInt64( inDataView, inOffset ) + + }; + + if ( dwaHeader.version < 2 ) + throw new Error( 'EXRLoader.parse: ' + EXRHeader.compression + ' version ' + dwaHeader.version + ' is unsupported' ); + + // Read channel ruleset information + const channelRules = new Array(); + let ruleSize = parseUint16( inDataView, inOffset ) - INT16_SIZE; + + while ( ruleSize > 0 ) { + + const name = parseNullTerminatedString( inDataView.buffer, inOffset ); + const value = parseUint8( inDataView, inOffset ); + const compression = ( value >> 2 ) & 3; + const csc = ( value >> 4 ) - 1; + const index = new Int8Array( [ csc ] )[ 0 ]; + const type = parseUint8( inDataView, inOffset ); + + channelRules.push( { + name: name, + index: index, + type: type, + compression: compression, + } ); + + ruleSize -= name.length + 3; + + } + + // Classify channels + const channels = EXRHeader.channels; + const channelData = new Array( info.channels ); + + for ( let i = 0; i < info.channels; ++ i ) { + + const cd = channelData[ i ] = {}; + const channel = channels[ i ]; + + cd.name = channel.name; + cd.compression = UNKNOWN; + cd.decoded = false; + cd.type = channel.pixelType; + cd.pLinear = channel.pLinear; + cd.width = info.width; + cd.height = info.lines; + + } + + const cscSet = { + idx: new Array( 3 ) + }; + + for ( let offset = 0; offset < info.channels; ++ offset ) { + + const cd = channelData[ offset ]; + + for ( let i = 0; i < channelRules.length; ++ i ) { + + const rule = channelRules[ i ]; + + if ( cd.name == rule.name ) { + + cd.compression = rule.compression; + + if ( rule.index >= 0 ) { + + cscSet.idx[ rule.index ] = offset; + + } + + cd.offset = offset; + + } + + } + + } + + let acBuffer, dcBuffer, rleBuffer; + + // Read DCT - AC component data + if ( dwaHeader.acCompressedSize > 0 ) { + + switch ( dwaHeader.acCompression ) { + + case STATIC_HUFFMAN: + + acBuffer = new Uint16Array( dwaHeader.totalAcUncompressedCount ); + hufUncompress( info.array, inDataView, inOffset, dwaHeader.acCompressedSize, acBuffer, dwaHeader.totalAcUncompressedCount ); + break; + + case DEFLATE: + + const compressed = info.array.slice( inOffset.value, inOffset.value + dwaHeader.totalAcUncompressedCount ); + const data = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + acBuffer = new Uint16Array( data.buffer ); + inOffset.value += dwaHeader.totalAcUncompressedCount; + break; + + } + + + } + + // Read DCT - DC component data + if ( dwaHeader.dcCompressedSize > 0 ) { + + const zlibInfo = { + array: info.array, + offset: inOffset, + size: dwaHeader.dcCompressedSize + }; + dcBuffer = new Uint16Array( uncompressZIP( zlibInfo ).buffer ); + inOffset.value += dwaHeader.dcCompressedSize; + + } + + // Read RLE compressed data + if ( dwaHeader.rleRawSize > 0 ) { + + const compressed = info.array.slice( inOffset.value, inOffset.value + dwaHeader.rleCompressedSize ); + const data = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + rleBuffer = decodeRunLength( data.buffer ); + + inOffset.value += dwaHeader.rleCompressedSize; + + } + + // Prepare outbuffer data offset + let outBufferEnd = 0; + const rowOffsets = new Array( channelData.length ); + for ( let i = 0; i < rowOffsets.length; ++ i ) { + + rowOffsets[ i ] = new Array(); + + } + + for ( let y = 0; y < info.lines; ++ y ) { + + for ( let chan = 0; chan < channelData.length; ++ chan ) { + + rowOffsets[ chan ].push( outBufferEnd ); + outBufferEnd += channelData[ chan ].width * info.type * INT16_SIZE; + + } + + } + + // Lossy DCT decode RGB channels + lossyDctDecode( cscSet, rowOffsets, channelData, acBuffer, dcBuffer, outBuffer ); + + // Decode other channels + for ( let i = 0; i < channelData.length; ++ i ) { + + const cd = channelData[ i ]; + + if ( cd.decoded ) continue; + + switch ( cd.compression ) { + + case RLE: + + let row = 0; + let rleOffset = 0; + + for ( let y = 0; y < info.lines; ++ y ) { + + let rowOffsetBytes = rowOffsets[ i ][ row ]; + + for ( let x = 0; x < cd.width; ++ x ) { + + for ( let byte = 0; byte < INT16_SIZE * cd.type; ++ byte ) { + + outBuffer[ rowOffsetBytes ++ ] = rleBuffer[ rleOffset + byte * cd.width * cd.height ]; + + } + + rleOffset ++; + + } + + row ++; + + } + + break; + + case LOSSY_DCT: // skip + + default: + throw new Error( 'EXRLoader.parse: unsupported channel compression' ); + + } + + } + + return new DataView( outBuffer.buffer ); + + } + + function parseNullTerminatedString( buffer, offset ) { + + const uintBuffer = new Uint8Array( buffer ); + let endOffset = 0; + + while ( uintBuffer[ offset.value + endOffset ] != 0 ) { + + endOffset += 1; + + } + + const stringValue = new TextDecoder().decode( + uintBuffer.slice( offset.value, offset.value + endOffset ) + ); + + offset.value = offset.value + endOffset + 1; + + return stringValue; + + } + + function parseFixedLengthString( buffer, offset, size ) { + + const stringValue = new TextDecoder().decode( + new Uint8Array( buffer ).slice( offset.value, offset.value + size ) + ); + + offset.value = offset.value + size; + + return stringValue; + + } + + function parseRational( dataView, offset ) { + + const x = parseInt32( dataView, offset ); + const y = parseUint32( dataView, offset ); + + return [ x, y ]; + + } + + function parseTimecode( dataView, offset ) { + + const x = parseUint32( dataView, offset ); + const y = parseUint32( dataView, offset ); + + return [ x, y ]; + + } + + function parseInt32( dataView, offset ) { + + const Int32 = dataView.getInt32( offset.value, true ); + + offset.value = offset.value + INT32_SIZE; + + return Int32; + + } + + function parseUint32( dataView, offset ) { + + const Uint32 = dataView.getUint32( offset.value, true ); + + offset.value = offset.value + INT32_SIZE; + + return Uint32; + + } + + function parseUint8Array( uInt8Array, offset ) { + + const Uint8 = uInt8Array[ offset.value ]; + + offset.value = offset.value + INT8_SIZE; + + return Uint8; + + } + + function parseUint8( dataView, offset ) { + + const Uint8 = dataView.getUint8( offset.value ); + + offset.value = offset.value + INT8_SIZE; + + return Uint8; + + } + + const parseInt64 = function ( dataView, offset ) { + + let int; + + if ( 'getBigInt64' in DataView.prototype ) { + + int = Number( dataView.getBigInt64( offset.value, true ) ); + + } else { + + int = dataView.getUint32( offset.value + 4, true ) + Number( dataView.getUint32( offset.value, true ) << 32 ); + + } + + offset.value += ULONG_SIZE; + + return int; + + }; + + function parseFloat32( dataView, offset ) { + + const float = dataView.getFloat32( offset.value, true ); + + offset.value += FLOAT32_SIZE; + + return float; + + } + + function decodeFloat32( dataView, offset ) { + + return DataUtils.toHalfFloat( parseFloat32( dataView, offset ) ); + + } + + // https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript + function decodeFloat16( binary ) { + + const exponent = ( binary & 0x7C00 ) >> 10, + fraction = binary & 0x03FF; + + return ( binary >> 15 ? - 1 : 1 ) * ( + exponent ? + ( + exponent === 0x1F ? + fraction ? NaN : Infinity : + Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 ) + ) : + 6.103515625e-5 * ( fraction / 0x400 ) + ); + + } + + function parseUint16( dataView, offset ) { + + const Uint16 = dataView.getUint16( offset.value, true ); + + offset.value += INT16_SIZE; + + return Uint16; + + } + + function parseFloat16( buffer, offset ) { + + return decodeFloat16( parseUint16( buffer, offset ) ); + + } + + function parseChlist( dataView, buffer, offset, size ) { + + const startOffset = offset.value; + const channels = []; + + while ( offset.value < ( startOffset + size - 1 ) ) { + + const name = parseNullTerminatedString( buffer, offset ); + const pixelType = parseInt32( dataView, offset ); + const pLinear = parseUint8( dataView, offset ); + offset.value += 3; // reserved, three chars + const xSampling = parseInt32( dataView, offset ); + const ySampling = parseInt32( dataView, offset ); + + channels.push( { + name: name, + pixelType: pixelType, + pLinear: pLinear, + xSampling: xSampling, + ySampling: ySampling + } ); + + } + + offset.value += 1; + + return channels; + + } + + function parseChromaticities( dataView, offset ) { + + const redX = parseFloat32( dataView, offset ); + const redY = parseFloat32( dataView, offset ); + const greenX = parseFloat32( dataView, offset ); + const greenY = parseFloat32( dataView, offset ); + const blueX = parseFloat32( dataView, offset ); + const blueY = parseFloat32( dataView, offset ); + const whiteX = parseFloat32( dataView, offset ); + const whiteY = parseFloat32( dataView, offset ); + + return { redX: redX, redY: redY, greenX: greenX, greenY: greenY, blueX: blueX, blueY: blueY, whiteX: whiteX, whiteY: whiteY }; + + } + + function parseCompression( dataView, offset ) { + + const compressionCodes = [ + 'NO_COMPRESSION', + 'RLE_COMPRESSION', + 'ZIPS_COMPRESSION', + 'ZIP_COMPRESSION', + 'PIZ_COMPRESSION', + 'PXR24_COMPRESSION', + 'B44_COMPRESSION', + 'B44A_COMPRESSION', + 'DWAA_COMPRESSION', + 'DWAB_COMPRESSION' + ]; + + const compression = parseUint8( dataView, offset ); + + return compressionCodes[ compression ]; + + } + + function parseBox2i( dataView, offset ) { + + const xMin = parseUint32( dataView, offset ); + const yMin = parseUint32( dataView, offset ); + const xMax = parseUint32( dataView, offset ); + const yMax = parseUint32( dataView, offset ); + + return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax }; + + } + + function parseLineOrder( dataView, offset ) { + + const lineOrders = [ + 'INCREASING_Y' + ]; + + const lineOrder = parseUint8( dataView, offset ); + + return lineOrders[ lineOrder ]; + + } + + function parseV2f( dataView, offset ) { + + const x = parseFloat32( dataView, offset ); + const y = parseFloat32( dataView, offset ); + + return [ x, y ]; + + } + + function parseV3f( dataView, offset ) { + + const x = parseFloat32( dataView, offset ); + const y = parseFloat32( dataView, offset ); + const z = parseFloat32( dataView, offset ); + + return [ x, y, z ]; + + } + + function parseValue( dataView, buffer, offset, type, size ) { + + if ( type === 'string' || type === 'stringvector' || type === 'iccProfile' ) { + + return parseFixedLengthString( buffer, offset, size ); + + } else if ( type === 'chlist' ) { + + return parseChlist( dataView, buffer, offset, size ); + + } else if ( type === 'chromaticities' ) { + + return parseChromaticities( dataView, offset ); + + } else if ( type === 'compression' ) { + + return parseCompression( dataView, offset ); + + } else if ( type === 'box2i' ) { + + return parseBox2i( dataView, offset ); + + } else if ( type === 'lineOrder' ) { + + return parseLineOrder( dataView, offset ); + + } else if ( type === 'float' ) { + + return parseFloat32( dataView, offset ); + + } else if ( type === 'v2f' ) { + + return parseV2f( dataView, offset ); + + } else if ( type === 'v3f' ) { + + return parseV3f( dataView, offset ); + + } else if ( type === 'int' ) { + + return parseInt32( dataView, offset ); + + } else if ( type === 'rational' ) { + + return parseRational( dataView, offset ); + + } else if ( type === 'timecode' ) { + + return parseTimecode( dataView, offset ); + + } else if ( type === 'preview' ) { + + offset.value += size; + return 'skipped'; + + } else { + + offset.value += size; + return undefined; + + } + + } + + function parseHeader( dataView, buffer, offset ) { + + const EXRHeader = {}; + + if ( dataView.getUint32( 0, true ) != 20000630 ) { // magic + + throw new Error( 'THREE.EXRLoader: provided file doesn\'t appear to be in OpenEXR format.' ); + + } + + EXRHeader.version = dataView.getUint8( 4 ); + + const spec = dataView.getUint8( 5 ); // fullMask + + EXRHeader.spec = { + singleTile: !! ( spec & 2 ), + longName: !! ( spec & 4 ), + deepFormat: !! ( spec & 8 ), + multiPart: !! ( spec & 16 ), + }; + + // start of header + + offset.value = 8; // start at 8 - after pre-amble + + let keepReading = true; + + while ( keepReading ) { + + const attributeName = parseNullTerminatedString( buffer, offset ); + + if ( attributeName == 0 ) { + + keepReading = false; + + } else { + + const attributeType = parseNullTerminatedString( buffer, offset ); + const attributeSize = parseUint32( dataView, offset ); + const attributeValue = parseValue( dataView, buffer, offset, attributeType, attributeSize ); + + if ( attributeValue === undefined ) { + + console.warn( `EXRLoader.parse: skipped unknown header attribute type \'${attributeType}\'.` ); + + } else { + + EXRHeader[ attributeName ] = attributeValue; + + } + + } + + } + + if ( spec != 0 ) { + + console.error( 'EXRHeader:', EXRHeader ); + throw new Error( 'THREE.EXRLoader: provided file is currently unsupported.' ); + + } + + return EXRHeader; + + } + + function setupDecoder( EXRHeader, dataView, uInt8Array, offset, outputType ) { + + const EXRDecoder = { + size: 0, + viewer: dataView, + array: uInt8Array, + offset: offset, + width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1, + height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1, + channels: EXRHeader.channels.length, + bytesPerLine: null, + lines: null, + inputSize: null, + type: EXRHeader.channels[ 0 ].pixelType, + uncompress: null, + getter: null, + format: null, + encoding: null, + }; + + switch ( EXRHeader.compression ) { + + case 'NO_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressRAW; + break; + + case 'RLE_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressRLE; + break; + + case 'ZIPS_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressZIP; + break; + + case 'ZIP_COMPRESSION': + EXRDecoder.lines = 16; + EXRDecoder.uncompress = uncompressZIP; + break; + + case 'PIZ_COMPRESSION': + EXRDecoder.lines = 32; + EXRDecoder.uncompress = uncompressPIZ; + break; + + case 'PXR24_COMPRESSION': + EXRDecoder.lines = 16; + EXRDecoder.uncompress = uncompressPXR; + break; + + case 'DWAA_COMPRESSION': + EXRDecoder.lines = 32; + EXRDecoder.uncompress = uncompressDWA; + break; + + case 'DWAB_COMPRESSION': + EXRDecoder.lines = 256; + EXRDecoder.uncompress = uncompressDWA; + break; + + default: + throw new Error( 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported' ); + + } + + EXRDecoder.scanlineBlockSize = EXRDecoder.lines; + + if ( EXRDecoder.type == 1 ) { + + // half + switch ( outputType ) { + + case FloatType: + EXRDecoder.getter = parseFloat16; + EXRDecoder.inputSize = INT16_SIZE; + break; + + case HalfFloatType: + EXRDecoder.getter = parseUint16; + EXRDecoder.inputSize = INT16_SIZE; + break; + + } + + } else if ( EXRDecoder.type == 2 ) { + + // float + switch ( outputType ) { + + case FloatType: + EXRDecoder.getter = parseFloat32; + EXRDecoder.inputSize = FLOAT32_SIZE; + break; + + case HalfFloatType: + EXRDecoder.getter = decodeFloat32; + EXRDecoder.inputSize = FLOAT32_SIZE; + + } + + } else { + + throw new Error( 'EXRLoader.parse: unsupported pixelType ' + EXRDecoder.type + ' for ' + EXRHeader.compression + '.' ); + + } + + EXRDecoder.blockCount = ( EXRHeader.dataWindow.yMax + 1 ) / EXRDecoder.scanlineBlockSize; + + for ( let i = 0; i < EXRDecoder.blockCount; i ++ ) + parseInt64( dataView, offset ); // scanlineOffset + + // we should be passed the scanline offset table, ready to start reading pixel data. + + // RGB images will be converted to RGBA format, preventing software emulation in select devices. + EXRDecoder.outputChannels = ( ( EXRDecoder.channels == 3 ) ? 4 : EXRDecoder.channels ); + const size = EXRDecoder.width * EXRDecoder.height * EXRDecoder.outputChannels; + + switch ( outputType ) { + + case FloatType: + EXRDecoder.byteArray = new Float32Array( size ); + + // Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten + if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + EXRDecoder.byteArray.fill( 1, 0, size ); + + break; + + case HalfFloatType: + EXRDecoder.byteArray = new Uint16Array( size ); + + if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + EXRDecoder.byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1 + + break; + + default: + console.error( 'THREE.EXRLoader: unsupported type: ', outputType ); + break; + + } + + EXRDecoder.bytesPerLine = EXRDecoder.width * EXRDecoder.inputSize * EXRDecoder.channels; + + if ( EXRDecoder.outputChannels == 4 ) { + + EXRDecoder.format = RGBAFormat; + EXRDecoder.encoding = LinearEncoding; + + } else { + + EXRDecoder.format = RedFormat; + EXRDecoder.encoding = LinearEncoding; + + } + + return EXRDecoder; + + } + + // start parsing file [START] + + const bufferDataView = new DataView( buffer ); + const uInt8Array = new Uint8Array( buffer ); + const offset = { value: 0 }; + + // get header information and validate format. + const EXRHeader = parseHeader( bufferDataView, buffer, offset ); + + // get input compression information and prepare decoding. + const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type ); + + const tmpOffset = { value: 0 }; + const channelOffsets = { R: 0, G: 1, B: 2, A: 3, Y: 0 }; + + for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.scanlineBlockSize; scanlineBlockIdx ++ ) { + + const line = parseUint32( bufferDataView, offset ); // line_no + EXRDecoder.size = parseUint32( bufferDataView, offset ); // data_len + EXRDecoder.lines = ( ( line + EXRDecoder.scanlineBlockSize > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.scanlineBlockSize ); + + const isCompressed = EXRDecoder.size < EXRDecoder.lines * EXRDecoder.bytesPerLine; + const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder ); + + offset.value += EXRDecoder.size; + + for ( let line_y = 0; line_y < EXRDecoder.scanlineBlockSize; line_y ++ ) { + + const true_y = line_y + scanlineBlockIdx * EXRDecoder.scanlineBlockSize; + if ( true_y >= EXRDecoder.height ) break; + + for ( let channelID = 0; channelID < EXRDecoder.channels; channelID ++ ) { + + const cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ]; + + for ( let x = 0; x < EXRDecoder.width; x ++ ) { + + tmpOffset.value = ( line_y * ( EXRDecoder.channels * EXRDecoder.width ) + channelID * EXRDecoder.width + x ) * EXRDecoder.inputSize; + const outIndex = ( EXRDecoder.height - 1 - true_y ) * ( EXRDecoder.width * EXRDecoder.outputChannels ) + x * EXRDecoder.outputChannels + cOff; + EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset ); + + } + + } + + } + + } + + return { + header: EXRHeader, + width: EXRDecoder.width, + height: EXRDecoder.height, + data: EXRDecoder.byteArray, + format: EXRDecoder.format, + encoding: EXRDecoder.encoding, + type: this.type, + }; + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + function onLoadCallback( texture, texData ) { + + texture.encoding = texData.encoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.flipY = false; + + if ( onLoad ) onLoad( texture, texData ); + + } + + return super.load( url, onLoadCallback, onProgress, onError ); + + } + +} + +export { EXRLoader }; diff --git a/jsm/loaders/FBXLoader.js b/jsm/loaders/FBXLoader.js new file mode 100644 index 0000000..d6a3d5e --- /dev/null +++ b/jsm/loaders/FBXLoader.js @@ -0,0 +1,4128 @@ +import { + AmbientLight, + AnimationClip, + Bone, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DirectionalLight, + EquirectangularReflectionMapping, + Euler, + FileLoader, + Float32BufferAttribute, + Group, + Line, + LineBasicMaterial, + Loader, + LoaderUtils, + MathUtils, + Matrix3, + Matrix4, + Mesh, + MeshLambertMaterial, + MeshPhongMaterial, + NumberKeyframeTrack, + Object3D, + OrthographicCamera, + PerspectiveCamera, + PointLight, + PropertyBinding, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Skeleton, + SkinnedMesh, + SpotLight, + Texture, + TextureLoader, + Uint16BufferAttribute, + Vector3, + Vector4, + VectorKeyframeTrack, + sRGBEncoding +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; +import { NURBSCurve } from '../curves/NURBSCurve.js'; + +/** + * Loader loads FBX file and generates Group representing FBX scene. + * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format + * Versions lower than this may load but will probably have errors + * + * Needs Support: + * Morph normals / blend shape normals + * + * FBX format references: + * https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference) + * + * Binary format specification: + * https://code.blender.org/2013/08/fbx-binary-file-format-specification/ + */ + + +let fbxTree; +let connections; +let sceneGraph; + +class FBXLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( scope.path === '' ) ? LoaderUtils.extractUrlBase( url ) : scope.path; + + const loader = new FileLoader( this.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + + loader.load( url, function ( buffer ) { + + try { + + onLoad( scope.parse( buffer, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( FBXBuffer, path ) { + + if ( isFbxFormatBinary( FBXBuffer ) ) { + + fbxTree = new BinaryParser().parse( FBXBuffer ); + + } else { + + const FBXText = convertArrayBufferToString( FBXBuffer ); + + if ( ! isFbxFormatASCII( FBXText ) ) { + + throw new Error( 'THREE.FBXLoader: Unknown format.' ); + + } + + if ( getFbxVersion( FBXText ) < 7000 ) { + + throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) ); + + } + + fbxTree = new TextParser().parse( FBXText ); + + } + + // console.log( fbxTree ); + + const textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + + return new FBXTreeParser( textureLoader, this.manager ).parse( fbxTree ); + + } + +} + +// Parse the FBXTree object returned by the BinaryParser or TextParser and return a Group +class FBXTreeParser { + + constructor( textureLoader, manager ) { + + this.textureLoader = textureLoader; + this.manager = manager; + + } + + parse() { + + connections = this.parseConnections(); + + const images = this.parseImages(); + const textures = this.parseTextures( images ); + const materials = this.parseMaterials( textures ); + const deformers = this.parseDeformers(); + const geometryMap = new GeometryParser().parse( deformers ); + + this.parseScene( deformers, geometryMap, materials ); + + return sceneGraph; + + } + + // Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry ) + // and details the connection type + parseConnections() { + + const connectionMap = new Map(); + + if ( 'Connections' in fbxTree ) { + + const rawConnections = fbxTree.Connections.connections; + + rawConnections.forEach( function ( rawConnection ) { + + const fromID = rawConnection[ 0 ]; + const toID = rawConnection[ 1 ]; + const relationship = rawConnection[ 2 ]; + + if ( ! connectionMap.has( fromID ) ) { + + connectionMap.set( fromID, { + parents: [], + children: [] + } ); + + } + + const parentRelationship = { ID: toID, relationship: relationship }; + connectionMap.get( fromID ).parents.push( parentRelationship ); + + if ( ! connectionMap.has( toID ) ) { + + connectionMap.set( toID, { + parents: [], + children: [] + } ); + + } + + const childRelationship = { ID: fromID, relationship: relationship }; + connectionMap.get( toID ).children.push( childRelationship ); + + } ); + + } + + return connectionMap; + + } + + // Parse FBXTree.Objects.Video for embedded image data + // These images are connected to textures in FBXTree.Objects.Textures + // via FBXTree.Connections. + parseImages() { + + const images = {}; + const blobs = {}; + + if ( 'Video' in fbxTree.Objects ) { + + const videoNodes = fbxTree.Objects.Video; + + for ( const nodeID in videoNodes ) { + + const videoNode = videoNodes[ nodeID ]; + + const id = parseInt( nodeID ); + + images[ id ] = videoNode.RelativeFilename || videoNode.Filename; + + // raw image data is in videoNode.Content + if ( 'Content' in videoNode ) { + + const arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 ); + const base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' ); + + if ( arrayBufferContent || base64Content ) { + + const image = this.parseImage( videoNodes[ nodeID ] ); + + blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image; + + } + + } + + } + + } + + for ( const id in images ) { + + const filename = images[ id ]; + + if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ]; + else images[ id ] = images[ id ].split( '\\' ).pop(); + + } + + return images; + + } + + // Parse embedded image data in FBXTree.Video.Content + parseImage( videoNode ) { + + const content = videoNode.Content; + const fileName = videoNode.RelativeFilename || videoNode.Filename; + const extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase(); + + let type; + + switch ( extension ) { + + case 'bmp': + + type = 'image/bmp'; + break; + + case 'jpg': + case 'jpeg': + + type = 'image/jpeg'; + break; + + case 'png': + + type = 'image/png'; + break; + + case 'tif': + + type = 'image/tiff'; + break; + + case 'tga': + + if ( this.manager.getHandler( '.tga' ) === null ) { + + console.warn( 'FBXLoader: TGA loader not found, skipping ', fileName ); + + } + + type = 'image/tga'; + break; + + default: + + console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' ); + return; + + } + + if ( typeof content === 'string' ) { // ASCII format + + return 'data:' + type + ';base64,' + content; + + } else { // Binary Format + + const array = new Uint8Array( content ); + return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) ); + + } + + } + + // Parse nodes in FBXTree.Objects.Texture + // These contain details such as UV scaling, cropping, rotation etc and are connected + // to images in FBXTree.Objects.Video + parseTextures( images ) { + + const textureMap = new Map(); + + if ( 'Texture' in fbxTree.Objects ) { + + const textureNodes = fbxTree.Objects.Texture; + for ( const nodeID in textureNodes ) { + + const texture = this.parseTexture( textureNodes[ nodeID ], images ); + textureMap.set( parseInt( nodeID ), texture ); + + } + + } + + return textureMap; + + } + + // Parse individual node in FBXTree.Objects.Texture + parseTexture( textureNode, images ) { + + const texture = this.loadTexture( textureNode, images ); + + texture.ID = textureNode.id; + + texture.name = textureNode.attrName; + + const wrapModeU = textureNode.WrapModeU; + const wrapModeV = textureNode.WrapModeV; + + const valueU = wrapModeU !== undefined ? wrapModeU.value : 0; + const valueV = wrapModeV !== undefined ? wrapModeV.value : 0; + + // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a + // 0: repeat(default), 1: clamp + + texture.wrapS = valueU === 0 ? RepeatWrapping : ClampToEdgeWrapping; + texture.wrapT = valueV === 0 ? RepeatWrapping : ClampToEdgeWrapping; + + if ( 'Scaling' in textureNode ) { + + const values = textureNode.Scaling.value; + + texture.repeat.x = values[ 0 ]; + texture.repeat.y = values[ 1 ]; + + } + + if ( 'Translation' in textureNode ) { + + const values = textureNode.Translation.value; + + texture.offset.x = values[ 0 ]; + texture.offset.y = values[ 1 ]; + + } + + return texture; + + } + + // load a texture specified as a blob or data URI, or via an external URL using TextureLoader + loadTexture( textureNode, images ) { + + let fileName; + + const currentPath = this.textureLoader.path; + + const children = connections.get( textureNode.id ).children; + + if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) { + + fileName = images[ children[ 0 ].ID ]; + + if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) { + + this.textureLoader.setPath( undefined ); + + } + + } + + let texture; + + const extension = textureNode.FileName.slice( - 3 ).toLowerCase(); + + if ( extension === 'tga' ) { + + const loader = this.manager.getHandler( '.tga' ); + + if ( loader === null ) { + + console.warn( 'FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename ); + texture = new Texture(); + + } else { + + loader.setPath( this.textureLoader.path ); + texture = loader.load( fileName ); + + } + + } else if ( extension === 'psd' ) { + + console.warn( 'FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename ); + texture = new Texture(); + + } else { + + texture = this.textureLoader.load( fileName ); + + } + + this.textureLoader.setPath( currentPath ); + + return texture; + + } + + // Parse nodes in FBXTree.Objects.Material + parseMaterials( textureMap ) { + + const materialMap = new Map(); + + if ( 'Material' in fbxTree.Objects ) { + + const materialNodes = fbxTree.Objects.Material; + + for ( const nodeID in materialNodes ) { + + const material = this.parseMaterial( materialNodes[ nodeID ], textureMap ); + + if ( material !== null ) materialMap.set( parseInt( nodeID ), material ); + + } + + } + + return materialMap; + + } + + // Parse single node in FBXTree.Objects.Material + // Materials are connected to texture maps in FBXTree.Objects.Textures + // FBX format currently only supports Lambert and Phong shading models + parseMaterial( materialNode, textureMap ) { + + const ID = materialNode.id; + const name = materialNode.attrName; + let type = materialNode.ShadingModel; + + // Case where FBX wraps shading model in property object. + if ( typeof type === 'object' ) { + + type = type.value; + + } + + // Ignore unused materials which don't have any connections. + if ( ! connections.has( ID ) ) return null; + + const parameters = this.parseParameters( materialNode, textureMap, ID ); + + let material; + + switch ( type.toLowerCase() ) { + + case 'phong': + material = new MeshPhongMaterial(); + break; + case 'lambert': + material = new MeshLambertMaterial(); + break; + default: + console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type ); + material = new MeshPhongMaterial(); + break; + + } + + material.setValues( parameters ); + material.name = name; + + return material; + + } + + // Parse FBX material and return parameters suitable for a three.js material + // Also parse the texture map and return any textures associated with the material + parseParameters( materialNode, textureMap, ID ) { + + const parameters = {}; + + if ( materialNode.BumpFactor ) { + + parameters.bumpScale = materialNode.BumpFactor.value; + + } + + if ( materialNode.Diffuse ) { + + parameters.color = new Color().fromArray( materialNode.Diffuse.value ); + + } else if ( materialNode.DiffuseColor && ( materialNode.DiffuseColor.type === 'Color' || materialNode.DiffuseColor.type === 'ColorRGB' ) ) { + + // The blender exporter exports diffuse here instead of in materialNode.Diffuse + parameters.color = new Color().fromArray( materialNode.DiffuseColor.value ); + + } + + if ( materialNode.DisplacementFactor ) { + + parameters.displacementScale = materialNode.DisplacementFactor.value; + + } + + if ( materialNode.Emissive ) { + + parameters.emissive = new Color().fromArray( materialNode.Emissive.value ); + + } else if ( materialNode.EmissiveColor && ( materialNode.EmissiveColor.type === 'Color' || materialNode.EmissiveColor.type === 'ColorRGB' ) ) { + + // The blender exporter exports emissive color here instead of in materialNode.Emissive + parameters.emissive = new Color().fromArray( materialNode.EmissiveColor.value ); + + } + + if ( materialNode.EmissiveFactor ) { + + parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value ); + + } + + if ( materialNode.Opacity ) { + + parameters.opacity = parseFloat( materialNode.Opacity.value ); + + } + + if ( parameters.opacity < 1.0 ) { + + parameters.transparent = true; + + } + + if ( materialNode.ReflectionFactor ) { + + parameters.reflectivity = materialNode.ReflectionFactor.value; + + } + + if ( materialNode.Shininess ) { + + parameters.shininess = materialNode.Shininess.value; + + } + + if ( materialNode.Specular ) { + + parameters.specular = new Color().fromArray( materialNode.Specular.value ); + + } else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) { + + // The blender exporter exports specular color here instead of in materialNode.Specular + parameters.specular = new Color().fromArray( materialNode.SpecularColor.value ); + + } + + const scope = this; + connections.get( ID ).children.forEach( function ( child ) { + + const type = child.relationship; + + switch ( type ) { + + case 'Bump': + parameters.bumpMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'Maya|TEX_ao_map': + parameters.aoMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'DiffuseColor': + case 'Maya|TEX_color_map': + parameters.map = scope.getTexture( textureMap, child.ID ); + if ( parameters.map !== undefined ) { + + parameters.map.encoding = sRGBEncoding; + + } + + break; + + case 'DisplacementColor': + parameters.displacementMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'EmissiveColor': + parameters.emissiveMap = scope.getTexture( textureMap, child.ID ); + if ( parameters.emissiveMap !== undefined ) { + + parameters.emissiveMap.encoding = sRGBEncoding; + + } + + break; + + case 'NormalMap': + case 'Maya|TEX_normal_map': + parameters.normalMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'ReflectionColor': + parameters.envMap = scope.getTexture( textureMap, child.ID ); + if ( parameters.envMap !== undefined ) { + + parameters.envMap.mapping = EquirectangularReflectionMapping; + parameters.envMap.encoding = sRGBEncoding; + + } + + break; + + case 'SpecularColor': + parameters.specularMap = scope.getTexture( textureMap, child.ID ); + if ( parameters.specularMap !== undefined ) { + + parameters.specularMap.encoding = sRGBEncoding; + + } + + break; + + case 'TransparentColor': + case 'TransparencyFactor': + parameters.alphaMap = scope.getTexture( textureMap, child.ID ); + parameters.transparent = true; + break; + + case 'AmbientColor': + case 'ShininessExponent': // AKA glossiness map + case 'SpecularFactor': // AKA specularLevel + case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor + default: + console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type ); + break; + + } + + } ); + + return parameters; + + } + + // get a texture from the textureMap for use by a material. + getTexture( textureMap, id ) { + + // if the texture is a layered texture, just use the first layer and issue a warning + if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) { + + console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' ); + id = connections.get( id ).children[ 0 ].ID; + + } + + return textureMap.get( id ); + + } + + // Parse nodes in FBXTree.Objects.Deformer + // Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here + // Generates map of Skeleton-like objects for use later when generating and binding skeletons. + parseDeformers() { + + const skeletons = {}; + const morphTargets = {}; + + if ( 'Deformer' in fbxTree.Objects ) { + + const DeformerNodes = fbxTree.Objects.Deformer; + + for ( const nodeID in DeformerNodes ) { + + const deformerNode = DeformerNodes[ nodeID ]; + + const relationships = connections.get( parseInt( nodeID ) ); + + if ( deformerNode.attrType === 'Skin' ) { + + const skeleton = this.parseSkeleton( relationships, DeformerNodes ); + skeleton.ID = nodeID; + + if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' ); + skeleton.geometryID = relationships.parents[ 0 ].ID; + + skeletons[ nodeID ] = skeleton; + + } else if ( deformerNode.attrType === 'BlendShape' ) { + + const morphTarget = { + id: nodeID, + }; + + morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes ); + morphTarget.id = nodeID; + + if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' ); + + morphTargets[ nodeID ] = morphTarget; + + } + + } + + } + + return { + + skeletons: skeletons, + morphTargets: morphTargets, + + }; + + } + + // Parse single nodes in FBXTree.Objects.Deformer + // The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster' + // Each skin node represents a skeleton and each cluster node represents a bone + parseSkeleton( relationships, deformerNodes ) { + + const rawBones = []; + + relationships.children.forEach( function ( child ) { + + const boneNode = deformerNodes[ child.ID ]; + + if ( boneNode.attrType !== 'Cluster' ) return; + + const rawBone = { + + ID: child.ID, + indices: [], + weights: [], + transformLink: new Matrix4().fromArray( boneNode.TransformLink.a ), + // transform: new Matrix4().fromArray( boneNode.Transform.a ), + // linkMode: boneNode.Mode, + + }; + + if ( 'Indexes' in boneNode ) { + + rawBone.indices = boneNode.Indexes.a; + rawBone.weights = boneNode.Weights.a; + + } + + rawBones.push( rawBone ); + + } ); + + return { + + rawBones: rawBones, + bones: [] + + }; + + } + + // The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel" + parseMorphTargets( relationships, deformerNodes ) { + + const rawMorphTargets = []; + + for ( let i = 0; i < relationships.children.length; i ++ ) { + + const child = relationships.children[ i ]; + + const morphTargetNode = deformerNodes[ child.ID ]; + + const rawMorphTarget = { + + name: morphTargetNode.attrName, + initialWeight: morphTargetNode.DeformPercent, + id: morphTargetNode.id, + fullWeights: morphTargetNode.FullWeights.a + + }; + + if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return; + + rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) { + + return child.relationship === undefined; + + } )[ 0 ].ID; + + rawMorphTargets.push( rawMorphTarget ); + + } + + return rawMorphTargets; + + } + + // create the main Group() to be returned by the loader + parseScene( deformers, geometryMap, materialMap ) { + + sceneGraph = new Group(); + + const modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap ); + + const modelNodes = fbxTree.Objects.Model; + + const scope = this; + modelMap.forEach( function ( model ) { + + const modelNode = modelNodes[ model.ID ]; + scope.setLookAtProperties( model, modelNode ); + + const parentConnections = connections.get( model.ID ).parents; + + parentConnections.forEach( function ( connection ) { + + const parent = modelMap.get( connection.ID ); + if ( parent !== undefined ) parent.add( model ); + + } ); + + if ( model.parent === null ) { + + sceneGraph.add( model ); + + } + + + } ); + + this.bindSkeleton( deformers.skeletons, geometryMap, modelMap ); + + this.createAmbientLight(); + + sceneGraph.traverse( function ( node ) { + + if ( node.userData.transformData ) { + + if ( node.parent ) { + + node.userData.transformData.parentMatrix = node.parent.matrix; + node.userData.transformData.parentMatrixWorld = node.parent.matrixWorld; + + } + + const transform = generateTransform( node.userData.transformData ); + + node.applyMatrix4( transform ); + node.updateWorldMatrix(); + + } + + } ); + + const animations = new AnimationParser().parse(); + + // if all the models where already combined in a single group, just return that + if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) { + + sceneGraph.children[ 0 ].animations = animations; + sceneGraph = sceneGraph.children[ 0 ]; + + } + + sceneGraph.animations = animations; + + } + + // parse nodes in FBXTree.Objects.Model + parseModels( skeletons, geometryMap, materialMap ) { + + const modelMap = new Map(); + const modelNodes = fbxTree.Objects.Model; + + for ( const nodeID in modelNodes ) { + + const id = parseInt( nodeID ); + const node = modelNodes[ nodeID ]; + const relationships = connections.get( id ); + + let model = this.buildSkeleton( relationships, skeletons, id, node.attrName ); + + if ( ! model ) { + + switch ( node.attrType ) { + + case 'Camera': + model = this.createCamera( relationships ); + break; + case 'Light': + model = this.createLight( relationships ); + break; + case 'Mesh': + model = this.createMesh( relationships, geometryMap, materialMap ); + break; + case 'NurbsCurve': + model = this.createCurve( relationships, geometryMap ); + break; + case 'LimbNode': + case 'Root': + model = new Bone(); + break; + case 'Null': + default: + model = new Group(); + break; + + } + + model.name = node.attrName ? PropertyBinding.sanitizeNodeName( node.attrName ) : ''; + + model.ID = id; + + } + + this.getTransformData( model, node ); + modelMap.set( id, model ); + + } + + return modelMap; + + } + + buildSkeleton( relationships, skeletons, id, name ) { + + let bone = null; + + relationships.parents.forEach( function ( parent ) { + + for ( const ID in skeletons ) { + + const skeleton = skeletons[ ID ]; + + skeleton.rawBones.forEach( function ( rawBone, i ) { + + if ( rawBone.ID === parent.ID ) { + + const subBone = bone; + bone = new Bone(); + + bone.matrixWorld.copy( rawBone.transformLink ); + + // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id + + bone.name = name ? PropertyBinding.sanitizeNodeName( name ) : ''; + bone.ID = id; + + skeleton.bones[ i ] = bone; + + // In cases where a bone is shared between multiple meshes + // duplicate the bone here and and it as a child of the first bone + if ( subBone !== null ) { + + bone.add( subBone ); + + } + + } + + } ); + + } + + } ); + + return bone; + + } + + // create a PerspectiveCamera or OrthographicCamera + createCamera( relationships ) { + + let model; + let cameraAttribute; + + relationships.children.forEach( function ( child ) { + + const attr = fbxTree.Objects.NodeAttribute[ child.ID ]; + + if ( attr !== undefined ) { + + cameraAttribute = attr; + + } + + } ); + + if ( cameraAttribute === undefined ) { + + model = new Object3D(); + + } else { + + let type = 0; + if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) { + + type = 1; + + } + + let nearClippingPlane = 1; + if ( cameraAttribute.NearPlane !== undefined ) { + + nearClippingPlane = cameraAttribute.NearPlane.value / 1000; + + } + + let farClippingPlane = 1000; + if ( cameraAttribute.FarPlane !== undefined ) { + + farClippingPlane = cameraAttribute.FarPlane.value / 1000; + + } + + + let width = window.innerWidth; + let height = window.innerHeight; + + if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) { + + width = cameraAttribute.AspectWidth.value; + height = cameraAttribute.AspectHeight.value; + + } + + const aspect = width / height; + + let fov = 45; + if ( cameraAttribute.FieldOfView !== undefined ) { + + fov = cameraAttribute.FieldOfView.value; + + } + + const focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null; + + switch ( type ) { + + case 0: // Perspective + model = new PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane ); + if ( focalLength !== null ) model.setFocalLength( focalLength ); + break; + + case 1: // Orthographic + model = new OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane ); + break; + + default: + console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' ); + model = new Object3D(); + break; + + } + + } + + return model; + + } + + // Create a DirectionalLight, PointLight or SpotLight + createLight( relationships ) { + + let model; + let lightAttribute; + + relationships.children.forEach( function ( child ) { + + const attr = fbxTree.Objects.NodeAttribute[ child.ID ]; + + if ( attr !== undefined ) { + + lightAttribute = attr; + + } + + } ); + + if ( lightAttribute === undefined ) { + + model = new Object3D(); + + } else { + + let type; + + // LightType can be undefined for Point lights + if ( lightAttribute.LightType === undefined ) { + + type = 0; + + } else { + + type = lightAttribute.LightType.value; + + } + + let color = 0xffffff; + + if ( lightAttribute.Color !== undefined ) { + + color = new Color().fromArray( lightAttribute.Color.value ); + + } + + let intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100; + + // light disabled + if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) { + + intensity = 0; + + } + + let distance = 0; + if ( lightAttribute.FarAttenuationEnd !== undefined ) { + + if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) { + + distance = 0; + + } else { + + distance = lightAttribute.FarAttenuationEnd.value; + + } + + } + + // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd? + const decay = 1; + + switch ( type ) { + + case 0: // Point + model = new PointLight( color, intensity, distance, decay ); + break; + + case 1: // Directional + model = new DirectionalLight( color, intensity ); + break; + + case 2: // Spot + let angle = Math.PI / 3; + + if ( lightAttribute.InnerAngle !== undefined ) { + + angle = MathUtils.degToRad( lightAttribute.InnerAngle.value ); + + } + + let penumbra = 0; + if ( lightAttribute.OuterAngle !== undefined ) { + + // TODO: this is not correct - FBX calculates outer and inner angle in degrees + // with OuterAngle > InnerAngle && OuterAngle <= Math.PI + // while three.js uses a penumbra between (0, 1) to attenuate the inner angle + penumbra = MathUtils.degToRad( lightAttribute.OuterAngle.value ); + penumbra = Math.max( penumbra, 1 ); + + } + + model = new SpotLight( color, intensity, distance, angle, penumbra, decay ); + break; + + default: + console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a PointLight.' ); + model = new PointLight( color, intensity ); + break; + + } + + if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) { + + model.castShadow = true; + + } + + } + + return model; + + } + + createMesh( relationships, geometryMap, materialMap ) { + + let model; + let geometry = null; + let material = null; + const materials = []; + + // get geometry and materials(s) from connections + relationships.children.forEach( function ( child ) { + + if ( geometryMap.has( child.ID ) ) { + + geometry = geometryMap.get( child.ID ); + + } + + if ( materialMap.has( child.ID ) ) { + + materials.push( materialMap.get( child.ID ) ); + + } + + } ); + + if ( materials.length > 1 ) { + + material = materials; + + } else if ( materials.length > 0 ) { + + material = materials[ 0 ]; + + } else { + + material = new MeshPhongMaterial( { color: 0xcccccc } ); + materials.push( material ); + + } + + if ( 'color' in geometry.attributes ) { + + materials.forEach( function ( material ) { + + material.vertexColors = true; + + } ); + + } + + if ( geometry.FBX_Deformer ) { + + model = new SkinnedMesh( geometry, material ); + model.normalizeSkinWeights(); + + } else { + + model = new Mesh( geometry, material ); + + } + + return model; + + } + + createCurve( relationships, geometryMap ) { + + const geometry = relationships.children.reduce( function ( geo, child ) { + + if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID ); + + return geo; + + }, null ); + + // FBX does not list materials for Nurbs lines, so we'll just put our own in here. + const material = new LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } ); + return new Line( geometry, material ); + + } + + // parse the model node for transform data + getTransformData( model, modelNode ) { + + const transformData = {}; + + if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value ); + + if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); + else transformData.eulerOrder = 'ZYX'; + + if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value; + + if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value; + if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value; + if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value; + + if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value; + + if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value; + if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value; + + if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value; + if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value; + + model.userData.transformData = transformData; + + } + + setLookAtProperties( model, modelNode ) { + + if ( 'LookAtProperty' in modelNode ) { + + const children = connections.get( model.ID ).children; + + children.forEach( function ( child ) { + + if ( child.relationship === 'LookAtProperty' ) { + + const lookAtTarget = fbxTree.Objects.Model[ child.ID ]; + + if ( 'Lcl_Translation' in lookAtTarget ) { + + const pos = lookAtTarget.Lcl_Translation.value; + + // DirectionalLight, SpotLight + if ( model.target !== undefined ) { + + model.target.position.fromArray( pos ); + sceneGraph.add( model.target ); + + } else { // Cameras and other Object3Ds + + model.lookAt( new Vector3().fromArray( pos ) ); + + } + + } + + } + + } ); + + } + + } + + bindSkeleton( skeletons, geometryMap, modelMap ) { + + const bindMatrices = this.parsePoseNodes(); + + for ( const ID in skeletons ) { + + const skeleton = skeletons[ ID ]; + + const parents = connections.get( parseInt( skeleton.ID ) ).parents; + + parents.forEach( function ( parent ) { + + if ( geometryMap.has( parent.ID ) ) { + + const geoID = parent.ID; + const geoRelationships = connections.get( geoID ); + + geoRelationships.parents.forEach( function ( geoConnParent ) { + + if ( modelMap.has( geoConnParent.ID ) ) { + + const model = modelMap.get( geoConnParent.ID ); + + model.bind( new Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] ); + + } + + } ); + + } + + } ); + + } + + } + + parsePoseNodes() { + + const bindMatrices = {}; + + if ( 'Pose' in fbxTree.Objects ) { + + const BindPoseNode = fbxTree.Objects.Pose; + + for ( const nodeID in BindPoseNode ) { + + if ( BindPoseNode[ nodeID ].attrType === 'BindPose' && BindPoseNode[ nodeID ].NbPoseNodes > 0 ) { + + const poseNodes = BindPoseNode[ nodeID ].PoseNode; + + if ( Array.isArray( poseNodes ) ) { + + poseNodes.forEach( function ( poseNode ) { + + bindMatrices[ poseNode.Node ] = new Matrix4().fromArray( poseNode.Matrix.a ); + + } ); + + } else { + + bindMatrices[ poseNodes.Node ] = new Matrix4().fromArray( poseNodes.Matrix.a ); + + } + + } + + } + + } + + return bindMatrices; + + } + + // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light + createAmbientLight() { + + if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) { + + const ambientColor = fbxTree.GlobalSettings.AmbientColor.value; + const r = ambientColor[ 0 ]; + const g = ambientColor[ 1 ]; + const b = ambientColor[ 2 ]; + + if ( r !== 0 || g !== 0 || b !== 0 ) { + + const color = new Color( r, g, b ); + sceneGraph.add( new AmbientLight( color, 1 ) ); + + } + + } + + } + +} + +// parse Geometry data from FBXTree and return map of BufferGeometries +class GeometryParser { + + // Parse nodes in FBXTree.Objects.Geometry + parse( deformers ) { + + const geometryMap = new Map(); + + if ( 'Geometry' in fbxTree.Objects ) { + + const geoNodes = fbxTree.Objects.Geometry; + + for ( const nodeID in geoNodes ) { + + const relationships = connections.get( parseInt( nodeID ) ); + const geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers ); + + geometryMap.set( parseInt( nodeID ), geo ); + + } + + } + + return geometryMap; + + } + + // Parse single node in FBXTree.Objects.Geometry + parseGeometry( relationships, geoNode, deformers ) { + + switch ( geoNode.attrType ) { + + case 'Mesh': + return this.parseMeshGeometry( relationships, geoNode, deformers ); + break; + + case 'NurbsCurve': + return this.parseNurbsGeometry( geoNode ); + break; + + } + + } + + // Parse single node mesh geometry in FBXTree.Objects.Geometry + parseMeshGeometry( relationships, geoNode, deformers ) { + + const skeletons = deformers.skeletons; + const morphTargets = []; + + const modelNodes = relationships.parents.map( function ( parent ) { + + return fbxTree.Objects.Model[ parent.ID ]; + + } ); + + // don't create geometry if it is not associated with any models + if ( modelNodes.length === 0 ) return; + + const skeleton = relationships.children.reduce( function ( skeleton, child ) { + + if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ]; + + return skeleton; + + }, null ); + + relationships.children.forEach( function ( child ) { + + if ( deformers.morphTargets[ child.ID ] !== undefined ) { + + morphTargets.push( deformers.morphTargets[ child.ID ] ); + + } + + } ); + + // Assume one model and get the preRotation from that + // if there is more than one model associated with the geometry this may cause problems + const modelNode = modelNodes[ 0 ]; + + const transformData = {}; + + if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); + if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value ); + + if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value; + if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value; + if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value; + + const transform = generateTransform( transformData ); + + return this.genGeometry( geoNode, skeleton, morphTargets, transform ); + + } + + // Generate a BufferGeometry from a node in FBXTree.Objects.Geometry + genGeometry( geoNode, skeleton, morphTargets, preTransform ) { + + const geo = new BufferGeometry(); + if ( geoNode.attrName ) geo.name = geoNode.attrName; + + const geoInfo = this.parseGeoNode( geoNode, skeleton ); + const buffers = this.genBuffers( geoInfo ); + + const positionAttribute = new Float32BufferAttribute( buffers.vertex, 3 ); + + positionAttribute.applyMatrix4( preTransform ); + + geo.setAttribute( 'position', positionAttribute ); + + if ( buffers.colors.length > 0 ) { + + geo.setAttribute( 'color', new Float32BufferAttribute( buffers.colors, 3 ) ); + + } + + if ( skeleton ) { + + geo.setAttribute( 'skinIndex', new Uint16BufferAttribute( buffers.weightsIndices, 4 ) ); + + geo.setAttribute( 'skinWeight', new Float32BufferAttribute( buffers.vertexWeights, 4 ) ); + + // used later to bind the skeleton to the model + geo.FBX_Deformer = skeleton; + + } + + if ( buffers.normal.length > 0 ) { + + const normalMatrix = new Matrix3().getNormalMatrix( preTransform ); + + const normalAttribute = new Float32BufferAttribute( buffers.normal, 3 ); + normalAttribute.applyNormalMatrix( normalMatrix ); + + geo.setAttribute( 'normal', normalAttribute ); + + } + + buffers.uvs.forEach( function ( uvBuffer, i ) { + + // subsequent uv buffers are called 'uv1', 'uv2', ... + let name = 'uv' + ( i + 1 ).toString(); + + // the first uv buffer is just called 'uv' + if ( i === 0 ) { + + name = 'uv'; + + } + + geo.setAttribute( name, new Float32BufferAttribute( buffers.uvs[ i ], 2 ) ); + + } ); + + if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { + + // Convert the material indices of each vertex into rendering groups on the geometry. + let prevMaterialIndex = buffers.materialIndex[ 0 ]; + let startIndex = 0; + + buffers.materialIndex.forEach( function ( currentIndex, i ) { + + if ( currentIndex !== prevMaterialIndex ) { + + geo.addGroup( startIndex, i - startIndex, prevMaterialIndex ); + + prevMaterialIndex = currentIndex; + startIndex = i; + + } + + } ); + + // the loop above doesn't add the last group, do that here. + if ( geo.groups.length > 0 ) { + + const lastGroup = geo.groups[ geo.groups.length - 1 ]; + const lastIndex = lastGroup.start + lastGroup.count; + + if ( lastIndex !== buffers.materialIndex.length ) { + + geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex ); + + } + + } + + // case where there are multiple materials but the whole geometry is only + // using one of them + if ( geo.groups.length === 0 ) { + + geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] ); + + } + + } + + this.addMorphTargets( geo, geoNode, morphTargets, preTransform ); + + return geo; + + } + + parseGeoNode( geoNode, skeleton ) { + + const geoInfo = {}; + + geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : []; + geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : []; + + if ( geoNode.LayerElementColor ) { + + geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] ); + + } + + if ( geoNode.LayerElementMaterial ) { + + geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] ); + + } + + if ( geoNode.LayerElementNormal ) { + + geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] ); + + } + + if ( geoNode.LayerElementUV ) { + + geoInfo.uv = []; + + let i = 0; + while ( geoNode.LayerElementUV[ i ] ) { + + if ( geoNode.LayerElementUV[ i ].UV ) { + + geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) ); + + } + + i ++; + + } + + } + + geoInfo.weightTable = {}; + + if ( skeleton !== null ) { + + geoInfo.skeleton = skeleton; + + skeleton.rawBones.forEach( function ( rawBone, i ) { + + // loop over the bone's vertex indices and weights + rawBone.indices.forEach( function ( index, j ) { + + if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = []; + + geoInfo.weightTable[ index ].push( { + + id: i, + weight: rawBone.weights[ j ], + + } ); + + } ); + + } ); + + } + + return geoInfo; + + } + + genBuffers( geoInfo ) { + + const buffers = { + vertex: [], + normal: [], + colors: [], + uvs: [], + materialIndex: [], + vertexWeights: [], + weightsIndices: [], + }; + + let polygonIndex = 0; + let faceLength = 0; + let displayedWeightsWarning = false; + + // these will hold data for a single face + let facePositionIndexes = []; + let faceNormals = []; + let faceColors = []; + let faceUVs = []; + let faceWeights = []; + let faceWeightIndices = []; + + const scope = this; + geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) { + + let materialIndex; + let endOfFace = false; + + // Face index and vertex index arrays are combined in a single array + // A cube with quad faces looks like this: + // PolygonVertexIndex: *24 { + // a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5 + // } + // Negative numbers mark the end of a face - first face here is 0, 1, 3, -3 + // to find index of last vertex bit shift the index: ^ - 1 + if ( vertexIndex < 0 ) { + + vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1 + endOfFace = true; + + } + + let weightIndices = []; + let weights = []; + + facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 ); + + if ( geoInfo.color ) { + + const data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color ); + + faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] ); + + } + + if ( geoInfo.skeleton ) { + + if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) { + + geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) { + + weights.push( wt.weight ); + weightIndices.push( wt.id ); + + } ); + + + } + + if ( weights.length > 4 ) { + + if ( ! displayedWeightsWarning ) { + + console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' ); + displayedWeightsWarning = true; + + } + + const wIndex = [ 0, 0, 0, 0 ]; + const Weight = [ 0, 0, 0, 0 ]; + + weights.forEach( function ( weight, weightIndex ) { + + let currentWeight = weight; + let currentIndex = weightIndices[ weightIndex ]; + + Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) { + + if ( currentWeight > comparedWeight ) { + + comparedWeightArray[ comparedWeightIndex ] = currentWeight; + currentWeight = comparedWeight; + + const tmp = wIndex[ comparedWeightIndex ]; + wIndex[ comparedWeightIndex ] = currentIndex; + currentIndex = tmp; + + } + + } ); + + } ); + + weightIndices = wIndex; + weights = Weight; + + } + + // if the weight array is shorter than 4 pad with 0s + while ( weights.length < 4 ) { + + weights.push( 0 ); + weightIndices.push( 0 ); + + } + + for ( let i = 0; i < 4; ++ i ) { + + faceWeights.push( weights[ i ] ); + faceWeightIndices.push( weightIndices[ i ] ); + + } + + } + + if ( geoInfo.normal ) { + + const data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal ); + + faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] ); + + } + + if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { + + materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ]; + + } + + if ( geoInfo.uv ) { + + geoInfo.uv.forEach( function ( uv, i ) { + + const data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv ); + + if ( faceUVs[ i ] === undefined ) { + + faceUVs[ i ] = []; + + } + + faceUVs[ i ].push( data[ 0 ] ); + faceUVs[ i ].push( data[ 1 ] ); + + } ); + + } + + faceLength ++; + + if ( endOfFace ) { + + scope.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ); + + polygonIndex ++; + faceLength = 0; + + // reset arrays for the next face + facePositionIndexes = []; + faceNormals = []; + faceColors = []; + faceUVs = []; + faceWeights = []; + faceWeightIndices = []; + + } + + } ); + + return buffers; + + } + + // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris + genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) { + + for ( let i = 2; i < faceLength; i ++ ) { + + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] ); + + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] ); + + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] ); + + if ( geoInfo.skeleton ) { + + buffers.vertexWeights.push( faceWeights[ 0 ] ); + buffers.vertexWeights.push( faceWeights[ 1 ] ); + buffers.vertexWeights.push( faceWeights[ 2 ] ); + buffers.vertexWeights.push( faceWeights[ 3 ] ); + + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] ); + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] ); + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] ); + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] ); + + buffers.vertexWeights.push( faceWeights[ i * 4 ] ); + buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] ); + buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] ); + buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] ); + + buffers.weightsIndices.push( faceWeightIndices[ 0 ] ); + buffers.weightsIndices.push( faceWeightIndices[ 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ 3 ] ); + + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] ); + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] ); + + buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] ); + + } + + if ( geoInfo.color ) { + + buffers.colors.push( faceColors[ 0 ] ); + buffers.colors.push( faceColors[ 1 ] ); + buffers.colors.push( faceColors[ 2 ] ); + + buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] ); + buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] ); + buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] ); + + buffers.colors.push( faceColors[ i * 3 ] ); + buffers.colors.push( faceColors[ i * 3 + 1 ] ); + buffers.colors.push( faceColors[ i * 3 + 2 ] ); + + } + + if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { + + buffers.materialIndex.push( materialIndex ); + buffers.materialIndex.push( materialIndex ); + buffers.materialIndex.push( materialIndex ); + + } + + if ( geoInfo.normal ) { + + buffers.normal.push( faceNormals[ 0 ] ); + buffers.normal.push( faceNormals[ 1 ] ); + buffers.normal.push( faceNormals[ 2 ] ); + + buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] ); + buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] ); + buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] ); + + buffers.normal.push( faceNormals[ i * 3 ] ); + buffers.normal.push( faceNormals[ i * 3 + 1 ] ); + buffers.normal.push( faceNormals[ i * 3 + 2 ] ); + + } + + if ( geoInfo.uv ) { + + geoInfo.uv.forEach( function ( uv, j ) { + + if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = []; + + buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] ); + + buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] ); + + buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] ); + + } ); + + } + + } + + } + + addMorphTargets( parentGeo, parentGeoNode, morphTargets, preTransform ) { + + if ( morphTargets.length === 0 ) return; + + parentGeo.morphTargetsRelative = true; + + parentGeo.morphAttributes.position = []; + // parentGeo.morphAttributes.normal = []; // not implemented + + const scope = this; + morphTargets.forEach( function ( morphTarget ) { + + morphTarget.rawTargets.forEach( function ( rawTarget ) { + + const morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ]; + + if ( morphGeoNode !== undefined ) { + + scope.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name ); + + } + + } ); + + } ); + + } + + // a morph geometry node is similar to a standard node, and the node is also contained + // in FBXTree.Objects.Geometry, however it can only have attributes for position, normal + // and a special attribute Index defining which vertices of the original geometry are affected + // Normal and position attributes only have data for the vertices that are affected by the morph + genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) { + + const vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : []; + + const morphPositionsSparse = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : []; + const indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : []; + + const length = parentGeo.attributes.position.count * 3; + const morphPositions = new Float32Array( length ); + + for ( let i = 0; i < indices.length; i ++ ) { + + const morphIndex = indices[ i ] * 3; + + morphPositions[ morphIndex ] = morphPositionsSparse[ i * 3 ]; + morphPositions[ morphIndex + 1 ] = morphPositionsSparse[ i * 3 + 1 ]; + morphPositions[ morphIndex + 2 ] = morphPositionsSparse[ i * 3 + 2 ]; + + } + + // TODO: add morph normal support + const morphGeoInfo = { + vertexIndices: vertexIndices, + vertexPositions: morphPositions, + + }; + + const morphBuffers = this.genBuffers( morphGeoInfo ); + + const positionAttribute = new Float32BufferAttribute( morphBuffers.vertex, 3 ); + positionAttribute.name = name || morphGeoNode.attrName; + + positionAttribute.applyMatrix4( preTransform ); + + parentGeo.morphAttributes.position.push( positionAttribute ); + + } + + // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists + parseNormals( NormalNode ) { + + const mappingType = NormalNode.MappingInformationType; + const referenceType = NormalNode.ReferenceInformationType; + const buffer = NormalNode.Normals.a; + let indexBuffer = []; + if ( referenceType === 'IndexToDirect' ) { + + if ( 'NormalIndex' in NormalNode ) { + + indexBuffer = NormalNode.NormalIndex.a; + + } else if ( 'NormalsIndex' in NormalNode ) { + + indexBuffer = NormalNode.NormalsIndex.a; + + } + + } + + return { + dataSize: 3, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists + parseUVs( UVNode ) { + + const mappingType = UVNode.MappingInformationType; + const referenceType = UVNode.ReferenceInformationType; + const buffer = UVNode.UV.a; + let indexBuffer = []; + if ( referenceType === 'IndexToDirect' ) { + + indexBuffer = UVNode.UVIndex.a; + + } + + return { + dataSize: 2, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists + parseVertexColors( ColorNode ) { + + const mappingType = ColorNode.MappingInformationType; + const referenceType = ColorNode.ReferenceInformationType; + const buffer = ColorNode.Colors.a; + let indexBuffer = []; + if ( referenceType === 'IndexToDirect' ) { + + indexBuffer = ColorNode.ColorIndex.a; + + } + + return { + dataSize: 4, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists + parseMaterialIndices( MaterialNode ) { + + const mappingType = MaterialNode.MappingInformationType; + const referenceType = MaterialNode.ReferenceInformationType; + + if ( mappingType === 'NoMappingInformation' ) { + + return { + dataSize: 1, + buffer: [ 0 ], + indices: [ 0 ], + mappingType: 'AllSame', + referenceType: referenceType + }; + + } + + const materialIndexBuffer = MaterialNode.Materials.a; + + // Since materials are stored as indices, there's a bit of a mismatch between FBX and what + // we expect.So we create an intermediate buffer that points to the index in the buffer, + // for conforming with the other functions we've written for other data. + const materialIndices = []; + + for ( let i = 0; i < materialIndexBuffer.length; ++ i ) { + + materialIndices.push( i ); + + } + + return { + dataSize: 1, + buffer: materialIndexBuffer, + indices: materialIndices, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Generate a NurbGeometry from a node in FBXTree.Objects.Geometry + parseNurbsGeometry( geoNode ) { + + if ( NURBSCurve === undefined ) { + + console.error( 'THREE.FBXLoader: The loader relies on NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' ); + return new BufferGeometry(); + + } + + const order = parseInt( geoNode.Order ); + + if ( isNaN( order ) ) { + + console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id ); + return new BufferGeometry(); + + } + + const degree = order - 1; + + const knots = geoNode.KnotVector.a; + const controlPoints = []; + const pointsValues = geoNode.Points.a; + + for ( let i = 0, l = pointsValues.length; i < l; i += 4 ) { + + controlPoints.push( new Vector4().fromArray( pointsValues, i ) ); + + } + + let startKnot, endKnot; + + if ( geoNode.Form === 'Closed' ) { + + controlPoints.push( controlPoints[ 0 ] ); + + } else if ( geoNode.Form === 'Periodic' ) { + + startKnot = degree; + endKnot = knots.length - 1 - startKnot; + + for ( let i = 0; i < degree; ++ i ) { + + controlPoints.push( controlPoints[ i ] ); + + } + + } + + const curve = new NURBSCurve( degree, knots, controlPoints, startKnot, endKnot ); + const points = curve.getPoints( controlPoints.length * 12 ); + + return new BufferGeometry().setFromPoints( points ); + + } + +} + +// parse animation data from FBXTree +class AnimationParser { + + // take raw animation clips and turn them into three.js animation clips + parse() { + + const animationClips = []; + + const rawClips = this.parseClips(); + + if ( rawClips !== undefined ) { + + for ( const key in rawClips ) { + + const rawClip = rawClips[ key ]; + + const clip = this.addClip( rawClip ); + + animationClips.push( clip ); + + } + + } + + return animationClips; + + } + + parseClips() { + + // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve, + // if this is undefined we can safely assume there are no animations + if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined; + + const curveNodesMap = this.parseAnimationCurveNodes(); + + this.parseAnimationCurves( curveNodesMap ); + + const layersMap = this.parseAnimationLayers( curveNodesMap ); + const rawClips = this.parseAnimStacks( layersMap ); + + return rawClips; + + } + + // parse nodes in FBXTree.Objects.AnimationCurveNode + // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation ) + // and is referenced by an AnimationLayer + parseAnimationCurveNodes() { + + const rawCurveNodes = fbxTree.Objects.AnimationCurveNode; + + const curveNodesMap = new Map(); + + for ( const nodeID in rawCurveNodes ) { + + const rawCurveNode = rawCurveNodes[ nodeID ]; + + if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) { + + const curveNode = { + + id: rawCurveNode.id, + attr: rawCurveNode.attrName, + curves: {}, + + }; + + curveNodesMap.set( curveNode.id, curveNode ); + + } + + } + + return curveNodesMap; + + } + + // parse nodes in FBXTree.Objects.AnimationCurve and connect them up to + // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated + // axis ( e.g. times and values of x rotation) + parseAnimationCurves( curveNodesMap ) { + + const rawCurves = fbxTree.Objects.AnimationCurve; + + // TODO: Many values are identical up to roundoff error, but won't be optimised + // e.g. position times: [0, 0.4, 0. 8] + // position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809] + // clearly, this should be optimised to + // times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809] + // this shows up in nearly every FBX file, and generally time array is length > 100 + + for ( const nodeID in rawCurves ) { + + const animationCurve = { + + id: rawCurves[ nodeID ].id, + times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ), + values: rawCurves[ nodeID ].KeyValueFloat.a, + + }; + + const relationships = connections.get( animationCurve.id ); + + if ( relationships !== undefined ) { + + const animationCurveID = relationships.parents[ 0 ].ID; + const animationCurveRelationship = relationships.parents[ 0 ].relationship; + + if ( animationCurveRelationship.match( /X/ ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve; + + } else if ( animationCurveRelationship.match( /Y/ ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve; + + } else if ( animationCurveRelationship.match( /Z/ ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve; + + } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve; + + } + + } + + } + + } + + // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references + // to various AnimationCurveNodes and is referenced by an AnimationStack node + // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack + parseAnimationLayers( curveNodesMap ) { + + const rawLayers = fbxTree.Objects.AnimationLayer; + + const layersMap = new Map(); + + for ( const nodeID in rawLayers ) { + + const layerCurveNodes = []; + + const connection = connections.get( parseInt( nodeID ) ); + + if ( connection !== undefined ) { + + // all the animationCurveNodes used in the layer + const children = connection.children; + + children.forEach( function ( child, i ) { + + if ( curveNodesMap.has( child.ID ) ) { + + const curveNode = curveNodesMap.get( child.ID ); + + // check that the curves are defined for at least one axis, otherwise ignore the curveNode + if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) { + + if ( layerCurveNodes[ i ] === undefined ) { + + const modelID = connections.get( child.ID ).parents.filter( function ( parent ) { + + return parent.relationship !== undefined; + + } )[ 0 ].ID; + + if ( modelID !== undefined ) { + + const rawModel = fbxTree.Objects.Model[ modelID.toString() ]; + + if ( rawModel === undefined ) { + + console.warn( 'THREE.FBXLoader: Encountered a unused curve.', child ); + return; + + } + + const node = { + + modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '', + ID: rawModel.id, + initialPosition: [ 0, 0, 0 ], + initialRotation: [ 0, 0, 0 ], + initialScale: [ 1, 1, 1 ], + + }; + + sceneGraph.traverse( function ( child ) { + + if ( child.ID === rawModel.id ) { + + node.transform = child.matrix; + + if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder; + + } + + } ); + + if ( ! node.transform ) node.transform = new Matrix4(); + + // if the animated model is pre rotated, we'll have to apply the pre rotations to every + // animation value as well + if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value; + if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value; + + layerCurveNodes[ i ] = node; + + } + + } + + if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode; + + } else if ( curveNode.curves.morph !== undefined ) { + + if ( layerCurveNodes[ i ] === undefined ) { + + const deformerID = connections.get( child.ID ).parents.filter( function ( parent ) { + + return parent.relationship !== undefined; + + } )[ 0 ].ID; + + const morpherID = connections.get( deformerID ).parents[ 0 ].ID; + const geoID = connections.get( morpherID ).parents[ 0 ].ID; + + // assuming geometry is not used in more than one model + const modelID = connections.get( geoID ).parents[ 0 ].ID; + + const rawModel = fbxTree.Objects.Model[ modelID ]; + + const node = { + + modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '', + morphName: fbxTree.Objects.Deformer[ deformerID ].attrName, + + }; + + layerCurveNodes[ i ] = node; + + } + + layerCurveNodes[ i ][ curveNode.attr ] = curveNode; + + } + + } + + } ); + + layersMap.set( parseInt( nodeID ), layerCurveNodes ); + + } + + } + + return layersMap; + + } + + // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation + // hierarchy. Each Stack node will be used to create a AnimationClip + parseAnimStacks( layersMap ) { + + const rawStacks = fbxTree.Objects.AnimationStack; + + // connect the stacks (clips) up to the layers + const rawClips = {}; + + for ( const nodeID in rawStacks ) { + + const children = connections.get( parseInt( nodeID ) ).children; + + if ( children.length > 1 ) { + + // it seems like stacks will always be associated with a single layer. But just in case there are files + // where there are multiple layers per stack, we'll display a warning + console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' ); + + } + + const layer = layersMap.get( children[ 0 ].ID ); + + rawClips[ nodeID ] = { + + name: rawStacks[ nodeID ].attrName, + layer: layer, + + }; + + } + + return rawClips; + + } + + addClip( rawClip ) { + + let tracks = []; + + const scope = this; + rawClip.layer.forEach( function ( rawTracks ) { + + tracks = tracks.concat( scope.generateTracks( rawTracks ) ); + + } ); + + return new AnimationClip( rawClip.name, - 1, tracks ); + + } + + generateTracks( rawTracks ) { + + const tracks = []; + + let initialPosition = new Vector3(); + let initialRotation = new Quaternion(); + let initialScale = new Vector3(); + + if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale ); + + initialPosition = initialPosition.toArray(); + initialRotation = new Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray(); + initialScale = initialScale.toArray(); + + if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) { + + const positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' ); + if ( positionTrack !== undefined ) tracks.push( positionTrack ); + + } + + if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) { + + const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder ); + if ( rotationTrack !== undefined ) tracks.push( rotationTrack ); + + } + + if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) { + + const scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' ); + if ( scaleTrack !== undefined ) tracks.push( scaleTrack ); + + } + + if ( rawTracks.DeformPercent !== undefined ) { + + const morphTrack = this.generateMorphTrack( rawTracks ); + if ( morphTrack !== undefined ) tracks.push( morphTrack ); + + } + + return tracks; + + } + + generateVectorTrack( modelName, curves, initialValue, type ) { + + const times = this.getTimesForAllAxes( curves ); + const values = this.getKeyframeTrackValues( times, curves, initialValue ); + + return new VectorKeyframeTrack( modelName + '.' + type, times, values ); + + } + + generateRotationTrack( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) { + + if ( curves.x !== undefined ) { + + this.interpolateRotations( curves.x ); + curves.x.values = curves.x.values.map( MathUtils.degToRad ); + + } + + if ( curves.y !== undefined ) { + + this.interpolateRotations( curves.y ); + curves.y.values = curves.y.values.map( MathUtils.degToRad ); + + } + + if ( curves.z !== undefined ) { + + this.interpolateRotations( curves.z ); + curves.z.values = curves.z.values.map( MathUtils.degToRad ); + + } + + const times = this.getTimesForAllAxes( curves ); + const values = this.getKeyframeTrackValues( times, curves, initialValue ); + + if ( preRotation !== undefined ) { + + preRotation = preRotation.map( MathUtils.degToRad ); + preRotation.push( eulerOrder ); + + preRotation = new Euler().fromArray( preRotation ); + preRotation = new Quaternion().setFromEuler( preRotation ); + + } + + if ( postRotation !== undefined ) { + + postRotation = postRotation.map( MathUtils.degToRad ); + postRotation.push( eulerOrder ); + + postRotation = new Euler().fromArray( postRotation ); + postRotation = new Quaternion().setFromEuler( postRotation ).invert(); + + } + + const quaternion = new Quaternion(); + const euler = new Euler(); + + const quaternionValues = []; + + for ( let i = 0; i < values.length; i += 3 ) { + + euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder ); + + quaternion.setFromEuler( euler ); + + if ( preRotation !== undefined ) quaternion.premultiply( preRotation ); + if ( postRotation !== undefined ) quaternion.multiply( postRotation ); + + quaternion.toArray( quaternionValues, ( i / 3 ) * 4 ); + + } + + return new QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues ); + + } + + generateMorphTrack( rawTracks ) { + + const curves = rawTracks.DeformPercent.curves.morph; + const values = curves.values.map( function ( val ) { + + return val / 100; + + } ); + + const morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ]; + + return new NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values ); + + } + + // For all animated objects, times are defined separately for each axis + // Here we'll combine the times into one sorted array without duplicates + getTimesForAllAxes( curves ) { + + let times = []; + + // first join together the times for each axis, if defined + if ( curves.x !== undefined ) times = times.concat( curves.x.times ); + if ( curves.y !== undefined ) times = times.concat( curves.y.times ); + if ( curves.z !== undefined ) times = times.concat( curves.z.times ); + + // then sort them + times = times.sort( function ( a, b ) { + + return a - b; + + } ); + + // and remove duplicates + if ( times.length > 1 ) { + + let targetIndex = 1; + let lastValue = times[ 0 ]; + for ( let i = 1; i < times.length; i ++ ) { + + const currentValue = times[ i ]; + if ( currentValue !== lastValue ) { + + times[ targetIndex ] = currentValue; + lastValue = currentValue; + targetIndex ++; + + } + + } + + times = times.slice( 0, targetIndex ); + + } + + return times; + + } + + getKeyframeTrackValues( times, curves, initialValue ) { + + const prevValue = initialValue; + + const values = []; + + let xIndex = - 1; + let yIndex = - 1; + let zIndex = - 1; + + times.forEach( function ( time ) { + + if ( curves.x ) xIndex = curves.x.times.indexOf( time ); + if ( curves.y ) yIndex = curves.y.times.indexOf( time ); + if ( curves.z ) zIndex = curves.z.times.indexOf( time ); + + // if there is an x value defined for this frame, use that + if ( xIndex !== - 1 ) { + + const xValue = curves.x.values[ xIndex ]; + values.push( xValue ); + prevValue[ 0 ] = xValue; + + } else { + + // otherwise use the x value from the previous frame + values.push( prevValue[ 0 ] ); + + } + + if ( yIndex !== - 1 ) { + + const yValue = curves.y.values[ yIndex ]; + values.push( yValue ); + prevValue[ 1 ] = yValue; + + } else { + + values.push( prevValue[ 1 ] ); + + } + + if ( zIndex !== - 1 ) { + + const zValue = curves.z.values[ zIndex ]; + values.push( zValue ); + prevValue[ 2 ] = zValue; + + } else { + + values.push( prevValue[ 2 ] ); + + } + + } ); + + return values; + + } + + // Rotations are defined as Euler angles which can have values of any size + // These will be converted to quaternions which don't support values greater than + // PI, so we'll interpolate large rotations + interpolateRotations( curve ) { + + for ( let i = 1; i < curve.values.length; i ++ ) { + + const initialValue = curve.values[ i - 1 ]; + const valuesSpan = curve.values[ i ] - initialValue; + + const absoluteSpan = Math.abs( valuesSpan ); + + if ( absoluteSpan >= 180 ) { + + const numSubIntervals = absoluteSpan / 180; + + const step = valuesSpan / numSubIntervals; + let nextValue = initialValue + step; + + const initialTime = curve.times[ i - 1 ]; + const timeSpan = curve.times[ i ] - initialTime; + const interval = timeSpan / numSubIntervals; + let nextTime = initialTime + interval; + + const interpolatedTimes = []; + const interpolatedValues = []; + + while ( nextTime < curve.times[ i ] ) { + + interpolatedTimes.push( nextTime ); + nextTime += interval; + + interpolatedValues.push( nextValue ); + nextValue += step; + + } + + curve.times = inject( curve.times, i, interpolatedTimes ); + curve.values = inject( curve.values, i, interpolatedValues ); + + } + + } + + } + +} + +// parse an FBX file in ASCII format +class TextParser { + + getPrevNode() { + + return this.nodeStack[ this.currentIndent - 2 ]; + + } + + getCurrentNode() { + + return this.nodeStack[ this.currentIndent - 1 ]; + + } + + getCurrentProp() { + + return this.currentProp; + + } + + pushStack( node ) { + + this.nodeStack.push( node ); + this.currentIndent += 1; + + } + + popStack() { + + this.nodeStack.pop(); + this.currentIndent -= 1; + + } + + setCurrentProp( val, name ) { + + this.currentProp = val; + this.currentPropName = name; + + } + + parse( text ) { + + this.currentIndent = 0; + + this.allNodes = new FBXTree(); + this.nodeStack = []; + this.currentProp = []; + this.currentPropName = ''; + + const scope = this; + + const split = text.split( /[\r\n]+/ ); + + split.forEach( function ( line, i ) { + + const matchComment = line.match( /^[\s\t]*;/ ); + const matchEmpty = line.match( /^[\s\t]*$/ ); + + if ( matchComment || matchEmpty ) return; + + const matchBeginning = line.match( '^\\t{' + scope.currentIndent + '}(\\w+):(.*){', '' ); + const matchProperty = line.match( '^\\t{' + ( scope.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' ); + const matchEnd = line.match( '^\\t{' + ( scope.currentIndent - 1 ) + '}}' ); + + if ( matchBeginning ) { + + scope.parseNodeBegin( line, matchBeginning ); + + } else if ( matchProperty ) { + + scope.parseNodeProperty( line, matchProperty, split[ ++ i ] ); + + } else if ( matchEnd ) { + + scope.popStack(); + + } else if ( line.match( /^[^\s\t}]/ ) ) { + + // large arrays are split over multiple lines terminated with a ',' character + // if this is encountered the line needs to be joined to the previous line + scope.parseNodePropertyContinued( line ); + + } + + } ); + + return this.allNodes; + + } + + parseNodeBegin( line, property ) { + + const nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' ); + + const nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) { + + return attr.trim().replace( /^"/, '' ).replace( /"$/, '' ); + + } ); + + const node = { name: nodeName }; + const attrs = this.parseNodeAttr( nodeAttrs ); + + const currentNode = this.getCurrentNode(); + + // a top node + if ( this.currentIndent === 0 ) { + + this.allNodes.add( nodeName, node ); + + } else { // a subnode + + // if the subnode already exists, append it + if ( nodeName in currentNode ) { + + // special case Pose needs PoseNodes as an array + if ( nodeName === 'PoseNode' ) { + + currentNode.PoseNode.push( node ); + + } else if ( currentNode[ nodeName ].id !== undefined ) { + + currentNode[ nodeName ] = {}; + currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ]; + + } + + if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node; + + } else if ( typeof attrs.id === 'number' ) { + + currentNode[ nodeName ] = {}; + currentNode[ nodeName ][ attrs.id ] = node; + + } else if ( nodeName !== 'Properties70' ) { + + if ( nodeName === 'PoseNode' ) currentNode[ nodeName ] = [ node ]; + else currentNode[ nodeName ] = node; + + } + + } + + if ( typeof attrs.id === 'number' ) node.id = attrs.id; + if ( attrs.name !== '' ) node.attrName = attrs.name; + if ( attrs.type !== '' ) node.attrType = attrs.type; + + this.pushStack( node ); + + } + + parseNodeAttr( attrs ) { + + let id = attrs[ 0 ]; + + if ( attrs[ 0 ] !== '' ) { + + id = parseInt( attrs[ 0 ] ); + + if ( isNaN( id ) ) { + + id = attrs[ 0 ]; + + } + + } + + let name = '', type = ''; + + if ( attrs.length > 1 ) { + + name = attrs[ 1 ].replace( /^(\w+)::/, '' ); + type = attrs[ 2 ]; + + } + + return { id: id, name: name, type: type }; + + } + + parseNodeProperty( line, property, contentLine ) { + + let propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim(); + let propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim(); + + // for special case: base64 image data follows "Content: ," line + // Content: , + // "/9j/4RDaRXhpZgAATU0A..." + if ( propName === 'Content' && propValue === ',' ) { + + propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim(); + + } + + const currentNode = this.getCurrentNode(); + const parentName = currentNode.name; + + if ( parentName === 'Properties70' ) { + + this.parseNodeSpecialProperty( line, propName, propValue ); + return; + + } + + // Connections + if ( propName === 'C' ) { + + const connProps = propValue.split( ',' ).slice( 1 ); + const from = parseInt( connProps[ 0 ] ); + const to = parseInt( connProps[ 1 ] ); + + let rest = propValue.split( ',' ).slice( 3 ); + + rest = rest.map( function ( elem ) { + + return elem.trim().replace( /^"/, '' ); + + } ); + + propName = 'connections'; + propValue = [ from, to ]; + append( propValue, rest ); + + if ( currentNode[ propName ] === undefined ) { + + currentNode[ propName ] = []; + + } + + } + + // Node + if ( propName === 'Node' ) currentNode.id = propValue; + + // connections + if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) { + + currentNode[ propName ].push( propValue ); + + } else { + + if ( propName !== 'a' ) currentNode[ propName ] = propValue; + else currentNode.a = propValue; + + } + + this.setCurrentProp( currentNode, propName ); + + // convert string to array, unless it ends in ',' in which case more will be added to it + if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) { + + currentNode.a = parseNumberArray( propValue ); + + } + + } + + parseNodePropertyContinued( line ) { + + const currentNode = this.getCurrentNode(); + + currentNode.a += line; + + // if the line doesn't end in ',' we have reached the end of the property value + // so convert the string to an array + if ( line.slice( - 1 ) !== ',' ) { + + currentNode.a = parseNumberArray( currentNode.a ); + + } + + } + + // parse "Property70" + parseNodeSpecialProperty( line, propName, propValue ) { + + // split this + // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + // into array like below + // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ] + const props = propValue.split( '",' ).map( function ( prop ) { + + return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' ); + + } ); + + const innerPropName = props[ 0 ]; + const innerPropType1 = props[ 1 ]; + const innerPropType2 = props[ 2 ]; + const innerPropFlag = props[ 3 ]; + let innerPropValue = props[ 4 ]; + + // cast values where needed, otherwise leave as strings + switch ( innerPropType1 ) { + + case 'int': + case 'enum': + case 'bool': + case 'ULongLong': + case 'double': + case 'Number': + case 'FieldOfView': + innerPropValue = parseFloat( innerPropValue ); + break; + + case 'Color': + case 'ColorRGB': + case 'Vector3D': + case 'Lcl_Translation': + case 'Lcl_Rotation': + case 'Lcl_Scaling': + innerPropValue = parseNumberArray( innerPropValue ); + break; + + } + + // CAUTION: these props must append to parent's parent + this.getPrevNode()[ innerPropName ] = { + + 'type': innerPropType1, + 'type2': innerPropType2, + 'flag': innerPropFlag, + 'value': innerPropValue + + }; + + this.setCurrentProp( this.getPrevNode(), innerPropName ); + + } + +} + +// Parse an FBX file in Binary format +class BinaryParser { + + parse( buffer ) { + + const reader = new BinaryReader( buffer ); + reader.skip( 23 ); // skip magic 23 bytes + + const version = reader.getUint32(); + + if ( version < 6400 ) { + + throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + version ); + + } + + const allNodes = new FBXTree(); + + while ( ! this.endOfContent( reader ) ) { + + const node = this.parseNode( reader, version ); + if ( node !== null ) allNodes.add( node.name, node ); + + } + + return allNodes; + + } + + // Check if reader has reached the end of content. + endOfContent( reader ) { + + // footer size: 160bytes + 16-byte alignment padding + // - 16bytes: magic + // - padding til 16-byte alignment (at least 1byte?) + // (seems like some exporters embed fixed 15 or 16bytes?) + // - 4bytes: magic + // - 4bytes: version + // - 120bytes: zero + // - 16bytes: magic + if ( reader.size() % 16 === 0 ) { + + return ( ( reader.getOffset() + 160 + 16 ) & ~ 0xf ) >= reader.size(); + + } else { + + return reader.getOffset() + 160 + 16 >= reader.size(); + + } + + } + + // recursively parse nodes until the end of the file is reached + parseNode( reader, version ) { + + const node = {}; + + // The first three data sizes depends on version. + const endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); + const numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); + + ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); // the returned propertyListLen is not used + + const nameLen = reader.getUint8(); + const name = reader.getString( nameLen ); + + // Regards this node as NULL-record if endOffset is zero + if ( endOffset === 0 ) return null; + + const propertyList = []; + + for ( let i = 0; i < numProperties; i ++ ) { + + propertyList.push( this.parseProperty( reader ) ); + + } + + // Regards the first three elements in propertyList as id, attrName, and attrType + const id = propertyList.length > 0 ? propertyList[ 0 ] : ''; + const attrName = propertyList.length > 1 ? propertyList[ 1 ] : ''; + const attrType = propertyList.length > 2 ? propertyList[ 2 ] : ''; + + // check if this node represents just a single property + // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]} + node.singleProperty = ( numProperties === 1 && reader.getOffset() === endOffset ) ? true : false; + + while ( endOffset > reader.getOffset() ) { + + const subNode = this.parseNode( reader, version ); + + if ( subNode !== null ) this.parseSubNode( name, node, subNode ); + + } + + node.propertyList = propertyList; // raw property list used by parent + + if ( typeof id === 'number' ) node.id = id; + if ( attrName !== '' ) node.attrName = attrName; + if ( attrType !== '' ) node.attrType = attrType; + if ( name !== '' ) node.name = name; + + return node; + + } + + parseSubNode( name, node, subNode ) { + + // special case: child node is single property + if ( subNode.singleProperty === true ) { + + const value = subNode.propertyList[ 0 ]; + + if ( Array.isArray( value ) ) { + + node[ subNode.name ] = subNode; + + subNode.a = value; + + } else { + + node[ subNode.name ] = value; + + } + + } else if ( name === 'Connections' && subNode.name === 'C' ) { + + const array = []; + + subNode.propertyList.forEach( function ( property, i ) { + + // first Connection is FBX type (OO, OP, etc.). We'll discard these + if ( i !== 0 ) array.push( property ); + + } ); + + if ( node.connections === undefined ) { + + node.connections = []; + + } + + node.connections.push( array ); + + } else if ( subNode.name === 'Properties70' ) { + + const keys = Object.keys( subNode ); + + keys.forEach( function ( key ) { + + node[ key ] = subNode[ key ]; + + } ); + + } else if ( name === 'Properties70' && subNode.name === 'P' ) { + + let innerPropName = subNode.propertyList[ 0 ]; + let innerPropType1 = subNode.propertyList[ 1 ]; + const innerPropType2 = subNode.propertyList[ 2 ]; + const innerPropFlag = subNode.propertyList[ 3 ]; + let innerPropValue; + + if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' ); + if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' ); + + if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) { + + innerPropValue = [ + subNode.propertyList[ 4 ], + subNode.propertyList[ 5 ], + subNode.propertyList[ 6 ] + ]; + + } else { + + innerPropValue = subNode.propertyList[ 4 ]; + + } + + // this will be copied to parent, see above + node[ innerPropName ] = { + + 'type': innerPropType1, + 'type2': innerPropType2, + 'flag': innerPropFlag, + 'value': innerPropValue + + }; + + } else if ( node[ subNode.name ] === undefined ) { + + if ( typeof subNode.id === 'number' ) { + + node[ subNode.name ] = {}; + node[ subNode.name ][ subNode.id ] = subNode; + + } else { + + node[ subNode.name ] = subNode; + + } + + } else { + + if ( subNode.name === 'PoseNode' ) { + + if ( ! Array.isArray( node[ subNode.name ] ) ) { + + node[ subNode.name ] = [ node[ subNode.name ] ]; + + } + + node[ subNode.name ].push( subNode ); + + } else if ( node[ subNode.name ][ subNode.id ] === undefined ) { + + node[ subNode.name ][ subNode.id ] = subNode; + + } + + } + + } + + parseProperty( reader ) { + + const type = reader.getString( 1 ); + let length; + + switch ( type ) { + + case 'C': + return reader.getBoolean(); + + case 'D': + return reader.getFloat64(); + + case 'F': + return reader.getFloat32(); + + case 'I': + return reader.getInt32(); + + case 'L': + return reader.getInt64(); + + case 'R': + length = reader.getUint32(); + return reader.getArrayBuffer( length ); + + case 'S': + length = reader.getUint32(); + return reader.getString( length ); + + case 'Y': + return reader.getInt16(); + + case 'b': + case 'c': + case 'd': + case 'f': + case 'i': + case 'l': + + const arrayLength = reader.getUint32(); + const encoding = reader.getUint32(); // 0: non-compressed, 1: compressed + const compressedLength = reader.getUint32(); + + if ( encoding === 0 ) { + + switch ( type ) { + + case 'b': + case 'c': + return reader.getBooleanArray( arrayLength ); + + case 'd': + return reader.getFloat64Array( arrayLength ); + + case 'f': + return reader.getFloat32Array( arrayLength ); + + case 'i': + return reader.getInt32Array( arrayLength ); + + case 'l': + return reader.getInt64Array( arrayLength ); + + } + + } + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.FBXLoader: External library fflate.min.js required.' ); + + } + + const data = fflate.unzlibSync( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef + const reader2 = new BinaryReader( data.buffer ); + + switch ( type ) { + + case 'b': + case 'c': + return reader2.getBooleanArray( arrayLength ); + + case 'd': + return reader2.getFloat64Array( arrayLength ); + + case 'f': + return reader2.getFloat32Array( arrayLength ); + + case 'i': + return reader2.getInt32Array( arrayLength ); + + case 'l': + return reader2.getInt64Array( arrayLength ); + + } + + default: + throw new Error( 'THREE.FBXLoader: Unknown property type ' + type ); + + } + + } + +} + +class BinaryReader { + + constructor( buffer, littleEndian ) { + + this.dv = new DataView( buffer ); + this.offset = 0; + this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true; + + } + + getOffset() { + + return this.offset; + + } + + size() { + + return this.dv.buffer.byteLength; + + } + + skip( length ) { + + this.offset += length; + + } + + // seems like true/false representation depends on exporter. + // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54) + // then sees LSB. + getBoolean() { + + return ( this.getUint8() & 1 ) === 1; + + } + + getBooleanArray( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getBoolean() ); + + } + + return a; + + } + + getUint8() { + + const value = this.dv.getUint8( this.offset ); + this.offset += 1; + return value; + + } + + getInt16() { + + const value = this.dv.getInt16( this.offset, this.littleEndian ); + this.offset += 2; + return value; + + } + + getInt32() { + + const value = this.dv.getInt32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + } + + getInt32Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getInt32() ); + + } + + return a; + + } + + getUint32() { + + const value = this.dv.getUint32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + } + + // JavaScript doesn't support 64-bit integer so calculate this here + // 1 << 32 will return 1 so using multiply operation instead here. + // There's a possibility that this method returns wrong value if the value + // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. + // TODO: safely handle 64-bit integer + getInt64() { + + let low, high; + + if ( this.littleEndian ) { + + low = this.getUint32(); + high = this.getUint32(); + + } else { + + high = this.getUint32(); + low = this.getUint32(); + + } + + // calculate negative value + if ( high & 0x80000000 ) { + + high = ~ high & 0xFFFFFFFF; + low = ~ low & 0xFFFFFFFF; + + if ( low === 0xFFFFFFFF ) high = ( high + 1 ) & 0xFFFFFFFF; + + low = ( low + 1 ) & 0xFFFFFFFF; + + return - ( high * 0x100000000 + low ); + + } + + return high * 0x100000000 + low; + + } + + getInt64Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getInt64() ); + + } + + return a; + + } + + // Note: see getInt64() comment + getUint64() { + + let low, high; + + if ( this.littleEndian ) { + + low = this.getUint32(); + high = this.getUint32(); + + } else { + + high = this.getUint32(); + low = this.getUint32(); + + } + + return high * 0x100000000 + low; + + } + + getFloat32() { + + const value = this.dv.getFloat32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + } + + getFloat32Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getFloat32() ); + + } + + return a; + + } + + getFloat64() { + + const value = this.dv.getFloat64( this.offset, this.littleEndian ); + this.offset += 8; + return value; + + } + + getFloat64Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getFloat64() ); + + } + + return a; + + } + + getArrayBuffer( size ) { + + const value = this.dv.buffer.slice( this.offset, this.offset + size ); + this.offset += size; + return value; + + } + + getString( size ) { + + // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead + let a = []; + + for ( let i = 0; i < size; i ++ ) { + + a[ i ] = this.getUint8(); + + } + + const nullByte = a.indexOf( 0 ); + if ( nullByte >= 0 ) a = a.slice( 0, nullByte ); + + return LoaderUtils.decodeText( new Uint8Array( a ) ); + + } + +} + +// FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format) +// and BinaryParser( FBX Binary format) +class FBXTree { + + add( key, val ) { + + this[ key ] = val; + + } + +} + +// ************** UTILITY FUNCTIONS ************** + +function isFbxFormatBinary( buffer ) { + + const CORRECT = 'Kaydara\u0020FBX\u0020Binary\u0020\u0020\0'; + + return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length ); + +} + +function isFbxFormatASCII( text ) { + + const CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ]; + + let cursor = 0; + + function read( offset ) { + + const result = text[ offset - 1 ]; + text = text.slice( cursor + offset ); + cursor ++; + return result; + + } + + for ( let i = 0; i < CORRECT.length; ++ i ) { + + const num = read( 1 ); + if ( num === CORRECT[ i ] ) { + + return false; + + } + + } + + return true; + +} + +function getFbxVersion( text ) { + + const versionRegExp = /FBXVersion: (\d+)/; + const match = text.match( versionRegExp ); + + if ( match ) { + + const version = parseInt( match[ 1 ] ); + return version; + + } + + throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' ); + +} + +// Converts FBX ticks into real time seconds. +function convertFBXTimeToSeconds( time ) { + + return time / 46186158000; + +} + +const dataArray = []; + +// extracts the data from the correct position in the FBX array based on indexing type +function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) { + + let index; + + switch ( infoObject.mappingType ) { + + case 'ByPolygonVertex' : + index = polygonVertexIndex; + break; + case 'ByPolygon' : + index = polygonIndex; + break; + case 'ByVertice' : + index = vertexIndex; + break; + case 'AllSame' : + index = infoObject.indices[ 0 ]; + break; + default : + console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType ); + + } + + if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ]; + + const from = index * infoObject.dataSize; + const to = from + infoObject.dataSize; + + return slice( dataArray, infoObject.buffer, from, to ); + +} + +const tempEuler = new Euler(); +const tempVec = new Vector3(); + +// generate transformation from FBX transform data +// ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm +// ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e +function generateTransform( transformData ) { + + const lTranslationM = new Matrix4(); + const lPreRotationM = new Matrix4(); + const lRotationM = new Matrix4(); + const lPostRotationM = new Matrix4(); + + const lScalingM = new Matrix4(); + const lScalingPivotM = new Matrix4(); + const lScalingOffsetM = new Matrix4(); + const lRotationOffsetM = new Matrix4(); + const lRotationPivotM = new Matrix4(); + + const lParentGX = new Matrix4(); + const lParentLX = new Matrix4(); + const lGlobalT = new Matrix4(); + + const inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0; + + if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) ); + + if ( transformData.preRotation ) { + + const array = transformData.preRotation.map( MathUtils.degToRad ); + array.push( transformData.eulerOrder ); + lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); + + } + + if ( transformData.rotation ) { + + const array = transformData.rotation.map( MathUtils.degToRad ); + array.push( transformData.eulerOrder ); + lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); + + } + + if ( transformData.postRotation ) { + + const array = transformData.postRotation.map( MathUtils.degToRad ); + array.push( transformData.eulerOrder ); + lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); + lPostRotationM.invert(); + + } + + if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) ); + + // Pivots and offsets + if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) ); + if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) ); + if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) ); + if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) ); + + // parent transform + if ( transformData.parentMatrixWorld ) { + + lParentLX.copy( transformData.parentMatrix ); + lParentGX.copy( transformData.parentMatrixWorld ); + + } + + const lLRM = lPreRotationM.clone().multiply( lRotationM ).multiply( lPostRotationM ); + // Global Rotation + const lParentGRM = new Matrix4(); + lParentGRM.extractRotation( lParentGX ); + + // Global Shear*Scaling + const lParentTM = new Matrix4(); + lParentTM.copyPosition( lParentGX ); + + const lParentGRSM = lParentTM.clone().invert().multiply( lParentGX ); + const lParentGSM = lParentGRM.clone().invert().multiply( lParentGRSM ); + const lLSM = lScalingM; + + const lGlobalRS = new Matrix4(); + + if ( inheritType === 0 ) { + + lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM ); + + } else if ( inheritType === 1 ) { + + lGlobalRS.copy( lParentGRM ).multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM ); + + } else { + + const lParentLSM = new Matrix4().scale( new Vector3().setFromMatrixScale( lParentLX ) ); + const lParentLSM_inv = lParentLSM.clone().invert(); + const lParentGSM_noLocal = lParentGSM.clone().multiply( lParentLSM_inv ); + + lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM ); + + } + + const lRotationPivotM_inv = lRotationPivotM.clone().invert(); + const lScalingPivotM_inv = lScalingPivotM.clone().invert(); + // Calculate the local transform matrix + let lTransform = lTranslationM.clone().multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM_inv ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM_inv ); + + const lLocalTWithAllPivotAndOffsetInfo = new Matrix4().copyPosition( lTransform ); + + const lGlobalTranslation = lParentGX.clone().multiply( lLocalTWithAllPivotAndOffsetInfo ); + lGlobalT.copyPosition( lGlobalTranslation ); + + lTransform = lGlobalT.clone().multiply( lGlobalRS ); + + // from global to local + lTransform.premultiply( lParentGX.invert() ); + + return lTransform; + +} + +// Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order +// ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html +function getEulerOrder( order ) { + + order = order || 0; + + const enums = [ + 'ZYX', // -> XYZ extrinsic + 'YZX', // -> XZY extrinsic + 'XZY', // -> YZX extrinsic + 'ZXY', // -> YXZ extrinsic + 'YXZ', // -> ZXY extrinsic + 'XYZ', // -> ZYX extrinsic + //'SphericXYZ', // not possible to support + ]; + + if ( order === 6 ) { + + console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' ); + return enums[ 0 ]; + + } + + return enums[ order ]; + +} + +// Parses comma separated list of numbers and returns them an array. +// Used internally by the TextParser +function parseNumberArray( value ) { + + const array = value.split( ',' ).map( function ( val ) { + + return parseFloat( val ); + + } ); + + return array; + +} + +function convertArrayBufferToString( buffer, from, to ) { + + if ( from === undefined ) from = 0; + if ( to === undefined ) to = buffer.byteLength; + + return LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ); + +} + +function append( a, b ) { + + for ( let i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) { + + a[ j ] = b[ i ]; + + } + +} + +function slice( a, b, from, to ) { + + for ( let i = from, j = 0; i < to; i ++, j ++ ) { + + a[ j ] = b[ i ]; + + } + + return a; + +} + +// inject array a2 into array a1 at index +function inject( a1, index, a2 ) { + + return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) ); + +} + +export { FBXLoader }; diff --git a/jsm/loaders/FontLoader.js b/jsm/loaders/FontLoader.js new file mode 100644 index 0000000..2f4f399 --- /dev/null +++ b/jsm/loaders/FontLoader.js @@ -0,0 +1,196 @@ +import { + FileLoader, + Loader, + ShapePath +} from 'three'; + +class FontLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + let json; + + try { + + json = JSON.parse( text ); + + } catch ( e ) { + + console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' ); + json = JSON.parse( text.substring( 65, text.length - 2 ) ); + + } + + const font = scope.parse( json ); + + if ( onLoad ) onLoad( font ); + + }, onProgress, onError ); + + } + + parse( json ) { + + return new Font( json ); + + } + +} + +// + +class Font { + + constructor( data ) { + + this.type = 'Font'; + + this.data = data; + + } + + generateShapes( text, size = 100 ) { + + const shapes = []; + const paths = createPaths( text, size, this.data ); + + for ( let p = 0, pl = paths.length; p < pl; p ++ ) { + + Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); + + } + + return shapes; + + } + +} + +function createPaths( text, size, data ) { + + const chars = Array.from( text ); + const scale = size / data.resolution; + const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; + + const paths = []; + + let offsetX = 0, offsetY = 0; + + for ( let i = 0; i < chars.length; i ++ ) { + + const char = chars[ i ]; + + if ( char === '\n' ) { + + offsetX = 0; + offsetY -= line_height; + + } else { + + const ret = createPath( char, scale, offsetX, offsetY, data ); + offsetX += ret.offsetX; + paths.push( ret.path ); + + } + + } + + return paths; + +} + +function createPath( char, scale, offsetX, offsetY, data ) { + + const glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; + + if ( ! glyph ) { + + console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' ); + + return; + + } + + const path = new ShapePath(); + + let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; + + if ( glyph.o ) { + + const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + + for ( let i = 0, l = outline.length; i < l; ) { + + const action = outline[ i ++ ]; + + switch ( action ) { + + case 'm': // moveTo + + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + + path.moveTo( x, y ); + + break; + + case 'l': // lineTo + + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + + path.lineTo( x, y ); + + break; + + case 'q': // quadraticCurveTo + + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + + path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); + + break; + + case 'b': // bezierCurveTo + + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + cpx2 = outline[ i ++ ] * scale + offsetX; + cpy2 = outline[ i ++ ] * scale + offsetY; + + path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); + + break; + + } + + } + + } + + return { offsetX: glyph.ha * scale, path: path }; + +} + +Font.prototype.isFont = true; + +export { FontLoader, Font }; diff --git a/jsm/loaders/GCodeLoader.js b/jsm/loaders/GCodeLoader.js new file mode 100644 index 0000000..b438b51 --- /dev/null +++ b/jsm/loaders/GCodeLoader.js @@ -0,0 +1,262 @@ +import { + BufferGeometry, + Euler, + FileLoader, + Float32BufferAttribute, + Group, + LineBasicMaterial, + LineSegments, + Loader +} from 'three'; + +/** + * GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications. + * + * Gcode files are composed by commands used by machines to create objects. + * + * @class GCodeLoader + * @param {Manager} manager Loading manager. + */ + +class GCodeLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.splitLayer = false; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + let state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false }; + const layers = []; + + let currentLayer = undefined; + + const pathMaterial = new LineBasicMaterial( { color: 0xFF0000 } ); + pathMaterial.name = 'path'; + + const extrudingMaterial = new LineBasicMaterial( { color: 0x00FF00 } ); + extrudingMaterial.name = 'extruded'; + + function newLayer( line ) { + + currentLayer = { vertex: [], pathVertex: [], z: line.z }; + layers.push( currentLayer ); + + } + + //Create lie segment between p1 and p2 + function addSegment( p1, p2 ) { + + if ( currentLayer === undefined ) { + + newLayer( p1 ); + + } + + if ( state.extruding ) { + + currentLayer.vertex.push( p1.x, p1.y, p1.z ); + currentLayer.vertex.push( p2.x, p2.y, p2.z ); + + } else { + + currentLayer.pathVertex.push( p1.x, p1.y, p1.z ); + currentLayer.pathVertex.push( p2.x, p2.y, p2.z ); + + } + + } + + function delta( v1, v2 ) { + + return state.relative ? v2 : v2 - v1; + + } + + function absolute( v1, v2 ) { + + return state.relative ? v1 + v2 : v2; + + } + + const lines = data.replace( /;.+/g, '' ).split( '\n' ); + + for ( let i = 0; i < lines.length; i ++ ) { + + const tokens = lines[ i ].split( ' ' ); + const cmd = tokens[ 0 ].toUpperCase(); + + //Argumments + const args = {}; + tokens.splice( 1 ).forEach( function ( token ) { + + if ( token[ 0 ] !== undefined ) { + + const key = token[ 0 ].toLowerCase(); + const value = parseFloat( token.substring( 1 ) ); + args[ key ] = value; + + } + + } ); + + //Process commands + //G0/G1 – Linear Movement + if ( cmd === 'G0' || cmd === 'G1' ) { + + const line = { + x: args.x !== undefined ? absolute( state.x, args.x ) : state.x, + y: args.y !== undefined ? absolute( state.y, args.y ) : state.y, + z: args.z !== undefined ? absolute( state.z, args.z ) : state.z, + e: args.e !== undefined ? absolute( state.e, args.e ) : state.e, + f: args.f !== undefined ? absolute( state.f, args.f ) : state.f, + }; + + //Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position + if ( delta( state.e, line.e ) > 0 ) { + + state.extruding = delta( state.e, line.e ) > 0; + + if ( currentLayer == undefined || line.z != currentLayer.z ) { + + newLayer( line ); + + } + + } + + addSegment( state, line ); + state = line; + + } else if ( cmd === 'G2' || cmd === 'G3' ) { + + //G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise ) + //console.warn( 'THREE.GCodeLoader: Arc command not supported' ); + + } else if ( cmd === 'G90' ) { + + //G90: Set to Absolute Positioning + state.relative = false; + + } else if ( cmd === 'G91' ) { + + //G91: Set to state.relative Positioning + state.relative = true; + + } else if ( cmd === 'G92' ) { + + //G92: Set Position + const line = state; + line.x = args.x !== undefined ? args.x : line.x; + line.y = args.y !== undefined ? args.y : line.y; + line.z = args.z !== undefined ? args.z : line.z; + line.e = args.e !== undefined ? args.e : line.e; + + } else { + + //console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd ); + + } + + } + + function addObject( vertex, extruding, i ) { + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertex, 3 ) ); + const segments = new LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial ); + segments.name = 'layer' + i; + object.add( segments ); + + } + + const object = new Group(); + object.name = 'gcode'; + + if ( this.splitLayer ) { + + for ( let i = 0; i < layers.length; i ++ ) { + + const layer = layers[ i ]; + addObject( layer.vertex, true, i ); + addObject( layer.pathVertex, false, i ); + + } + + } else { + + const vertex = [], + pathVertex = []; + + for ( let i = 0; i < layers.length; i ++ ) { + + const layer = layers[ i ]; + const layerVertex = layer.vertex; + const layerPathVertex = layer.pathVertex; + + for ( let j = 0; j < layerVertex.length; j ++ ) { + + vertex.push( layerVertex[ j ] ); + + } + + for ( let j = 0; j < layerPathVertex.length; j ++ ) { + + pathVertex.push( layerPathVertex[ j ] ); + + } + + } + + addObject( vertex, true, layers.length ); + addObject( pathVertex, false, layers.length ); + + } + + object.quaternion.setFromEuler( new Euler( - Math.PI / 2, 0, 0 ) ); + + return object; + + } + +} + +export { GCodeLoader }; diff --git a/jsm/loaders/GLTFLoader.js b/jsm/loaders/GLTFLoader.js new file mode 100644 index 0000000..31cfe0d --- /dev/null +++ b/jsm/loaders/GLTFLoader.js @@ -0,0 +1,4402 @@ +import { + AnimationClip, + Bone, + Box3, + BufferAttribute, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DirectionalLight, + DoubleSide, + FileLoader, + FrontSide, + Group, + ImageBitmapLoader, + InterleavedBuffer, + InterleavedBufferAttribute, + Interpolant, + InterpolateDiscrete, + InterpolateLinear, + Line, + LineBasicMaterial, + LineLoop, + LineSegments, + LinearFilter, + LinearMipmapLinearFilter, + LinearMipmapNearestFilter, + Loader, + LoaderUtils, + Material, + MathUtils, + Matrix4, + Mesh, + MeshBasicMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MirroredRepeatWrapping, + NearestFilter, + NearestMipmapLinearFilter, + NearestMipmapNearestFilter, + NumberKeyframeTrack, + Object3D, + OrthographicCamera, + PerspectiveCamera, + PointLight, + Points, + PointsMaterial, + PropertyBinding, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Skeleton, + SkinnedMesh, + Sphere, + SpotLight, + TangentSpaceNormalMap, + Texture, + TextureLoader, + TriangleFanDrawMode, + TriangleStripDrawMode, + Vector2, + Vector3, + VectorKeyframeTrack, + sRGBEncoding +} from '../../src/Three.js'; + +class GLTFLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.dracoLoader = null; + this.ktx2Loader = null; + this.meshoptDecoder = null; + + this.pluginCallbacks = []; + + this.register( function ( parser ) { + + return new GLTFMaterialsClearcoatExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFTextureBasisUExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFTextureWebPExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsSheenExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsTransmissionExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsVolumeExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsIorExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsSpecularExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFLightsExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMeshoptCompression( parser ); + + } ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + let resourcePath; + + if ( this.resourcePath !== '' ) { + + resourcePath = this.resourcePath; + + } else if ( this.path !== '' ) { + + resourcePath = this.path; + + } else { + + resourcePath = LoaderUtils.extractUrlBase( url ); + + } + + // Tells the LoadingManager to track an extra item, which resolves after + // the model is fully loaded. This means the count of items loaded will + // be incorrect, but ensures manager.onLoad() does not fire early. + this.manager.itemStart( url ); + + const _onError = function ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + }; + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, function ( data ) { + + try { + + scope.parse( data, resourcePath, function ( gltf ) { + + onLoad( gltf ); + + scope.manager.itemEnd( url ); + + }, _onError ); + + } catch ( e ) { + + _onError( e ); + + } + + }, onProgress, _onError ); + + } + + setDRACOLoader( dracoLoader ) { + + this.dracoLoader = dracoLoader; + return this; + + } + + setDDSLoader() { + + throw new Error( + + 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' + + ); + + } + + setKTX2Loader( ktx2Loader ) { + + this.ktx2Loader = ktx2Loader; + return this; + + } + + setMeshoptDecoder( meshoptDecoder ) { + + this.meshoptDecoder = meshoptDecoder; + return this; + + } + + register( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + + this.pluginCallbacks.push( callback ); + + } + + return this; + + } + + unregister( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + + } + + return this; + + } + + parse( data, path, onLoad, onError ) { + + let content; + const extensions = {}; + const plugins = {}; + + if ( typeof data === 'string' ) { + + content = data; + + } else { + + const magic = LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); + + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { + + try { + + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + + } catch ( error ) { + + if ( onError ) onError( error ); + return; + + } + + content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + + } else { + + content = LoaderUtils.decodeText( new Uint8Array( data ) ); + + } + + } + + const json = JSON.parse( content ); + + if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { + + if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); + return; + + } + + const parser = new GLTFParser( json, { + + path: path || this.resourcePath || '', + crossOrigin: this.crossOrigin, + requestHeader: this.requestHeader, + manager: this.manager, + ktx2Loader: this.ktx2Loader, + meshoptDecoder: this.meshoptDecoder + + } ); + + parser.fileLoader.setRequestHeader( this.requestHeader ); + + for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { + + const plugin = this.pluginCallbacks[ i ]( parser ); + plugins[ plugin.name ] = plugin; + + // Workaround to avoid determining as unknown extension + // in addUnknownExtensionsToUserData(). + // Remove this workaround if we move all the existing + // extension handlers to plugin system + extensions[ plugin.name ] = true; + + } + + if ( json.extensionsUsed ) { + + for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { + + const extensionName = json.extensionsUsed[ i ]; + const extensionsRequired = json.extensionsRequired || []; + + switch ( extensionName ) { + + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); + break; + + case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: + extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); + break; + + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); + break; + + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); + break; + + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); + break; + + default: + + if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { + + console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); + + } + + } + + } + + } + + parser.setExtensions( extensions ); + parser.setPlugins( plugins ); + parser.parse( onLoad, onError ); + + } + + parseAsync( data, path ) { + + const scope = this; + + return new Promise( function ( resolve, reject ) { + + scope.parse( data, path, resolve, reject ); + + } ); + + } + +} + +/* GLTFREGISTRY */ + +function GLTFRegistry() { + + let objects = {}; + + return { + + get: function ( key ) { + + return objects[ key ]; + + }, + + add: function ( key, object ) { + + objects[ key ] = object; + + }, + + remove: function ( key ) { + + delete objects[ key ]; + + }, + + removeAll: function () { + + objects = {}; + + } + + }; + +} + +/*********************************/ +/********** EXTENSIONS ***********/ +/*********************************/ + +const EXTENSIONS = { + KHR_BINARY_GLTF: 'KHR_binary_glTF', + KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', + KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', + KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', + KHR_MATERIALS_IOR: 'KHR_materials_ior', + KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', + KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', + KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', + KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', + KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', + KHR_MATERIALS_VOLUME: 'KHR_materials_volume', + KHR_TEXTURE_BASISU: 'KHR_texture_basisu', + KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', + KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', + EXT_TEXTURE_WEBP: 'EXT_texture_webp', + EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression' +}; + +/** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ +class GLTFLightsExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; + + // Object3D instance caches + this.cache = { refs: {}, uses: {} }; + + } + + _markDefs() { + + const parser = this.parser; + const nodeDefs = this.parser.json.nodes || []; + + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + + const nodeDef = nodeDefs[ nodeIndex ]; + + if ( nodeDef.extensions + && nodeDef.extensions[ this.name ] + && nodeDef.extensions[ this.name ].light !== undefined ) { + + parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); + + } + + } + + } + + _loadLight( lightIndex ) { + + const parser = this.parser; + const cacheKey = 'light:' + lightIndex; + let dependency = parser.cache.get( cacheKey ); + + if ( dependency ) return dependency; + + const json = parser.json; + const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; + const lightDefs = extensions.lights || []; + const lightDef = lightDefs[ lightIndex ]; + let lightNode; + + const color = new Color( 0xffffff ); + + if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); + + const range = lightDef.range !== undefined ? lightDef.range : 0; + + switch ( lightDef.type ) { + + case 'directional': + lightNode = new DirectionalLight( color ); + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + case 'point': + lightNode = new PointLight( color ); + lightNode.distance = range; + break; + + case 'spot': + lightNode = new SpotLight( color ); + lightNode.distance = range; + // Handle spotlight properties. + lightDef.spot = lightDef.spot || {}; + lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; + lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; + lightNode.angle = lightDef.spot.outerConeAngle; + lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + default: + throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type ); + + } + + // Some lights (e.g. spot) default to a position other than the origin. Reset the position + // here, because node-level parsing will only override position if explicitly specified. + lightNode.position.set( 0, 0, 0 ); + + lightNode.decay = 2; + + if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + + lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); + + dependency = Promise.resolve( lightNode ); + + parser.cache.add( cacheKey, dependency ); + + return dependency; + + } + + createNodeAttachment( nodeIndex ) { + + const self = this; + const parser = this.parser; + const json = parser.json; + const nodeDef = json.nodes[ nodeIndex ]; + const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; + const lightIndex = lightDef.light; + + if ( lightIndex === undefined ) return null; + + return this._loadLight( lightIndex ).then( function ( light ) { + + return parser._getNodeRef( self.cache, lightIndex, light ); + + } ); + + } + +} + +/** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ +class GLTFMaterialsUnlitExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; + + } + + getMaterialType() { + + return MeshBasicMaterial; + + } + + extendParams( materialParams, materialDef, parser ) { + + const pending = []; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + const metallicRoughness = materialDef.pbrMetallicRoughness; + + if ( metallicRoughness ) { + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + const array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, sRGBEncoding ) ); + + } + + } + + return Promise.all( pending ); + + } + +} + +/** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ +class GLTFMaterialsClearcoatExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + if ( extension.clearcoatFactor !== undefined ) { + + materialParams.clearcoat = extension.clearcoatFactor; + + } + + if ( extension.clearcoatTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); + + } + + if ( extension.clearcoatRoughnessFactor !== undefined ) { + + materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; + + } + + if ( extension.clearcoatRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); + + } + + if ( extension.clearcoatNormalTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); + + if ( extension.clearcoatNormalTexture.scale !== undefined ) { + + const scale = extension.clearcoatNormalTexture.scale; + + materialParams.clearcoatNormalScale = new Vector2( scale, scale ); + + } + + } + + return Promise.all( pending ); + + } + +} + +/** + * Sheen Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + */ +class GLTFMaterialsSheenExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + materialParams.sheenColor = new Color( 0, 0, 0 ); + materialParams.sheenRoughness = 0; + materialParams.sheen = 1; + + const extension = materialDef.extensions[ this.name ]; + + if ( extension.sheenColorFactor !== undefined ) { + + materialParams.sheenColor.fromArray( extension.sheenColorFactor ); + + } + + if ( extension.sheenRoughnessFactor !== undefined ) { + + materialParams.sheenRoughness = extension.sheenRoughnessFactor; + + } + + if ( extension.sheenColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, sRGBEncoding ) ); + + } + + if ( extension.sheenRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) ); + + } + + return Promise.all( pending ); + + } + +} + +/** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + * Draft: https://github.com/KhronosGroup/glTF/pull/1698 + */ +class GLTFMaterialsTransmissionExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + if ( extension.transmissionFactor !== undefined ) { + + materialParams.transmission = extension.transmissionFactor; + + } + + if ( extension.transmissionTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); + + } + + return Promise.all( pending ); + + } + +} + +/** + * Materials Volume Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + */ +class GLTFMaterialsVolumeExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; + + if ( extension.thicknessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) ); + + } + + materialParams.attenuationDistance = extension.attenuationDistance || 0; + + const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; + materialParams.attenuationColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] ); + + return Promise.all( pending ); + + } + +} + +/** + * Materials ior Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + */ +class GLTFMaterialsIorExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_IOR; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const extension = materialDef.extensions[ this.name ]; + + materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; + + return Promise.resolve(); + + } + +} + +/** + * Materials specular Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + */ +class GLTFMaterialsSpecularExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; + + if ( extension.specularTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) ); + + } + + const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; + materialParams.specularColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] ); + + if ( extension.specularColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, sRGBEncoding ) ); + + } + + return Promise.all( pending ); + + } + +} + +/** + * BasisU Texture Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu + */ +class GLTFTextureBasisUExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_TEXTURE_BASISU; + + } + + loadTexture( textureIndex ) { + + const parser = this.parser; + const json = parser.json; + + const textureDef = json.textures[ textureIndex ]; + + if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { + + return null; + + } + + const extension = textureDef.extensions[ this.name ]; + const loader = parser.options.ktx2Loader; + + if ( ! loader ) { + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); + + } else { + + // Assumes that the extension is optional and that a fallback texture is present + return null; + + } + + } + + return parser.loadTextureImage( textureIndex, extension.source, loader ); + + } + +} + +/** + * WebP Texture Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp + */ +class GLTFTextureWebPExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.EXT_TEXTURE_WEBP; + this.isSupported = null; + + } + + loadTexture( textureIndex ) { + + const name = this.name; + const parser = this.parser; + const json = parser.json; + + const textureDef = json.textures[ textureIndex ]; + + if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { + + return null; + + } + + const extension = textureDef.extensions[ name ]; + const source = json.images[ extension.source ]; + + let loader = parser.textureLoader; + if ( source.uri ) { + + const handler = parser.options.manager.getHandler( source.uri ); + if ( handler !== null ) loader = handler; + + } + + return this.detectSupport().then( function ( isSupported ) { + + if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); + + } + + // Fall back to PNG or JPEG. + return parser.loadTexture( textureIndex ); + + } ); + + } + + detectSupport() { + + if ( ! this.isSupported ) { + + this.isSupported = new Promise( function ( resolve ) { + + const image = new Image(); + + // Lossy test image. Support for lossy images doesn't guarantee support for all + // WebP images, unfortunately. + image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; + + image.onload = image.onerror = function () { + + resolve( image.height === 1 ); + + }; + + } ); + + } + + return this.isSupported; + + } + +} + +/** + * meshopt BufferView Compression Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression + */ +class GLTFMeshoptCompression { + + constructor( parser ) { + + this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; + this.parser = parser; + + } + + loadBufferView( index ) { + + const json = this.parser.json; + const bufferView = json.bufferViews[ index ]; + + if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { + + const extensionDef = bufferView.extensions[ this.name ]; + + const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); + const decoder = this.parser.options.meshoptDecoder; + + if ( ! decoder || ! decoder.supported ) { + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); + + } else { + + // Assumes that the extension is optional and that fallback buffer data is present + return null; + + } + + } + + return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) { + + const byteOffset = extensionDef.byteOffset || 0; + const byteLength = extensionDef.byteLength || 0; + + const count = extensionDef.count; + const stride = extensionDef.byteStride; + + const result = new ArrayBuffer( count * stride ); + const source = new Uint8Array( res[ 0 ], byteOffset, byteLength ); + + decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); + return result; + + } ); + + } else { + + return null; + + } + + } + +} + +/* BINARY EXTENSION */ +const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; +const BINARY_EXTENSION_HEADER_LENGTH = 12; +const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; + +class GLTFBinaryExtension { + + constructor( data ) { + + this.name = EXTENSIONS.KHR_BINARY_GLTF; + this.content = null; + this.body = null; + + const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); + + this.header = { + magic: LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), + version: headerView.getUint32( 4, true ), + length: headerView.getUint32( 8, true ) + }; + + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { + + throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); + + } else if ( this.header.version < 2.0 ) { + + throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' ); + + } + + const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); + let chunkIndex = 0; + + while ( chunkIndex < chunkContentsLength ) { + + const chunkLength = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + const chunkType = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { + + const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); + this.content = LoaderUtils.decodeText( contentArray ); + + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { + + const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; + this.body = data.slice( byteOffset, byteOffset + chunkLength ); + + } + + // Clients must ignore chunks with unknown types. + + chunkIndex += chunkLength; + + } + + if ( this.content === null ) { + + throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); + + } + + } + +} + +/** + * DRACO Mesh Compression Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression + */ +class GLTFDracoMeshCompressionExtension { + + constructor( json, dracoLoader ) { + + if ( ! dracoLoader ) { + + throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); + + } + + this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; + this.json = json; + this.dracoLoader = dracoLoader; + this.dracoLoader.preload(); + + } + + decodePrimitive( primitive, parser ) { + + const json = this.json; + const dracoLoader = this.dracoLoader; + const bufferViewIndex = primitive.extensions[ this.name ].bufferView; + const gltfAttributeMap = primitive.extensions[ this.name ].attributes; + const threeAttributeMap = {}; + const attributeNormalizedMap = {}; + const attributeTypeMap = {}; + + for ( const attributeName in gltfAttributeMap ) { + + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + + } + + for ( const attributeName in primitive.attributes ) { + + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + if ( gltfAttributeMap[ attributeName ] !== undefined ) { + + const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + attributeTypeMap[ threeAttributeName ] = componentType; + attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + + } + + } + + return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { + + return new Promise( function ( resolve ) { + + dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { + + for ( const attributeName in geometry.attributes ) { + + const attribute = geometry.attributes[ attributeName ]; + const normalized = attributeNormalizedMap[ attributeName ]; + + if ( normalized !== undefined ) attribute.normalized = normalized; + + } + + resolve( geometry ); + + }, threeAttributeMap, attributeTypeMap ); + + } ); + + } ); + + } + +} + +/** + * Texture Transform Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform + */ +class GLTFTextureTransformExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; + + } + + extendTexture( texture, transform ) { + + if ( transform.texCoord !== undefined ) { + + console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); + + } + + if ( transform.offset === undefined && transform.rotation === undefined && transform.scale === undefined ) { + + // See https://github.com/mrdoob/three.js/issues/21819. + return texture; + + } + + texture = texture.clone(); + + if ( transform.offset !== undefined ) { + + texture.offset.fromArray( transform.offset ); + + } + + if ( transform.rotation !== undefined ) { + + texture.rotation = transform.rotation; + + } + + if ( transform.scale !== undefined ) { + + texture.repeat.fromArray( transform.scale ); + + } + + texture.needsUpdate = true; + + return texture; + + } + +} + +/** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness + */ + +/** + * A sub class of StandardMaterial with some of the functionality + * changed via the `onBeforeCompile` callback + * @pailhead + */ +class GLTFMeshStandardSGMaterial extends MeshStandardMaterial { + + constructor( params ) { + + super(); + + this.isGLTFSpecularGlossinessMaterial = true; + + //various chunks that need replacing + const specularMapParsFragmentChunk = [ + '#ifdef USE_SPECULARMAP', + ' uniform sampler2D specularMap;', + '#endif' + ].join( '\n' ); + + const glossinessMapParsFragmentChunk = [ + '#ifdef USE_GLOSSINESSMAP', + ' uniform sampler2D glossinessMap;', + '#endif' + ].join( '\n' ); + + const specularMapFragmentChunk = [ + 'vec3 specularFactor = specular;', + '#ifdef USE_SPECULARMAP', + ' vec4 texelSpecular = texture2D( specularMap, vUv );', + ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' specularFactor *= texelSpecular.rgb;', + '#endif' + ].join( '\n' ); + + const glossinessMapFragmentChunk = [ + 'float glossinessFactor = glossiness;', + '#ifdef USE_GLOSSINESSMAP', + ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', + ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' glossinessFactor *= texelGlossiness.a;', + '#endif' + ].join( '\n' ); + + const lightPhysicalFragmentChunk = [ + 'PhysicalMaterial material;', + 'material.diffuseColor = diffuseColor.rgb * ( 1. - max( specularFactor.r, max( specularFactor.g, specularFactor.b ) ) );', + 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', + 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', + 'material.roughness = max( 1.0 - glossinessFactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.', + 'material.roughness += geometryRoughness;', + 'material.roughness = min( material.roughness, 1.0 );', + 'material.specularColor = specularFactor;', + ].join( '\n' ); + + const uniforms = { + specular: { value: new Color().setHex( 0xffffff ) }, + glossiness: { value: 1 }, + specularMap: { value: null }, + glossinessMap: { value: null } + }; + + this._extraUniforms = uniforms; + + this.onBeforeCompile = function ( shader ) { + + for ( const uniformName in uniforms ) { + + shader.uniforms[ uniformName ] = uniforms[ uniformName ]; + + } + + shader.fragmentShader = shader.fragmentShader + .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) + .replace( 'uniform float metalness;', 'uniform float glossiness;' ) + .replace( '#include ', specularMapParsFragmentChunk ) + .replace( '#include ', glossinessMapParsFragmentChunk ) + .replace( '#include ', specularMapFragmentChunk ) + .replace( '#include ', glossinessMapFragmentChunk ) + .replace( '#include ', lightPhysicalFragmentChunk ); + + }; + + Object.defineProperties( this, { + + specular: { + get: function () { + + return uniforms.specular.value; + + }, + set: function ( v ) { + + uniforms.specular.value = v; + + } + }, + + specularMap: { + get: function () { + + return uniforms.specularMap.value; + + }, + set: function ( v ) { + + uniforms.specularMap.value = v; + + if ( v ) { + + this.defines.USE_SPECULARMAP = ''; // USE_UV is set by the renderer for specular maps + + } else { + + delete this.defines.USE_SPECULARMAP; + + } + + } + }, + + glossiness: { + get: function () { + + return uniforms.glossiness.value; + + }, + set: function ( v ) { + + uniforms.glossiness.value = v; + + } + }, + + glossinessMap: { + get: function () { + + return uniforms.glossinessMap.value; + + }, + set: function ( v ) { + + uniforms.glossinessMap.value = v; + + if ( v ) { + + this.defines.USE_GLOSSINESSMAP = ''; + this.defines.USE_UV = ''; + + } else { + + delete this.defines.USE_GLOSSINESSMAP; + delete this.defines.USE_UV; + + } + + } + } + + } ); + + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; + + this.setValues( params ); + + } + + copy( source ) { + + super.copy( source ); + + this.specularMap = source.specularMap; + this.specular.copy( source.specular ); + this.glossinessMap = source.glossinessMap; + this.glossiness = source.glossiness; + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; + return this; + + } + +} + + +class GLTFMaterialsPbrSpecularGlossinessExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS; + + this.specularGlossinessParams = [ + 'color', + 'map', + 'lightMap', + 'lightMapIntensity', + 'aoMap', + 'aoMapIntensity', + 'emissive', + 'emissiveIntensity', + 'emissiveMap', + 'bumpMap', + 'bumpScale', + 'normalMap', + 'normalMapType', + 'displacementMap', + 'displacementScale', + 'displacementBias', + 'specularMap', + 'specular', + 'glossinessMap', + 'glossiness', + 'alphaMap', + 'envMap', + 'envMapIntensity' + ]; + + } + + getMaterialType() { + + return GLTFMeshStandardSGMaterial; + + } + + extendParams( materialParams, materialDef, parser ) { + + const pbrSpecularGlossiness = materialDef.extensions[ this.name ]; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + const pending = []; + + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + + const array = pbrSpecularGlossiness.diffuseFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture, sRGBEncoding ) ); + + } + + materialParams.emissive = new Color( 0.0, 0.0, 0.0 ); + materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + materialParams.specular = new Color( 1.0, 1.0, 1.0 ); + + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + + materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + + } + + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + + const specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef, sRGBEncoding ) ); + + } + + return Promise.all( pending ); + + } + + createMaterial( materialParams ) { + + const material = new GLTFMeshStandardSGMaterial( materialParams ); + material.fog = true; + + material.color = materialParams.color; + + material.map = materialParams.map === undefined ? null : materialParams.map; + + material.lightMap = null; + material.lightMapIntensity = 1.0; + + material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap; + material.aoMapIntensity = 1.0; + + material.emissive = materialParams.emissive; + material.emissiveIntensity = 1.0; + material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap; + + material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap; + material.bumpScale = 1; + + material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap; + material.normalMapType = TangentSpaceNormalMap; + + if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale; + + material.displacementMap = null; + material.displacementScale = 1; + material.displacementBias = 0; + + material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap; + material.specular = materialParams.specular; + + material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap; + material.glossiness = materialParams.glossiness; + + material.alphaMap = null; + + material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap; + material.envMapIntensity = 1.0; + + return material; + + } + +} + +/** + * Mesh Quantization Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization + */ +class GLTFMeshQuantizationExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; + + } + +} + +/*********************************/ +/********** INTERPOLATION ********/ +/*********************************/ + +// Spline Interpolation +// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation +class GLTFCubicSplineInterpolant extends Interpolant { + + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + copySampleValue_( index ) { + + // Copies a sample value to the result buffer. See description of glTF + // CUBICSPLINE values layout in interpolate_() function below. + + const result = this.resultBuffer, + values = this.sampleValues, + valueSize = this.valueSize, + offset = index * valueSize * 3 + valueSize; + + for ( let i = 0; i !== valueSize; i ++ ) { + + result[ i ] = values[ offset + i ]; + + } + + return result; + + } + +} + +GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + +GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + +GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { + + const result = this.resultBuffer; + const values = this.sampleValues; + const stride = this.valueSize; + + const stride2 = stride * 2; + const stride3 = stride * 3; + + const td = t1 - t0; + + const p = ( t - t0 ) / td; + const pp = p * p; + const ppp = pp * p; + + const offset1 = i1 * stride3; + const offset0 = offset1 - stride3; + + const s2 = - 2 * ppp + 3 * pp; + const s3 = ppp - pp; + const s0 = 1 - s2; + const s1 = s3 - pp + p; + + // Layout of keyframe output values for CUBICSPLINE animations: + // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] + for ( let i = 0; i !== stride; i ++ ) { + + const p0 = values[ offset0 + i + stride ]; // splineVertex_k + const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) + const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 + const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) + + result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; + + } + + return result; + +}; + +const _q = new Quaternion(); + +class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { + + interpolate_( i1, t0, t, t1 ) { + + const result = super.interpolate_( i1, t0, t, t1 ); + + _q.fromArray( result ).normalize().toArray( result ); + + return result; + + } + +} + + +/*********************************/ +/********** INTERNALS ************/ +/*********************************/ + +/* CONSTANTS */ + +const WEBGL_CONSTANTS = { + FLOAT: 5126, + //FLOAT_MAT2: 35674, + FLOAT_MAT3: 35675, + FLOAT_MAT4: 35676, + FLOAT_VEC2: 35664, + FLOAT_VEC3: 35665, + FLOAT_VEC4: 35666, + LINEAR: 9729, + REPEAT: 10497, + SAMPLER_2D: 35678, + POINTS: 0, + LINES: 1, + LINE_LOOP: 2, + LINE_STRIP: 3, + TRIANGLES: 4, + TRIANGLE_STRIP: 5, + TRIANGLE_FAN: 6, + UNSIGNED_BYTE: 5121, + UNSIGNED_SHORT: 5123 +}; + +const WEBGL_COMPONENT_TYPES = { + 5120: Int8Array, + 5121: Uint8Array, + 5122: Int16Array, + 5123: Uint16Array, + 5125: Uint32Array, + 5126: Float32Array +}; + +const WEBGL_FILTERS = { + 9728: NearestFilter, + 9729: LinearFilter, + 9984: NearestMipmapNearestFilter, + 9985: LinearMipmapNearestFilter, + 9986: NearestMipmapLinearFilter, + 9987: LinearMipmapLinearFilter +}; + +const WEBGL_WRAPPINGS = { + 33071: ClampToEdgeWrapping, + 33648: MirroredRepeatWrapping, + 10497: RepeatWrapping +}; + +const WEBGL_TYPE_SIZES = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 +}; + +const ATTRIBUTES = { + POSITION: 'position', + NORMAL: 'normal', + TANGENT: 'tangent', + TEXCOORD_0: 'uv', + TEXCOORD_1: 'uv2', + COLOR_0: 'color', + WEIGHTS_0: 'skinWeight', + JOINTS_0: 'skinIndex', +}; + +const PATH_PROPERTIES = { + scale: 'scale', + translation: 'position', + rotation: 'quaternion', + weights: 'morphTargetInfluences' +}; + +const INTERPOLATION = { + CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each + // keyframe track will be initialized with a default interpolation type, then modified. + LINEAR: InterpolateLinear, + STEP: InterpolateDiscrete +}; + +const ALPHA_MODES = { + OPAQUE: 'OPAQUE', + MASK: 'MASK', + BLEND: 'BLEND' +}; + +/** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material + */ +function createDefaultMaterial( cache ) { + + if ( cache[ 'DefaultMaterial' ] === undefined ) { + + cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( { + color: 0xFFFFFF, + emissive: 0x000000, + metalness: 1, + roughness: 1, + transparent: false, + depthTest: true, + side: FrontSide + } ); + + } + + return cache[ 'DefaultMaterial' ]; + +} + +function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { + + // Add unknown glTF extensions to an object's userData. + + for ( const name in objectDef.extensions ) { + + if ( knownExtensions[ name ] === undefined ) { + + object.userData.gltfExtensions = object.userData.gltfExtensions || {}; + object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; + + } + + } + +} + +/** + * @param {Object3D|Material|BufferGeometry} object + * @param {GLTF.definition} gltfDef + */ +function assignExtrasToUserData( object, gltfDef ) { + + if ( gltfDef.extras !== undefined ) { + + if ( typeof gltfDef.extras === 'object' ) { + + Object.assign( object.userData, gltfDef.extras ); + + } else { + + console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); + + } + + } + +} + +/** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets + * + * @param {BufferGeometry} geometry + * @param {Array} targets + * @param {GLTFParser} parser + * @return {Promise} + */ +function addMorphTargets( geometry, targets, parser ) { + + let hasMorphPosition = false; + let hasMorphNormal = false; + let hasMorphColor = false; + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; + + if ( target.POSITION !== undefined ) hasMorphPosition = true; + if ( target.NORMAL !== undefined ) hasMorphNormal = true; + if ( target.COLOR_0 !== undefined ) hasMorphColor = true; + + if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break; + + } + + if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry ); + + const pendingPositionAccessors = []; + const pendingNormalAccessors = []; + const pendingColorAccessors = []; + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; + + if ( hasMorphPosition ) { + + const pendingAccessor = target.POSITION !== undefined + ? parser.getDependency( 'accessor', target.POSITION ) + : geometry.attributes.position; + + pendingPositionAccessors.push( pendingAccessor ); + + } + + if ( hasMorphNormal ) { + + const pendingAccessor = target.NORMAL !== undefined + ? parser.getDependency( 'accessor', target.NORMAL ) + : geometry.attributes.normal; + + pendingNormalAccessors.push( pendingAccessor ); + + } + + if ( hasMorphColor ) { + + const pendingAccessor = target.COLOR_0 !== undefined + ? parser.getDependency( 'accessor', target.COLOR_0 ) + : geometry.attributes.color; + + pendingColorAccessors.push( pendingAccessor ); + + } + + } + + return Promise.all( [ + Promise.all( pendingPositionAccessors ), + Promise.all( pendingNormalAccessors ), + Promise.all( pendingColorAccessors ) + ] ).then( function ( accessors ) { + + const morphPositions = accessors[ 0 ]; + const morphNormals = accessors[ 1 ]; + const morphColors = accessors[ 2 ]; + + if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; + if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; + if ( hasMorphColor ) geometry.morphAttributes.color = morphColors; + geometry.morphTargetsRelative = true; + + return geometry; + + } ); + +} + +/** + * @param {Mesh} mesh + * @param {GLTF.Mesh} meshDef + */ +function updateMorphTargets( mesh, meshDef ) { + + mesh.updateMorphTargets(); + + if ( meshDef.weights !== undefined ) { + + for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) { + + mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; + + } + + } + + // .extras has user-defined data, so check that .extras.targetNames is an array. + if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { + + const targetNames = meshDef.extras.targetNames; + + if ( mesh.morphTargetInfluences.length === targetNames.length ) { + + mesh.morphTargetDictionary = {}; + + for ( let i = 0, il = targetNames.length; i < il; i ++ ) { + + mesh.morphTargetDictionary[ targetNames[ i ] ] = i; + + } + + } else { + + console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); + + } + + } + +} + +function createPrimitiveKey( primitiveDef ) { + + const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; + let geometryKey; + + if ( dracoExtension ) { + + geometryKey = 'draco:' + dracoExtension.bufferView + + ':' + dracoExtension.indices + + ':' + createAttributesKey( dracoExtension.attributes ); + + } else { + + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; + + } + + return geometryKey; + +} + +function createAttributesKey( attributes ) { + + let attributesKey = ''; + + const keys = Object.keys( attributes ).sort(); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; + + } + + return attributesKey; + +} + +function getNormalizedComponentScale( constructor ) { + + // Reference: + // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data + + switch ( constructor ) { + + case Int8Array: + return 1 / 127; + + case Uint8Array: + return 1 / 255; + + case Int16Array: + return 1 / 32767; + + case Uint16Array: + return 1 / 65535; + + default: + throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); + + } + +} + +function getImageURIMimeType( uri ) { + + if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; + if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; + + return 'image/png'; + +} + +/* GLTF PARSER */ + +class GLTFParser { + + constructor( json = {}, options = {} ) { + + this.json = json; + this.extensions = {}; + this.plugins = {}; + this.options = options; + + // loader object cache + this.cache = new GLTFRegistry(); + + // associations between Three.js objects and glTF elements + this.associations = new Map(); + + // BufferGeometry caching + this.primitiveCache = {}; + + // Object3D instance caches + this.meshCache = { refs: {}, uses: {} }; + this.cameraCache = { refs: {}, uses: {} }; + this.lightCache = { refs: {}, uses: {} }; + + this.sourceCache = {}; + this.textureCache = {}; + + // Track node names, to ensure no duplicates + this.nodeNamesUsed = {}; + + // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the + // expensive work of uploading a texture to the GPU off the main thread. + if ( typeof createImageBitmap !== 'undefined' && /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === false ) { + + this.textureLoader = new ImageBitmapLoader( this.options.manager ); + + } else { + + this.textureLoader = new TextureLoader( this.options.manager ); + + } + + this.textureLoader.setCrossOrigin( this.options.crossOrigin ); + this.textureLoader.setRequestHeader( this.options.requestHeader ); + + this.fileLoader = new FileLoader( this.options.manager ); + this.fileLoader.setResponseType( 'arraybuffer' ); + + if ( this.options.crossOrigin === 'use-credentials' ) { + + this.fileLoader.setWithCredentials( true ); + + } + + } + + setExtensions( extensions ) { + + this.extensions = extensions; + + } + + setPlugins( plugins ) { + + this.plugins = plugins; + + } + + parse( onLoad, onError ) { + + const parser = this; + const json = this.json; + const extensions = this.extensions; + + // Clear the loader cache + this.cache.removeAll(); + + // Mark the special nodes/meshes in json for efficient parse + this._invokeAll( function ( ext ) { + + return ext._markDefs && ext._markDefs(); + + } ); + + Promise.all( this._invokeAll( function ( ext ) { + + return ext.beforeRoot && ext.beforeRoot(); + + } ) ).then( function () { + + return Promise.all( [ + + parser.getDependencies( 'scene' ), + parser.getDependencies( 'animation' ), + parser.getDependencies( 'camera' ), + + ] ); + + } ).then( function ( dependencies ) { + + const result = { + scene: dependencies[ 0 ][ json.scene || 0 ], + scenes: dependencies[ 0 ], + animations: dependencies[ 1 ], + cameras: dependencies[ 2 ], + asset: json.asset, + parser: parser, + userData: {} + }; + + addUnknownExtensionsToUserData( extensions, result, json ); + + assignExtrasToUserData( result, json ); + + Promise.all( parser._invokeAll( function ( ext ) { + + return ext.afterRoot && ext.afterRoot( result ); + + } ) ).then( function () { + + onLoad( result ); + + } ); + + } ).catch( onError ); + + } + + /** + * Marks the special nodes/meshes in json for efficient parse. + */ + _markDefs() { + + const nodeDefs = this.json.nodes || []; + const skinDefs = this.json.skins || []; + const meshDefs = this.json.meshes || []; + + // Nothing in the node definition indicates whether it is a Bone or an + // Object3D. Use the skins' joint references to mark bones. + for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { + + const joints = skinDefs[ skinIndex ].joints; + + for ( let i = 0, il = joints.length; i < il; i ++ ) { + + nodeDefs[ joints[ i ] ].isBone = true; + + } + + } + + // Iterate over all nodes, marking references to shared resources, + // as well as skeleton joints. + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + + const nodeDef = nodeDefs[ nodeIndex ]; + + if ( nodeDef.mesh !== undefined ) { + + this._addNodeRef( this.meshCache, nodeDef.mesh ); + + // Nothing in the mesh definition indicates whether it is + // a SkinnedMesh or Mesh. Use the node's mesh reference + // to mark SkinnedMesh if node has skin. + if ( nodeDef.skin !== undefined ) { + + meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; + + } + + } + + if ( nodeDef.camera !== undefined ) { + + this._addNodeRef( this.cameraCache, nodeDef.camera ); + + } + + } + + } + + /** + * Counts references to shared node / Object3D resources. These resources + * can be reused, or "instantiated", at multiple nodes in the scene + * hierarchy. Mesh, Camera, and Light instances are instantiated and must + * be marked. Non-scenegraph resources (like Materials, Geometries, and + * Textures) can be reused directly and are not marked here. + * + * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. + */ + _addNodeRef( cache, index ) { + + if ( index === undefined ) return; + + if ( cache.refs[ index ] === undefined ) { + + cache.refs[ index ] = cache.uses[ index ] = 0; + + } + + cache.refs[ index ] ++; + + } + + /** Returns a reference to a shared resource, cloning it if necessary. */ + _getNodeRef( cache, index, object ) { + + if ( cache.refs[ index ] <= 1 ) return object; + + const ref = object.clone(); + + // Propagates mappings to the cloned object, prevents mappings on the + // original object from being lost. + const updateMappings = ( original, clone ) => { + + const mappings = this.associations.get( original ); + if ( mappings != null ) { + + this.associations.set( clone, mappings ); + + } + + for ( const [ i, child ] of original.children.entries() ) { + + updateMappings( child, clone.children[ i ] ); + + } + + }; + + updateMappings( object, ref ); + + ref.name += '_instance_' + ( cache.uses[ index ] ++ ); + + return ref; + + } + + _invokeOne( func ) { + + const extensions = Object.values( this.plugins ); + extensions.push( this ); + + for ( let i = 0; i < extensions.length; i ++ ) { + + const result = func( extensions[ i ] ); + + if ( result ) return result; + + } + + return null; + + } + + _invokeAll( func ) { + + const extensions = Object.values( this.plugins ); + extensions.unshift( this ); + + const pending = []; + + for ( let i = 0; i < extensions.length; i ++ ) { + + const result = func( extensions[ i ] ); + + if ( result ) pending.push( result ); + + } + + return pending; + + } + + /** + * Requests the specified dependency asynchronously, with caching. + * @param {string} type + * @param {number} index + * @return {Promise} + */ + getDependency( type, index ) { + + const cacheKey = type + ':' + index; + let dependency = this.cache.get( cacheKey ); + + if ( ! dependency ) { + + switch ( type ) { + + case 'scene': + dependency = this.loadScene( index ); + break; + + case 'node': + dependency = this.loadNode( index ); + break; + + case 'mesh': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadMesh && ext.loadMesh( index ); + + } ); + break; + + case 'accessor': + dependency = this.loadAccessor( index ); + break; + + case 'bufferView': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadBufferView && ext.loadBufferView( index ); + + } ); + break; + + case 'buffer': + dependency = this.loadBuffer( index ); + break; + + case 'material': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadMaterial && ext.loadMaterial( index ); + + } ); + break; + + case 'texture': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadTexture && ext.loadTexture( index ); + + } ); + break; + + case 'skin': + dependency = this.loadSkin( index ); + break; + + case 'animation': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadAnimation && ext.loadAnimation( index ); + + } ); + break; + + case 'camera': + dependency = this.loadCamera( index ); + break; + + default: + throw new Error( 'Unknown type: ' + type ); + + } + + this.cache.add( cacheKey, dependency ); + + } + + return dependency; + + } + + /** + * Requests all dependencies of the specified type asynchronously, with caching. + * @param {string} type + * @return {Promise>} + */ + getDependencies( type ) { + + let dependencies = this.cache.get( type ); + + if ( ! dependencies ) { + + const parser = this; + const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; + + dependencies = Promise.all( defs.map( function ( def, index ) { + + return parser.getDependency( type, index ); + + } ) ); + + this.cache.add( type, dependencies ); + + } + + return dependencies; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferIndex + * @return {Promise} + */ + loadBuffer( bufferIndex ) { + + const bufferDef = this.json.buffers[ bufferIndex ]; + const loader = this.fileLoader; + + if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { + + throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); + + } + + // If present, GLB container is required to be the first buffer. + if ( bufferDef.uri === undefined && bufferIndex === 0 ) { + + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); + + } + + const options = this.options; + + return new Promise( function ( resolve, reject ) { + + loader.load( LoaderUtils.resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { + + reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); + + } ); + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferViewIndex + * @return {Promise} + */ + loadBufferView( bufferViewIndex ) { + + const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; + + return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { + + const byteLength = bufferViewDef.byteLength || 0; + const byteOffset = bufferViewDef.byteOffset || 0; + return buffer.slice( byteOffset, byteOffset + byteLength ); + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors + * @param {number} accessorIndex + * @return {Promise} + */ + loadAccessor( accessorIndex ) { + + const parser = this; + const json = this.json; + + const accessorDef = this.json.accessors[ accessorIndex ]; + + if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { + + // Ignore empty accessors, which may be used to declare runtime + // information about attributes coming from another source (e.g. Draco + // compression extension). + return Promise.resolve( null ); + + } + + const pendingBufferViews = []; + + if ( accessorDef.bufferView !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); + + } else { + + pendingBufferViews.push( null ); + + } + + if ( accessorDef.sparse !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); + + } + + return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { + + const bufferView = bufferViews[ 0 ]; + + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. + const elementBytes = TypedArray.BYTES_PER_ELEMENT; + const itemBytes = elementBytes * itemSize; + const byteOffset = accessorDef.byteOffset || 0; + const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; + const normalized = accessorDef.normalized === true; + let array, bufferAttribute; + + // The buffer is not interleaved if the stride is the item size in bytes. + if ( byteStride && byteStride !== itemBytes ) { + + // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer + // This makes sure that IBA.count reflects accessor.count properly + const ibSlice = Math.floor( byteOffset / byteStride ); + const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; + let ib = parser.cache.get( ibCacheKey ); + + if ( ! ib ) { + + array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes ); + + // Integer parameters to IB/IBA are in array elements, not bytes. + ib = new InterleavedBuffer( array, byteStride / elementBytes ); + + parser.cache.add( ibCacheKey, ib ); + + } + + bufferAttribute = new InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized ); + + } else { + + if ( bufferView === null ) { + + array = new TypedArray( accessorDef.count * itemSize ); + + } else { + + array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); + + } + + bufferAttribute = new BufferAttribute( array, itemSize, normalized ); + + } + + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors + if ( accessorDef.sparse !== undefined ) { + + const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; + + const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; + const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; + + const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); + const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); + + if ( bufferView !== null ) { + + // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. + bufferAttribute = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); + + } + + for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { + + const index = sparseIndices[ i ]; + + bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); + if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); + if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); + if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); + if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); + + } + + } + + return bufferAttribute; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * @param {number} textureIndex + * @return {Promise} + */ + loadTexture( textureIndex ) { + + const json = this.json; + const options = this.options; + const textureDef = json.textures[ textureIndex ]; + const sourceIndex = textureDef.source; + const sourceDef = json.images[ sourceIndex ]; + + let loader = this.textureLoader; + + if ( sourceDef.uri ) { + + const handler = options.manager.getHandler( sourceDef.uri ); + if ( handler !== null ) loader = handler; + + } + + return this.loadTextureImage( textureIndex, sourceIndex, loader ); + + } + + loadTextureImage( textureIndex, sourceIndex, loader ) { + + const parser = this; + const json = this.json; + + const textureDef = json.textures[ textureIndex ]; + const sourceDef = json.images[ sourceIndex ]; + + const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler; + + if ( this.textureCache[ cacheKey ] ) { + + // See https://github.com/mrdoob/three.js/issues/21559. + return this.textureCache[ cacheKey ]; + + } + + const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) { + + texture.flipY = false; + + if ( textureDef.name ) texture.name = textureDef.name; + + const samplers = json.samplers || {}; + const sampler = samplers[ textureDef.sampler ] || {}; + + texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; + texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; + texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; + texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; + + parser.associations.set( texture, { textures: textureIndex } ); + + return texture; + + } ).catch( function () { + + return null; + + } ); + + this.textureCache[ cacheKey ] = promise; + + return promise; + + } + + loadImageSource( sourceIndex, loader ) { + + const parser = this; + const json = this.json; + const options = this.options; + + if ( this.sourceCache[ sourceIndex ] !== undefined ) { + + return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() ); + + } + + const sourceDef = json.images[ sourceIndex ]; + + const URL = self.URL || self.webkitURL; + + let sourceURI = sourceDef.uri || ''; + let isObjectURL = false; + + if ( sourceDef.bufferView !== undefined ) { + + // Load binary image data from bufferView, if provided. + + sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) { + + isObjectURL = true; + const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } ); + sourceURI = URL.createObjectURL( blob ); + return sourceURI; + + } ); + + } else if ( sourceDef.uri === undefined ) { + + throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' ); + + } + + const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) { + + return new Promise( function ( resolve, reject ) { + + let onLoad = resolve; + + if ( loader.isImageBitmapLoader === true ) { + + onLoad = function ( imageBitmap ) { + + const texture = new Texture( imageBitmap ); + texture.needsUpdate = true; + + resolve( texture ); + + }; + + } + + loader.load( LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); + + } ); + + } ).then( function ( texture ) { + + // Clean up resources and configure Texture. + + if ( isObjectURL === true ) { + + URL.revokeObjectURL( sourceURI ); + + } + + texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); + + return texture; + + } ).catch( function ( error ) { + + console.error( 'THREE.GLTFLoader: Couldn\'t load texture', sourceURI ); + throw error; + + } ); + + this.sourceCache[ sourceIndex ] = promise; + return promise; + + } + + /** + * Asynchronously assigns a texture to the given material parameters. + * @param {Object} materialParams + * @param {string} mapName + * @param {Object} mapDef + * @return {Promise} + */ + assignTexture( materialParams, mapName, mapDef, encoding ) { + + const parser = this; + + return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { + + // Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured + // However, we will copy UV set 0 to UV set 1 on demand for aoMap + if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) { + + console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' ); + + } + + if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { + + const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; + + if ( transform ) { + + const gltfReference = parser.associations.get( texture ); + texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); + parser.associations.set( texture, gltfReference ); + + } + + } + + if ( encoding !== undefined ) { + + texture.encoding = encoding; + + } + + materialParams[ mapName ] = texture; + + return texture; + + } ); + + } + + /** + * Assigns final material to a Mesh, Line, or Points instance. The instance + * already has a material (generated from the glTF material options alone) + * but reuse of the same glTF material may require multiple threejs materials + * to accommodate different primitive types, defines, etc. New materials will + * be created if necessary, and reused from a cache. + * @param {Object3D} mesh Mesh, Line, or Points instance. + */ + assignFinalMaterial( mesh ) { + + const geometry = mesh.geometry; + let material = mesh.material; + + const useDerivativeTangents = geometry.attributes.tangent === undefined; + const useVertexColors = geometry.attributes.color !== undefined; + const useFlatShading = geometry.attributes.normal === undefined; + + if ( mesh.isPoints ) { + + const cacheKey = 'PointsMaterial:' + material.uuid; + + let pointsMaterial = this.cache.get( cacheKey ); + + if ( ! pointsMaterial ) { + + pointsMaterial = new PointsMaterial(); + Material.prototype.copy.call( pointsMaterial, material ); + pointsMaterial.color.copy( material.color ); + pointsMaterial.map = material.map; + pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px + + this.cache.add( cacheKey, pointsMaterial ); + + } + + material = pointsMaterial; + + } else if ( mesh.isLine ) { + + const cacheKey = 'LineBasicMaterial:' + material.uuid; + + let lineMaterial = this.cache.get( cacheKey ); + + if ( ! lineMaterial ) { + + lineMaterial = new LineBasicMaterial(); + Material.prototype.copy.call( lineMaterial, material ); + lineMaterial.color.copy( material.color ); + + this.cache.add( cacheKey, lineMaterial ); + + } + + material = lineMaterial; + + } + + // Clone the material if it will be modified + if ( useDerivativeTangents || useVertexColors || useFlatShading ) { + + let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; + + if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; + if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; + if ( useVertexColors ) cacheKey += 'vertex-colors:'; + if ( useFlatShading ) cacheKey += 'flat-shading:'; + + let cachedMaterial = this.cache.get( cacheKey ); + + if ( ! cachedMaterial ) { + + cachedMaterial = material.clone(); + + if ( useVertexColors ) cachedMaterial.vertexColors = true; + if ( useFlatShading ) cachedMaterial.flatShading = true; + + if ( useDerivativeTangents ) { + + // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; + if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; + + } + + this.cache.add( cacheKey, cachedMaterial ); + + this.associations.set( cachedMaterial, this.associations.get( material ) ); + + } + + material = cachedMaterial; + + } + + // workarounds for mesh and geometry + + if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { + + geometry.setAttribute( 'uv2', geometry.attributes.uv ); + + } + + mesh.material = material; + + } + + getMaterialType( /* materialIndex */ ) { + + return MeshStandardMaterial; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials + * @param {number} materialIndex + * @return {Promise} + */ + loadMaterial( materialIndex ) { + + const parser = this; + const json = this.json; + const extensions = this.extensions; + const materialDef = json.materials[ materialIndex ]; + + let materialType; + const materialParams = {}; + const materialExtensions = materialDef.extensions || {}; + + const pending = []; + + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { + + const sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; + materialType = sgExtension.getMaterialType(); + pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); + + } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { + + const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; + materialType = kmuExtension.getMaterialType(); + pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); + + } else { + + // Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material + + const metallicRoughness = materialDef.pbrMetallicRoughness || {}; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + const array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, sRGBEncoding ) ); + + } + + materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; + materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; + + if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) ); + pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) ); + + } + + materialType = this._invokeOne( function ( ext ) { + + return ext.getMaterialType && ext.getMaterialType( materialIndex ); + + } ); + + pending.push( Promise.all( this._invokeAll( function ( ext ) { + + return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); + + } ) ) ); + + } + + if ( materialDef.doubleSided === true ) { + + materialParams.side = DoubleSide; + + } + + const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + + if ( alphaMode === ALPHA_MODES.BLEND ) { + + materialParams.transparent = true; + + // See: https://github.com/mrdoob/three.js/issues/17706 + materialParams.depthWrite = false; + + } else { + + materialParams.transparent = false; + + if ( alphaMode === ALPHA_MODES.MASK ) { + + materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; + + } + + } + + if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); + + materialParams.normalScale = new Vector2( 1, 1 ); + + if ( materialDef.normalTexture.scale !== undefined ) { + + const scale = materialDef.normalTexture.scale; + + materialParams.normalScale.set( scale, scale ); + + } + + } + + if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); + + if ( materialDef.occlusionTexture.strength !== undefined ) { + + materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; + + } + + } + + if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { + + materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor ); + + } + + if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, sRGBEncoding ) ); + + } + + return Promise.all( pending ).then( function () { + + let material; + + if ( materialType === GLTFMeshStandardSGMaterial ) { + + material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); + + } else { + + material = new materialType( materialParams ); + + } + + if ( materialDef.name ) material.name = materialDef.name; + + assignExtrasToUserData( material, materialDef ); + + parser.associations.set( material, { materials: materialIndex } ); + + if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); + + return material; + + } ); + + } + + /** When Object3D instances are targeted by animation, they need unique names. */ + createUniqueName( originalName ) { + + const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); + + let name = sanitizedName; + + for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) { + + name = sanitizedName + '_' + i; + + } + + this.nodeNamesUsed[ name ] = true; + + return name; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry + * + * Creates BufferGeometries from primitives. + * + * @param {Array} primitives + * @return {Promise>} + */ + loadGeometries( primitives ) { + + const parser = this; + const extensions = this.extensions; + const cache = this.primitiveCache; + + function createDracoPrimitive( primitive ) { + + return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] + .decodePrimitive( primitive, parser ) + .then( function ( geometry ) { + + return addPrimitiveAttributes( geometry, primitive, parser ); + + } ); + + } + + const pending = []; + + for ( let i = 0, il = primitives.length; i < il; i ++ ) { + + const primitive = primitives[ i ]; + const cacheKey = createPrimitiveKey( primitive ); + + // See if we've already created this geometry + const cached = cache[ cacheKey ]; + + if ( cached ) { + + // Use the cached geometry if it exists + pending.push( cached.promise ); + + } else { + + let geometryPromise; + + if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { + + // Use DRACO geometry if available + geometryPromise = createDracoPrimitive( primitive ); + + } else { + + // Otherwise create a new geometry + geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser ); + + } + + // Cache this geometry + cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise }; + + pending.push( geometryPromise ); + + } + + } + + return Promise.all( pending ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes + * @param {number} meshIndex + * @return {Promise} + */ + loadMesh( meshIndex ) { + + const parser = this; + const json = this.json; + const extensions = this.extensions; + + const meshDef = json.meshes[ meshIndex ]; + const primitives = meshDef.primitives; + + const pending = []; + + for ( let i = 0, il = primitives.length; i < il; i ++ ) { + + const material = primitives[ i ].material === undefined + ? createDefaultMaterial( this.cache ) + : this.getDependency( 'material', primitives[ i ].material ); + + pending.push( material ); + + } + + pending.push( parser.loadGeometries( primitives ) ); + + return Promise.all( pending ).then( function ( results ) { + + const materials = results.slice( 0, results.length - 1 ); + const geometries = results[ results.length - 1 ]; + + const meshes = []; + + for ( let i = 0, il = geometries.length; i < il; i ++ ) { + + const geometry = geometries[ i ]; + const primitive = primitives[ i ]; + + // 1. create Mesh + + let mesh; + + const material = materials[ i ]; + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || + primitive.mode === undefined ) { + + // .isSkinnedMesh isn't in glTF spec. See ._markDefs() + mesh = meshDef.isSkinnedMesh === true + ? new SkinnedMesh( geometry, material ) + : new Mesh( geometry, material ); + + if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) { + + // we normalize floating point skin weight array to fix malformed assets (see #15319) + // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs + mesh.normalizeSkinWeights(); + + } + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { + + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { + + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode ); + + } + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { + + mesh = new LineSegments( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { + + mesh = new Line( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { + + mesh = new LineLoop( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { + + mesh = new Points( geometry, material ); + + } else { + + throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); + + } + + if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { + + updateMorphTargets( mesh, meshDef ); + + } + + mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); + + assignExtrasToUserData( mesh, meshDef ); + + if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); + + parser.assignFinalMaterial( mesh ); + + meshes.push( mesh ); + + } + + for ( let i = 0, il = meshes.length; i < il; i ++ ) { + + parser.associations.set( meshes[ i ], { + meshes: meshIndex, + primitives: i + } ); + + } + + if ( meshes.length === 1 ) { + + return meshes[ 0 ]; + + } + + const group = new Group(); + + parser.associations.set( group, { meshes: meshIndex } ); + + for ( let i = 0, il = meshes.length; i < il; i ++ ) { + + group.add( meshes[ i ] ); + + } + + return group; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras + * @param {number} cameraIndex + * @return {Promise} + */ + loadCamera( cameraIndex ) { + + let camera; + const cameraDef = this.json.cameras[ cameraIndex ]; + const params = cameraDef[ cameraDef.type ]; + + if ( ! params ) { + + console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); + return; + + } + + if ( cameraDef.type === 'perspective' ) { + + camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); + + } else if ( cameraDef.type === 'orthographic' ) { + + camera = new OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); + + } + + if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); + + assignExtrasToUserData( camera, cameraDef ); + + return Promise.resolve( camera ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins + * @param {number} skinIndex + * @return {Promise} + */ + loadSkin( skinIndex ) { + + const skinDef = this.json.skins[ skinIndex ]; + + const skinEntry = { joints: skinDef.joints }; + + if ( skinDef.inverseBindMatrices === undefined ) { + + return Promise.resolve( skinEntry ); + + } + + return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) { + + skinEntry.inverseBindMatrices = accessor; + + return skinEntry; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations + * @param {number} animationIndex + * @return {Promise} + */ + loadAnimation( animationIndex ) { + + const json = this.json; + + const animationDef = json.animations[ animationIndex ]; + + const pendingNodes = []; + const pendingInputAccessors = []; + const pendingOutputAccessors = []; + const pendingSamplers = []; + const pendingTargets = []; + + for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) { + + const channel = animationDef.channels[ i ]; + const sampler = animationDef.samplers[ channel.sampler ]; + const target = channel.target; + const name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. + const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; + const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; + + pendingNodes.push( this.getDependency( 'node', name ) ); + pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); + pendingOutputAccessors.push( this.getDependency( 'accessor', output ) ); + pendingSamplers.push( sampler ); + pendingTargets.push( target ); + + } + + return Promise.all( [ + + Promise.all( pendingNodes ), + Promise.all( pendingInputAccessors ), + Promise.all( pendingOutputAccessors ), + Promise.all( pendingSamplers ), + Promise.all( pendingTargets ) + + ] ).then( function ( dependencies ) { + + const nodes = dependencies[ 0 ]; + const inputAccessors = dependencies[ 1 ]; + const outputAccessors = dependencies[ 2 ]; + const samplers = dependencies[ 3 ]; + const targets = dependencies[ 4 ]; + + const tracks = []; + + for ( let i = 0, il = nodes.length; i < il; i ++ ) { + + const node = nodes[ i ]; + const inputAccessor = inputAccessors[ i ]; + const outputAccessor = outputAccessors[ i ]; + const sampler = samplers[ i ]; + const target = targets[ i ]; + + if ( node === undefined ) continue; + + node.updateMatrix(); + node.matrixAutoUpdate = true; + + let TypedKeyframeTrack; + + switch ( PATH_PROPERTIES[ target.path ] ) { + + case PATH_PROPERTIES.weights: + + TypedKeyframeTrack = NumberKeyframeTrack; + break; + + case PATH_PROPERTIES.rotation: + + TypedKeyframeTrack = QuaternionKeyframeTrack; + break; + + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: + default: + + TypedKeyframeTrack = VectorKeyframeTrack; + break; + + } + + const targetName = node.name ? node.name : node.uuid; + + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; + + const targetNames = []; + + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { + + node.traverse( function ( object ) { + + if ( object.morphTargetInfluences ) { + + targetNames.push( object.name ? object.name : object.uuid ); + + } + + } ); + + } else { + + targetNames.push( targetName ); + + } + + let outputArray = outputAccessor.array; + + if ( outputAccessor.normalized ) { + + const scale = getNormalizedComponentScale( outputArray.constructor ); + const scaled = new Float32Array( outputArray.length ); + + for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { + + scaled[ j ] = outputArray[ j ] * scale; + + } + + outputArray = scaled; + + } + + for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { + + const track = new TypedKeyframeTrack( + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], + inputAccessor.array, + outputArray, + interpolation + ); + + // Override interpolation with custom factory method. + if ( sampler.interpolation === 'CUBICSPLINE' ) { + + track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { + + // A CUBICSPLINE keyframe in glTF has three output values for each input value, + // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() + // must be divided by three to get the interpolant's sampleSize argument. + + const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; + + return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); + + }; + + // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. + track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; + + } + + tracks.push( track ); + + } + + } + + const name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; + + return new AnimationClip( name, undefined, tracks ); + + } ); + + } + + createNodeMesh( nodeIndex ) { + + const json = this.json; + const parser = this; + const nodeDef = json.nodes[ nodeIndex ]; + + if ( nodeDef.mesh === undefined ) return null; + + return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { + + const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); + + // if weights are provided on the node, override weights on the mesh. + if ( nodeDef.weights !== undefined ) { + + node.traverse( function ( o ) { + + if ( ! o.isMesh ) return; + + for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { + + o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; + + } + + } ); + + } + + return node; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy + * @param {number} nodeIndex + * @return {Promise} + */ + loadNode( nodeIndex ) { + + const json = this.json; + const extensions = this.extensions; + const parser = this; + + const nodeDef = json.nodes[ nodeIndex ]; + + // reserve node's name before its dependencies, so the root has the intended name. + const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; + + return ( function () { + + const pending = []; + + const meshPromise = parser._invokeOne( function ( ext ) { + + return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); + + } ); + + if ( meshPromise ) { + + pending.push( meshPromise ); + + } + + if ( nodeDef.camera !== undefined ) { + + pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { + + return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); + + } ) ); + + } + + parser._invokeAll( function ( ext ) { + + return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); + + } ).forEach( function ( promise ) { + + pending.push( promise ); + + } ); + + return Promise.all( pending ); + + }() ).then( function ( objects ) { + + let node; + + // .isBone isn't in glTF spec. See ._markDefs + if ( nodeDef.isBone === true ) { + + node = new Bone(); + + } else if ( objects.length > 1 ) { + + node = new Group(); + + } else if ( objects.length === 1 ) { + + node = objects[ 0 ]; + + } else { + + node = new Object3D(); + + } + + if ( node !== objects[ 0 ] ) { + + for ( let i = 0, il = objects.length; i < il; i ++ ) { + + node.add( objects[ i ] ); + + } + + } + + if ( nodeDef.name ) { + + node.userData.name = nodeDef.name; + node.name = nodeName; + + } + + assignExtrasToUserData( node, nodeDef ); + + if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); + + if ( nodeDef.matrix !== undefined ) { + + const matrix = new Matrix4(); + matrix.fromArray( nodeDef.matrix ); + node.applyMatrix4( matrix ); + + } else { + + if ( nodeDef.translation !== undefined ) { + + node.position.fromArray( nodeDef.translation ); + + } + + if ( nodeDef.rotation !== undefined ) { + + node.quaternion.fromArray( nodeDef.rotation ); + + } + + if ( nodeDef.scale !== undefined ) { + + node.scale.fromArray( nodeDef.scale ); + + } + + } + + if ( ! parser.associations.has( node ) ) { + + parser.associations.set( node, {} ); + + } + + parser.associations.get( node ).nodes = nodeIndex; + + return node; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes + * @param {number} sceneIndex + * @return {Promise} + */ + loadScene( sceneIndex ) { + + const json = this.json; + const extensions = this.extensions; + const sceneDef = this.json.scenes[ sceneIndex ]; + const parser = this; + + // Loader returns Group, not Scene. + // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 + const scene = new Group(); + if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); + + assignExtrasToUserData( scene, sceneDef ); + + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); + + const nodeIds = sceneDef.nodes || []; + + const pending = []; + + for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { + + pending.push( buildNodeHierarchy( nodeIds[ i ], scene, json, parser ) ); + + } + + return Promise.all( pending ).then( function () { + + // Removes dangling associations, associations that reference a node that + // didn't make it into the scene. + const reduceAssociations = ( node ) => { + + const reducedAssociations = new Map(); + + for ( const [ key, value ] of parser.associations ) { + + if ( key instanceof Material || key instanceof Texture ) { + + reducedAssociations.set( key, value ); + + } + + } + + node.traverse( ( node ) => { + + const mappings = parser.associations.get( node ); + + if ( mappings != null ) { + + reducedAssociations.set( node, mappings ); + + } + + } ); + + return reducedAssociations; + + }; + + parser.associations = reduceAssociations( scene ); + + return scene; + + } ); + + } + +} + +function buildNodeHierarchy( nodeId, parentObject, json, parser ) { + + const nodeDef = json.nodes[ nodeId ]; + + return parser.getDependency( 'node', nodeId ).then( function ( node ) { + + if ( nodeDef.skin === undefined ) return node; + + // build skeleton here as well + + let skinEntry; + + return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { + + skinEntry = skin; + + const pendingJoints = []; + + for ( let i = 0, il = skinEntry.joints.length; i < il; i ++ ) { + + pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); + + } + + return Promise.all( pendingJoints ); + + } ).then( function ( jointNodes ) { + + node.traverse( function ( mesh ) { + + if ( ! mesh.isMesh ) return; + + const bones = []; + const boneInverses = []; + + for ( let j = 0, jl = jointNodes.length; j < jl; j ++ ) { + + const jointNode = jointNodes[ j ]; + + if ( jointNode ) { + + bones.push( jointNode ); + + const mat = new Matrix4(); + + if ( skinEntry.inverseBindMatrices !== undefined ) { + + mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); + + } + + boneInverses.push( mat ); + + } else { + + console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); + + } + + } + + mesh.bind( new Skeleton( bones, boneInverses ), mesh.matrixWorld ); + + } ); + + return node; + + } ); + + } ).then( function ( node ) { + + // build node hierachy + + parentObject.add( node ); + + const pending = []; + + if ( nodeDef.children ) { + + const children = nodeDef.children; + + for ( let i = 0, il = children.length; i < il; i ++ ) { + + const child = children[ i ]; + pending.push( buildNodeHierarchy( child, node, json, parser ) ); + + } + + } + + return Promise.all( pending ); + + } ); + +} + +/** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + */ +function computeBounds( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + + const box = new Box3(); + + if ( attributes.POSITION !== undefined ) { + + const accessor = parser.json.accessors[ attributes.POSITION ]; + + const min = accessor.min; + const max = accessor.max; + + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + box.set( + new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), + new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) + ); + + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + box.min.multiplyScalar( boxScale ); + box.max.multiplyScalar( boxScale ); + + } + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + return; + + } + + } else { + + return; + + } + + const targets = primitiveDef.targets; + + if ( targets !== undefined ) { + + const maxDisplacement = new Vector3(); + const vector = new Vector3(); + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; + + if ( target.POSITION !== undefined ) { + + const accessor = parser.json.accessors[ target.POSITION ]; + const min = accessor.min; + const max = accessor.max; + + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + // we need to get max of absolute components because target weight is [-1,1] + vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); + vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); + vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); + + + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + vector.multiplyScalar( boxScale ); + + } + + // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative + // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets + // are used to implement key-frame animations and as such only two are active at a time - this results in very large + // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. + maxDisplacement.max( vector ); + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + } + + } + + } + + // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. + box.expandByVector( maxDisplacement ); + + } + + geometry.boundingBox = box; + + const sphere = new Sphere(); + + box.getCenter( sphere.center ); + sphere.radius = box.min.distanceTo( box.max ) / 2; + + geometry.boundingSphere = sphere; + +} + +/** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + * @return {Promise} + */ +function addPrimitiveAttributes( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + + const pending = []; + + function assignAttributeAccessor( accessorIndex, attributeName ) { + + return parser.getDependency( 'accessor', accessorIndex ) + .then( function ( accessor ) { + + geometry.setAttribute( attributeName, accessor ); + + } ); + + } + + for ( const gltfAttributeName in attributes ) { + + const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + + // Skip attributes already provided by e.g. Draco extension. + if ( threeAttributeName in geometry.attributes ) continue; + + pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); + + } + + if ( primitiveDef.indices !== undefined && ! geometry.index ) { + + const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { + + geometry.setIndex( accessor ); + + } ); + + pending.push( accessor ); + + } + + assignExtrasToUserData( geometry, primitiveDef ); + + computeBounds( geometry, primitiveDef, parser ); + + return Promise.all( pending ).then( function () { + + return primitiveDef.targets !== undefined + ? addMorphTargets( geometry, primitiveDef.targets, parser ) + : geometry; + + } ); + +} + +/** + * @param {BufferGeometry} geometry + * @param {Number} drawMode + * @return {BufferGeometry} + */ +function toTrianglesDrawMode( geometry, drawMode ) { + + let index = geometry.getIndex(); + + // generate index if not present + + if ( index === null ) { + + const indices = []; + + const position = geometry.getAttribute( 'position' ); + + if ( position !== undefined ) { + + for ( let i = 0; i < position.count; i ++ ) { + + indices.push( i ); + + } + + geometry.setIndex( indices ); + index = geometry.getIndex(); + + } else { + + console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' ); + return geometry; + + } + + } + + // + + const numberOfTriangles = index.count - 2; + const newIndices = []; + + if ( drawMode === TriangleFanDrawMode ) { + + // gl.TRIANGLE_FAN + + for ( let i = 1; i <= numberOfTriangles; i ++ ) { + + newIndices.push( index.getX( 0 ) ); + newIndices.push( index.getX( i ) ); + newIndices.push( index.getX( i + 1 ) ); + + } + + } else { + + // gl.TRIANGLE_STRIP + + for ( let i = 0; i < numberOfTriangles; i ++ ) { + + if ( i % 2 === 0 ) { + + newIndices.push( index.getX( i ) ); + newIndices.push( index.getX( i + 1 ) ); + newIndices.push( index.getX( i + 2 ) ); + + + } else { + + newIndices.push( index.getX( i + 2 ) ); + newIndices.push( index.getX( i + 1 ) ); + newIndices.push( index.getX( i ) ); + + } + + } + + } + + if ( ( newIndices.length / 3 ) !== numberOfTriangles ) { + + console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' ); + + } + + // build final geometry + + const newGeometry = geometry.clone(); + newGeometry.setIndex( newIndices ); + + return newGeometry; + +} + +export { GLTFLoader }; diff --git a/jsm/loaders/HDRCubeTextureLoader.js b/jsm/loaders/HDRCubeTextureLoader.js new file mode 100644 index 0000000..643f664 --- /dev/null +++ b/jsm/loaders/HDRCubeTextureLoader.js @@ -0,0 +1,128 @@ +import { + CubeTexture, + DataTexture, + FileLoader, + FloatType, + HalfFloatType, + LinearEncoding, + LinearFilter, + Loader +} from 'three'; +import { RGBELoader } from '../loaders/RGBELoader.js'; + +class HDRCubeTextureLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.hdrLoader = new RGBELoader(); + this.type = HalfFloatType; + + } + + load( urls, onLoad, onProgress, onError ) { + + if ( ! Array.isArray( urls ) ) { + + console.warn( 'THREE.HDRCubeTextureLoader signature has changed. Use .setDataType() instead.' ); + + this.setDataType( urls ); + + urls = onLoad; + onLoad = onProgress; + onProgress = onError; + onError = arguments[ 4 ]; + + } + + const texture = new CubeTexture(); + + texture.type = this.type; + + switch ( texture.type ) { + + case FloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + break; + + case HalfFloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + break; + + } + + const scope = this; + + let loaded = 0; + + function loadHDRData( i, onLoad, onProgress, onError ) { + + new FileLoader( scope.manager ) + .setPath( scope.path ) + .setResponseType( 'arraybuffer' ) + .setWithCredentials( scope.withCredentials ) + .load( urls[ i ], function ( buffer ) { + + loaded ++; + + const texData = scope.hdrLoader.parse( buffer ); + + if ( ! texData ) return; + + if ( texData.data !== undefined ) { + + const dataTexture = new DataTexture( texData.data, texData.width, texData.height ); + + dataTexture.type = texture.type; + dataTexture.encoding = texture.encoding; + dataTexture.format = texture.format; + dataTexture.minFilter = texture.minFilter; + dataTexture.magFilter = texture.magFilter; + dataTexture.generateMipmaps = texture.generateMipmaps; + + texture.images[ i ] = dataTexture; + + } + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + }, onProgress, onError ); + + } + + for ( let i = 0; i < urls.length; i ++ ) { + + loadHDRData( i, onLoad, onProgress, onError ); + + } + + return texture; + + } + + setDataType( value ) { + + this.type = value; + this.hdrLoader.setDataType( value ); + + return this; + + } + +} + +export { HDRCubeTextureLoader }; diff --git a/jsm/loaders/IFCLoader.js b/jsm/loaders/IFCLoader.js new file mode 100644 index 0000000..b79ac0b --- /dev/null +++ b/jsm/loaders/IFCLoader.js @@ -0,0 +1,2431 @@ +import { + IFCRELAGGREGATES, + IFCRELCONTAINEDINSPATIALSTRUCTURE, + IFCRELDEFINESBYPROPERTIES, + IFCRELASSOCIATESMATERIAL, + IFCRELDEFINESBYTYPE, + IFCPROJECT, + IfcAPI +} from './ifc/web-ifc-api.js'; +import { + BufferAttribute, + Mesh, + Matrix4, + BufferGeometry, + Color, + MeshLambertMaterial, + DoubleSide, + Loader, + FileLoader +} from 'three'; +import { mergeBufferGeometries } from '../utils/BufferGeometryUtils.js'; + +const IdAttrName = 'expressID'; +const merge = ( geoms, createGroups = false ) => { + + return mergeBufferGeometries( geoms, createGroups ); + +}; + +const newFloatAttr = ( data, size ) => { + + return new BufferAttribute( new Float32Array( data ), size ); + +}; + +const newIntAttr = ( data, size ) => { + + return new BufferAttribute( new Uint32Array( data ), size ); + +}; + +const DEFAULT = 'default'; +const PropsNames = { + aggregates: { + name: IFCRELAGGREGATES, + relating: 'RelatingObject', + related: 'RelatedObjects', + key: 'children' + }, + spatial: { + name: IFCRELCONTAINEDINSPATIALSTRUCTURE, + relating: 'RelatingStructure', + related: 'RelatedElements', + key: 'children' + }, + psets: { + name: IFCRELDEFINESBYPROPERTIES, + relating: 'RelatingPropertyDefinition', + related: 'RelatedObjects', + key: 'hasPsets' + }, + materials: { + name: IFCRELASSOCIATESMATERIAL, + relating: 'RelatingMaterial', + related: 'RelatedObjects', + key: 'hasMaterial' + }, + type: { + name: IFCRELDEFINESBYTYPE, + relating: 'RelatingType', + related: 'RelatedObjects', + key: 'hasType' + } +}; + +class IFCParser { + + constructor( state, BVH ) { + + this.state = state; + this.BVH = BVH; + this.loadedModels = 0; + this.currentWebIfcID = - 1; + this.currentModelID = - 1; + + } + + async parse( buffer ) { + + if ( this.state.api.wasmModule === undefined ) + await this.state.api.Init(); + this.newIfcModel( buffer ); + this.loadedModels ++; + return this.loadAllGeometry(); + + } + + newIfcModel( buffer ) { + + const data = new Uint8Array( buffer ); + this.currentWebIfcID = this.state.api.OpenModel( data, this.state.webIfcSettings ); + this.currentModelID = this.state.useJSON ? this.loadedModels : this.currentWebIfcID; + this.state.models[ this.currentModelID ] = { + modelID: this.currentModelID, + mesh: {}, + items: {}, + types: {}, + jsonData: {} + }; + + } + + loadAllGeometry() { + + this.saveAllPlacedGeometriesByMaterial(); + return this.generateAllGeometriesByMaterial(); + + } + + generateAllGeometriesByMaterial() { + + const { geometry, materials } = this.getGeometryAndMaterials(); + this.BVH.applyThreeMeshBVH( geometry ); + const mesh = new Mesh( geometry, materials ); + mesh.modelID = this.currentModelID; + this.state.models[ this.currentModelID ].mesh = mesh; + return mesh; + + } + + getGeometryAndMaterials() { + + const items = this.state.models[ this.currentModelID ].items; + const mergedByMaterial = []; + const materials = []; + for ( const materialID in items ) { + + materials.push( items[ materialID ].material ); + const geometries = Object.values( items[ materialID ].geometries ); + mergedByMaterial.push( merge( geometries ) ); + + } + + const geometry = merge( mergedByMaterial, true ); + return { + geometry, + materials + }; + + } + + saveAllPlacedGeometriesByMaterial() { + + const flatMeshes = this.state.api.LoadAllGeometry( this.currentWebIfcID ); + for ( let i = 0; i < flatMeshes.size(); i ++ ) { + + const flatMesh = flatMeshes.get( i ); + const placedGeom = flatMesh.geometries; + for ( let j = 0; j < placedGeom.size(); j ++ ) { + + this.savePlacedGeometry( placedGeom.get( j ), flatMesh.expressID ); + + } + + } + + } + + savePlacedGeometry( placedGeometry, id ) { + + const geometry = this.getBufferGeometry( placedGeometry ); + geometry.computeVertexNormals(); + const matrix = this.getMeshMatrix( placedGeometry.flatTransformation ); + geometry.applyMatrix4( matrix ); + this.saveGeometryByMaterial( geometry, placedGeometry, id ); + + } + + getBufferGeometry( placed ) { + + const geometry = this.state.api.GetGeometry( this.currentWebIfcID, placed.geometryExpressID ); + const vertexData = this.getVertices( geometry ); + const indices = this.getIndices( geometry ); + const { vertices, normals } = this.extractVertexData( vertexData ); + return this.ifcGeomToBufferGeom( vertices, normals, indices ); + + } + + getVertices( geometry ) { + + const vData = geometry.GetVertexData(); + const vDataSize = geometry.GetVertexDataSize(); + return this.state.api.GetVertexArray( vData, vDataSize ); + + } + + getIndices( geometry ) { + + const iData = geometry.GetIndexData(); + const iDataSize = geometry.GetIndexDataSize(); + return this.state.api.GetIndexArray( iData, iDataSize ); + + } + + getMeshMatrix( matrix ) { + + const mat = new Matrix4(); + mat.fromArray( matrix ); + return mat; + + } + + ifcGeomToBufferGeom( vertices, normals, indexData ) { + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', newFloatAttr( vertices, 3 ) ); + geometry.setAttribute( 'normal', newFloatAttr( normals, 3 ) ); + geometry.setIndex( new BufferAttribute( indexData, 1 ) ); + return geometry; + + } + + extractVertexData( vertexData ) { + + const vertices = []; + const normals = []; + let isNormalData = false; + for ( let i = 0; i < vertexData.length; i ++ ) { + + isNormalData ? normals.push( vertexData[ i ] ) : vertices.push( vertexData[ i ] ); + if ( ( i + 1 ) % 3 == 0 ) + isNormalData = ! isNormalData; + + } + + return { + vertices, + normals + }; + + } + + saveGeometryByMaterial( geom, placedGeom, id ) { + + const color = placedGeom.color; + const colorID = `${color.x}${color.y}${color.z}${color.w}`; + this.storeGeometryAttribute( id, geom ); + this.createMaterial( colorID, color ); + const item = this.state.models[ this.currentModelID ].items[ colorID ]; + const currentGeom = item.geometries[ id ]; + if ( ! currentGeom ) + return ( item.geometries[ id ] = geom ); + const merged = merge( [ currentGeom, geom ] ); + item.geometries[ id ] = merged; + + } + + storeGeometryAttribute( id, geometry ) { + + const size = geometry.attributes.position.count; + const idAttribute = new Array( size ).fill( id ); + geometry.setAttribute( IdAttrName, newIntAttr( idAttribute, 1 ) ); + + } + + createMaterial( colorID, color ) { + + const items = this.state.models[ this.currentModelID ].items; + if ( items[ colorID ] ) + return; + const col = new Color( color.x, color.y, color.z ); + const newMaterial = new MeshLambertMaterial( { + color: col, + side: DoubleSide + } ); + newMaterial.transparent = color.w !== 1; + if ( newMaterial.transparent ) + newMaterial.opacity = color.w; + items[ colorID ] = { + material: newMaterial, + geometries: {} + }; + + } + +} + +class SubsetManager { + + constructor( state, BVH ) { + + this.selected = {}; + this.state = state; + this.BVH = BVH; + + } + + getSubset( modelID, material ) { + + const currentMat = this.matIDNoConfig( modelID, material ); + if ( ! this.selected[ currentMat ] ) + return null; + return this.selected[ currentMat ].mesh; + + } + + removeSubset( modelID, parent, material ) { + + const currentMat = this.matIDNoConfig( modelID, material ); + if ( ! this.selected[ currentMat ] ) + return; + if ( parent ) + parent.remove( this.selected[ currentMat ].mesh ); + delete this.selected[ currentMat ]; + + } + + createSubset( config ) { + + if ( ! this.isConfigValid( config ) ) + return; + if ( this.isPreviousSelection( config ) ) + return; + if ( this.isEasySelection( config ) ) + return this.addToPreviousSelection( config ); + this.updatePreviousSelection( config.scene, config ); + return this.createSelectionInScene( config ); + + } + + createSelectionInScene( config ) { + + const filtered = this.filter( config ); + const { geomsByMaterial, materials } = this.getGeomAndMat( filtered ); + const isDefMaterial = this.isDefaultMat( config ); + const geometry = this.getMergedGeometry( geomsByMaterial, isDefMaterial ); + const mats = isDefMaterial ? materials : config.material; + this.BVH.applyThreeMeshBVH( geometry ); + const mesh = new Mesh( geometry, mats ); + this.selected[ this.matID( config ) ].mesh = mesh; + mesh.modelID = config.modelID; + config.scene.add( mesh ); + return mesh; + + } + + getMergedGeometry( geomsByMaterial, hasDefaultMaterial ) { + + return geomsByMaterial.length > 0 + ? merge( geomsByMaterial, hasDefaultMaterial ) + : new BufferGeometry(); + + } + + isConfigValid( config ) { + + return ( this.isValid( config.scene ) && + this.isValid( config.modelID ) && + this.isValid( config.ids ) && + this.isValid( config.removePrevious ) ); + + } + + isValid( item ) { + + return item != undefined && item != null; + + } + + getGeomAndMat( filtered ) { + + const geomsByMaterial = []; + const materials = []; + for ( const matID in filtered ) { + + const geoms = Object.values( filtered[ matID ].geometries ); + if ( ! geoms.length ) + continue; + materials.push( filtered[ matID ].material ); + if ( geoms.length > 1 ) + geomsByMaterial.push( merge( geoms ) ); + else + geomsByMaterial.push( ...geoms ); + + } + + return { + geomsByMaterial, + materials + }; + + } + + updatePreviousSelection( parent, config ) { + + const previous = this.selected[ this.matID( config ) ]; + if ( ! previous ) + return this.newSelectionGroup( config ); + parent.remove( previous.mesh ); + config.removePrevious + ? ( previous.ids = new Set( config.ids ) ) + : config.ids.forEach( ( id ) => previous.ids.add( id ) ); + + } + + newSelectionGroup( config ) { + + this.selected[ this.matID( config ) ] = { + ids: new Set( config.ids ), + mesh: {} + }; + + } + + isPreviousSelection( config ) { + + if ( ! this.selected[ this.matID( config ) ] ) + return false; + if ( this.containsIds( config ) ) + return true; + const previousIds = this.selected[ this.matID( config ) ].ids; + return JSON.stringify( config.ids ) === JSON.stringify( previousIds ); + + } + + containsIds( config ) { + + const newIds = config.ids; + const previous = Array.from( this.selected[ this.matID( config ) ].ids ); + return newIds.every( ( i => v => ( i = previous.indexOf( v, i ) + 1 ) )( 0 ) ); + + } + + addToPreviousSelection( config ) { + + const previous = this.selected[ this.matID( config ) ]; + const filtered = this.filter( config ); + const geometries = Object.values( filtered ).map( ( i ) => Object.values( i.geometries ) ).flat(); + const previousGeom = previous.mesh.geometry; + previous.mesh.geometry = merge( [ previousGeom, ...geometries ] ); + config.ids.forEach( ( id ) => previous.ids.add( id ) ); + + } + + filter( config ) { + + const ids = this.selected[ this.matID( config ) ].ids; + const items = this.state.models[ config.modelID ].items; + const filtered = {}; + for ( const matID in items ) { + + filtered[ matID ] = { + material: items[ matID ].material, + geometries: this.filterGeometries( ids, items[ matID ].geometries ) + }; + + } + + return filtered; + + } + + filterGeometries( selectedIDs, geometries ) { + + const ids = Array.from( selectedIDs ); + return Object.keys( geometries ) + .filter( ( key ) => ids.includes( parseInt( key, 10 ) ) ) + .reduce( ( obj, key ) => { + + return { + ...obj, + [ key ]: geometries[ key ] + }; + + }, {} ); + + } + + isEasySelection( config ) { + + const matID = this.matID( config ); + if ( ! config.removePrevious && ! this.isDefaultMat( config ) && this.selected[ matID ] ) + return true; + + } + + isDefaultMat( config ) { + + return this.matIDNoConfig( config.modelID ) === this.matID( config ); + + } + + matID( config ) { + + let name; + if ( ! config.material ) + name = DEFAULT; + else + name = config.material.uuid || DEFAULT; + return name.concat( ' - ' ).concat( config.modelID.toString() ); + + } + + matIDNoConfig( modelID, material ) { + + let name = DEFAULT; + if ( material ) + name = material.uuid; + return name.concat( ' - ' ).concat( modelID.toString() ); + + } + +} + +const IfcElements = { + 103090709: 'IFCPROJECT', + 4097777520: 'IFCSITE', + 4031249490: 'IFCBUILDING', + 3124254112: 'IFCBUILDINGSTOREY', + 3856911033: 'IFCSPACE', + 1674181508: 'IFCANNOTATION', + 25142252: 'IFCCONTROLLER', + 32344328: 'IFCBOILER', + 76236018: 'IFCLAMP', + 90941305: 'IFCPUMP', + 177149247: 'IFCAIRTERMINALBOX', + 182646315: 'IFCFLOWINSTRUMENT', + 263784265: 'IFCFURNISHINGELEMENT', + 264262732: 'IFCELECTRICGENERATOR', + 277319702: 'IFCAUDIOVISUALAPPLIANCE', + 310824031: 'IFCPIPEFITTING', + 331165859: 'IFCSTAIR', + 342316401: 'IFCDUCTFITTING', + 377706215: 'IFCMECHANICALFASTENER', + 395920057: 'IFCDOOR', + 402227799: 'IFCELECTRICMOTOR', + 413509423: 'IFCSYSTEMFURNITUREELEMENT', + 484807127: 'IFCEVAPORATOR', + 486154966: 'IFCWINDOWSTANDARDCASE', + 629592764: 'IFCLIGHTFIXTURE', + 630975310: 'IFCUNITARYCONTROLELEMENT', + 635142910: 'IFCCABLECARRIERFITTING', + 639361253: 'IFCCOIL', + 647756555: 'IFCFASTENER', + 707683696: 'IFCFLOWSTORAGEDEVICE', + 738039164: 'IFCPROTECTIVEDEVICE', + 753842376: 'IFCBEAM', + 812556717: 'IFCTANK', + 819412036: 'IFCFILTER', + 843113511: 'IFCCOLUMN', + 862014818: 'IFCELECTRICDISTRIBUTIONBOARD', + 900683007: 'IFCFOOTING', + 905975707: 'IFCCOLUMNSTANDARDCASE', + 926996030: 'IFCVOIDINGFEATURE', + 979691226: 'IFCREINFORCINGBAR', + 987401354: 'IFCFLOWSEGMENT', + 1003880860: 'IFCELECTRICTIMECONTROL', + 1051757585: 'IFCCABLEFITTING', + 1052013943: 'IFCDISTRIBUTIONCHAMBERELEMENT', + 1062813311: 'IFCDISTRIBUTIONCONTROLELEMENT', + 1073191201: 'IFCMEMBER', + 1095909175: 'IFCBUILDINGELEMENTPROXY', + 1156407060: 'IFCPLATESTANDARDCASE', + 1162798199: 'IFCSWITCHINGDEVICE', + 1329646415: 'IFCSHADINGDEVICE', + 1335981549: 'IFCDISCRETEACCESSORY', + 1360408905: 'IFCDUCTSILENCER', + 1404847402: 'IFCSTACKTERMINAL', + 1426591983: 'IFCFIRESUPPRESSIONTERMINAL', + 1437502449: 'IFCMEDICALDEVICE', + 1509553395: 'IFCFURNITURE', + 1529196076: 'IFCSLAB', + 1620046519: 'IFCTRANSPORTELEMENT', + 1634111441: 'IFCAIRTERMINAL', + 1658829314: 'IFCENERGYCONVERSIONDEVICE', + 1677625105: 'IFCCIVILELEMENT', + 1687234759: 'IFCPILE', + 1904799276: 'IFCELECTRICAPPLIANCE', + 1911478936: 'IFCMEMBERSTANDARDCASE', + 1945004755: 'IFCDISTRIBUTIONELEMENT', + 1973544240: 'IFCCOVERING', + 1999602285: 'IFCSPACEHEATER', + 2016517767: 'IFCROOF', + 2056796094: 'IFCAIRTOAIRHEATRECOVERY', + 2058353004: 'IFCFLOWCONTROLLER', + 2068733104: 'IFCHUMIDIFIER', + 2176052936: 'IFCJUNCTIONBOX', + 2188021234: 'IFCFLOWMETER', + 2223149337: 'IFCFLOWTERMINAL', + 2262370178: 'IFCRAILING', + 2272882330: 'IFCCONDENSER', + 2295281155: 'IFCPROTECTIVEDEVICETRIPPINGUNIT', + 2320036040: 'IFCREINFORCINGMESH', + 2347447852: 'IFCTENDONANCHOR', + 2391383451: 'IFCVIBRATIONISOLATOR', + 2391406946: 'IFCWALL', + 2474470126: 'IFCMOTORCONNECTION', + 2769231204: 'IFCVIRTUALELEMENT', + 2814081492: 'IFCENGINE', + 2906023776: 'IFCBEAMSTANDARDCASE', + 2938176219: 'IFCBURNER', + 2979338954: 'IFCBUILDINGELEMENTPART', + 3024970846: 'IFCRAMP', + 3026737570: 'IFCTUBEBUNDLE', + 3027962421: 'IFCSLABSTANDARDCASE', + 3040386961: 'IFCDISTRIBUTIONFLOWELEMENT', + 3053780830: 'IFCSANITARYTERMINAL', + 3079942009: 'IFCOPENINGSTANDARDCASE', + 3087945054: 'IFCALARM', + 3101698114: 'IFCSURFACEFEATURE', + 3127900445: 'IFCSLABELEMENTEDCASE', + 3132237377: 'IFCFLOWMOVINGDEVICE', + 3171933400: 'IFCPLATE', + 3221913625: 'IFCCOMMUNICATIONSAPPLIANCE', + 3242481149: 'IFCDOORSTANDARDCASE', + 3283111854: 'IFCRAMPFLIGHT', + 3296154744: 'IFCCHIMNEY', + 3304561284: 'IFCWINDOW', + 3310460725: 'IFCELECTRICFLOWSTORAGEDEVICE', + 3319311131: 'IFCHEATEXCHANGER', + 3415622556: 'IFCFAN', + 3420628829: 'IFCSOLARDEVICE', + 3493046030: 'IFCGEOGRAPHICELEMENT', + 3495092785: 'IFCCURTAINWALL', + 3508470533: 'IFCFLOWTREATMENTDEVICE', + 3512223829: 'IFCWALLSTANDARDCASE', + 3518393246: 'IFCDUCTSEGMENT', + 3571504051: 'IFCCOMPRESSOR', + 3588315303: 'IFCOPENINGELEMENT', + 3612865200: 'IFCPIPESEGMENT', + 3640358203: 'IFCCOOLINGTOWER', + 3651124850: 'IFCPROJECTIONELEMENT', + 3694346114: 'IFCOUTLET', + 3747195512: 'IFCEVAPORATIVECOOLER', + 3758799889: 'IFCCABLECARRIERSEGMENT', + 3824725483: 'IFCTENDON', + 3825984169: 'IFCTRANSFORMER', + 3902619387: 'IFCCHILLER', + 4074379575: 'IFCDAMPER', + 4086658281: 'IFCSENSOR', + 4123344466: 'IFCELEMENTASSEMBLY', + 4136498852: 'IFCCOOLEDBEAM', + 4156078855: 'IFCWALLELEMENTEDCASE', + 4175244083: 'IFCINTERCEPTOR', + 4207607924: 'IFCVALVE', + 4217484030: 'IFCCABLESEGMENT', + 4237592921: 'IFCWASTETERMINAL', + 4252922144: 'IFCSTAIRFLIGHT', + 4278956645: 'IFCFLOWFITTING', + 4288193352: 'IFCACTUATOR', + 4292641817: 'IFCUNITARYEQUIPMENT', + 3009204131: 'IFCGRID' +}; + +const IfcTypesMap = { + 3821786052: 'IFCACTIONREQUEST', + 2296667514: 'IFCACTOR', + 3630933823: 'IFCACTORROLE', + 4288193352: 'IFCACTUATOR', + 2874132201: 'IFCACTUATORTYPE', + 618182010: 'IFCADDRESS', + 1635779807: 'IFCADVANCEDBREP', + 2603310189: 'IFCADVANCEDBREPWITHVOIDS', + 3406155212: 'IFCADVANCEDFACE', + 1634111441: 'IFCAIRTERMINAL', + 177149247: 'IFCAIRTERMINALBOX', + 1411407467: 'IFCAIRTERMINALBOXTYPE', + 3352864051: 'IFCAIRTERMINALTYPE', + 2056796094: 'IFCAIRTOAIRHEATRECOVERY', + 1871374353: 'IFCAIRTOAIRHEATRECOVERYTYPE', + 3087945054: 'IFCALARM', + 3001207471: 'IFCALARMTYPE', + 325726236: 'IFCALIGNMENT', + 749761778: 'IFCALIGNMENT2DHORIZONTAL', + 3199563722: 'IFCALIGNMENT2DHORIZONTALSEGMENT', + 2483840362: 'IFCALIGNMENT2DSEGMENT', + 3379348081: 'IFCALIGNMENT2DVERSEGCIRCULARARC', + 3239324667: 'IFCALIGNMENT2DVERSEGLINE', + 4263986512: 'IFCALIGNMENT2DVERSEGPARABOLICARC', + 53199957: 'IFCALIGNMENT2DVERTICAL', + 2029264950: 'IFCALIGNMENT2DVERTICALSEGMENT', + 3512275521: 'IFCALIGNMENTCURVE', + 1674181508: 'IFCANNOTATION', + 669184980: 'IFCANNOTATIONFILLAREA', + 639542469: 'IFCAPPLICATION', + 411424972: 'IFCAPPLIEDVALUE', + 130549933: 'IFCAPPROVAL', + 3869604511: 'IFCAPPROVALRELATIONSHIP', + 3798115385: 'IFCARBITRARYCLOSEDPROFILEDEF', + 1310608509: 'IFCARBITRARYOPENPROFILEDEF', + 2705031697: 'IFCARBITRARYPROFILEDEFWITHVOIDS', + 3460190687: 'IFCASSET', + 3207858831: 'IFCASYMMETRICISHAPEPROFILEDEF', + 277319702: 'IFCAUDIOVISUALAPPLIANCE', + 1532957894: 'IFCAUDIOVISUALAPPLIANCETYPE', + 4261334040: 'IFCAXIS1PLACEMENT', + 3125803723: 'IFCAXIS2PLACEMENT2D', + 2740243338: 'IFCAXIS2PLACEMENT3D', + 1967976161: 'IFCBSPLINECURVE', + 2461110595: 'IFCBSPLINECURVEWITHKNOTS', + 2887950389: 'IFCBSPLINESURFACE', + 167062518: 'IFCBSPLINESURFACEWITHKNOTS', + 753842376: 'IFCBEAM', + 2906023776: 'IFCBEAMSTANDARDCASE', + 819618141: 'IFCBEAMTYPE', + 4196446775: 'IFCBEARING', + 3649138523: 'IFCBEARINGTYPE', + 616511568: 'IFCBLOBTEXTURE', + 1334484129: 'IFCBLOCK', + 32344328: 'IFCBOILER', + 231477066: 'IFCBOILERTYPE', + 3649129432: 'IFCBOOLEANCLIPPINGRESULT', + 2736907675: 'IFCBOOLEANRESULT', + 4037036970: 'IFCBOUNDARYCONDITION', + 1136057603: 'IFCBOUNDARYCURVE', + 1560379544: 'IFCBOUNDARYEDGECONDITION', + 3367102660: 'IFCBOUNDARYFACECONDITION', + 1387855156: 'IFCBOUNDARYNODECONDITION', + 2069777674: 'IFCBOUNDARYNODECONDITIONWARPING', + 1260505505: 'IFCBOUNDEDCURVE', + 4182860854: 'IFCBOUNDEDSURFACE', + 2581212453: 'IFCBOUNDINGBOX', + 2713105998: 'IFCBOXEDHALFSPACE', + 644574406: 'IFCBRIDGE', + 963979645: 'IFCBRIDGEPART', + 4031249490: 'IFCBUILDING', + 3299480353: 'IFCBUILDINGELEMENT', + 2979338954: 'IFCBUILDINGELEMENTPART', + 39481116: 'IFCBUILDINGELEMENTPARTTYPE', + 1095909175: 'IFCBUILDINGELEMENTPROXY', + 1909888760: 'IFCBUILDINGELEMENTPROXYTYPE', + 1950629157: 'IFCBUILDINGELEMENTTYPE', + 3124254112: 'IFCBUILDINGSTOREY', + 1177604601: 'IFCBUILDINGSYSTEM', + 2938176219: 'IFCBURNER', + 2188180465: 'IFCBURNERTYPE', + 2898889636: 'IFCCSHAPEPROFILEDEF', + 635142910: 'IFCCABLECARRIERFITTING', + 395041908: 'IFCCABLECARRIERFITTINGTYPE', + 3758799889: 'IFCCABLECARRIERSEGMENT', + 3293546465: 'IFCCABLECARRIERSEGMENTTYPE', + 1051757585: 'IFCCABLEFITTING', + 2674252688: 'IFCCABLEFITTINGTYPE', + 4217484030: 'IFCCABLESEGMENT', + 1285652485: 'IFCCABLESEGMENTTYPE', + 3999819293: 'IFCCAISSONFOUNDATION', + 3203706013: 'IFCCAISSONFOUNDATIONTYPE', + 1123145078: 'IFCCARTESIANPOINT', + 574549367: 'IFCCARTESIANPOINTLIST', + 1675464909: 'IFCCARTESIANPOINTLIST2D', + 2059837836: 'IFCCARTESIANPOINTLIST3D', + 59481748: 'IFCCARTESIANTRANSFORMATIONOPERATOR', + 3749851601: 'IFCCARTESIANTRANSFORMATIONOPERATOR2D', + 3486308946: 'IFCCARTESIANTRANSFORMATIONOPERATOR2DNONUNIFORM', + 3331915920: 'IFCCARTESIANTRANSFORMATIONOPERATOR3D', + 1416205885: 'IFCCARTESIANTRANSFORMATIONOPERATOR3DNONUNIFORM', + 3150382593: 'IFCCENTERLINEPROFILEDEF', + 3902619387: 'IFCCHILLER', + 2951183804: 'IFCCHILLERTYPE', + 3296154744: 'IFCCHIMNEY', + 2197970202: 'IFCCHIMNEYTYPE', + 2611217952: 'IFCCIRCLE', + 2937912522: 'IFCCIRCLEHOLLOWPROFILEDEF', + 1383045692: 'IFCCIRCLEPROFILEDEF', + 1062206242: 'IFCCIRCULARARCSEGMENT2D', + 1677625105: 'IFCCIVILELEMENT', + 3893394355: 'IFCCIVILELEMENTTYPE', + 747523909: 'IFCCLASSIFICATION', + 647927063: 'IFCCLASSIFICATIONREFERENCE', + 2205249479: 'IFCCLOSEDSHELL', + 639361253: 'IFCCOIL', + 2301859152: 'IFCCOILTYPE', + 776857604: 'IFCCOLOURRGB', + 3285139300: 'IFCCOLOURRGBLIST', + 3264961684: 'IFCCOLOURSPECIFICATION', + 843113511: 'IFCCOLUMN', + 905975707: 'IFCCOLUMNSTANDARDCASE', + 300633059: 'IFCCOLUMNTYPE', + 3221913625: 'IFCCOMMUNICATIONSAPPLIANCE', + 400855858: 'IFCCOMMUNICATIONSAPPLIANCETYPE', + 2542286263: 'IFCCOMPLEXPROPERTY', + 3875453745: 'IFCCOMPLEXPROPERTYTEMPLATE', + 3732776249: 'IFCCOMPOSITECURVE', + 15328376: 'IFCCOMPOSITECURVEONSURFACE', + 2485617015: 'IFCCOMPOSITECURVESEGMENT', + 1485152156: 'IFCCOMPOSITEPROFILEDEF', + 3571504051: 'IFCCOMPRESSOR', + 3850581409: 'IFCCOMPRESSORTYPE', + 2272882330: 'IFCCONDENSER', + 2816379211: 'IFCCONDENSERTYPE', + 2510884976: 'IFCCONIC', + 370225590: 'IFCCONNECTEDFACESET', + 1981873012: 'IFCCONNECTIONCURVEGEOMETRY', + 2859738748: 'IFCCONNECTIONGEOMETRY', + 45288368: 'IFCCONNECTIONPOINTECCENTRICITY', + 2614616156: 'IFCCONNECTIONPOINTGEOMETRY', + 2732653382: 'IFCCONNECTIONSURFACEGEOMETRY', + 775493141: 'IFCCONNECTIONVOLUMEGEOMETRY', + 1959218052: 'IFCCONSTRAINT', + 3898045240: 'IFCCONSTRUCTIONEQUIPMENTRESOURCE', + 2185764099: 'IFCCONSTRUCTIONEQUIPMENTRESOURCETYPE', + 1060000209: 'IFCCONSTRUCTIONMATERIALRESOURCE', + 4105962743: 'IFCCONSTRUCTIONMATERIALRESOURCETYPE', + 488727124: 'IFCCONSTRUCTIONPRODUCTRESOURCE', + 1525564444: 'IFCCONSTRUCTIONPRODUCTRESOURCETYPE', + 2559216714: 'IFCCONSTRUCTIONRESOURCE', + 2574617495: 'IFCCONSTRUCTIONRESOURCETYPE', + 3419103109: 'IFCCONTEXT', + 3050246964: 'IFCCONTEXTDEPENDENTUNIT', + 3293443760: 'IFCCONTROL', + 25142252: 'IFCCONTROLLER', + 578613899: 'IFCCONTROLLERTYPE', + 2889183280: 'IFCCONVERSIONBASEDUNIT', + 2713554722: 'IFCCONVERSIONBASEDUNITWITHOFFSET', + 4136498852: 'IFCCOOLEDBEAM', + 335055490: 'IFCCOOLEDBEAMTYPE', + 3640358203: 'IFCCOOLINGTOWER', + 2954562838: 'IFCCOOLINGTOWERTYPE', + 1785450214: 'IFCCOORDINATEOPERATION', + 1466758467: 'IFCCOORDINATEREFERENCESYSTEM', + 3895139033: 'IFCCOSTITEM', + 1419761937: 'IFCCOSTSCHEDULE', + 602808272: 'IFCCOSTVALUE', + 1973544240: 'IFCCOVERING', + 1916426348: 'IFCCOVERINGTYPE', + 3295246426: 'IFCCREWRESOURCE', + 1815067380: 'IFCCREWRESOURCETYPE', + 2506170314: 'IFCCSGPRIMITIVE3D', + 2147822146: 'IFCCSGSOLID', + 539742890: 'IFCCURRENCYRELATIONSHIP', + 3495092785: 'IFCCURTAINWALL', + 1457835157: 'IFCCURTAINWALLTYPE', + 2601014836: 'IFCCURVE', + 2827736869: 'IFCCURVEBOUNDEDPLANE', + 2629017746: 'IFCCURVEBOUNDEDSURFACE', + 1186437898: 'IFCCURVESEGMENT2D', + 3800577675: 'IFCCURVESTYLE', + 1105321065: 'IFCCURVESTYLEFONT', + 2367409068: 'IFCCURVESTYLEFONTANDSCALING', + 3510044353: 'IFCCURVESTYLEFONTPATTERN', + 1213902940: 'IFCCYLINDRICALSURFACE', + 4074379575: 'IFCDAMPER', + 3961806047: 'IFCDAMPERTYPE', + 3426335179: 'IFCDEEPFOUNDATION', + 1306400036: 'IFCDEEPFOUNDATIONTYPE', + 3632507154: 'IFCDERIVEDPROFILEDEF', + 1765591967: 'IFCDERIVEDUNIT', + 1045800335: 'IFCDERIVEDUNITELEMENT', + 2949456006: 'IFCDIMENSIONALEXPONENTS', + 32440307: 'IFCDIRECTION', + 1335981549: 'IFCDISCRETEACCESSORY', + 2635815018: 'IFCDISCRETEACCESSORYTYPE', + 1945343521: 'IFCDISTANCEEXPRESSION', + 1052013943: 'IFCDISTRIBUTIONCHAMBERELEMENT', + 1599208980: 'IFCDISTRIBUTIONCHAMBERELEMENTTYPE', + 562808652: 'IFCDISTRIBUTIONCIRCUIT', + 1062813311: 'IFCDISTRIBUTIONCONTROLELEMENT', + 2063403501: 'IFCDISTRIBUTIONCONTROLELEMENTTYPE', + 1945004755: 'IFCDISTRIBUTIONELEMENT', + 3256556792: 'IFCDISTRIBUTIONELEMENTTYPE', + 3040386961: 'IFCDISTRIBUTIONFLOWELEMENT', + 3849074793: 'IFCDISTRIBUTIONFLOWELEMENTTYPE', + 3041715199: 'IFCDISTRIBUTIONPORT', + 3205830791: 'IFCDISTRIBUTIONSYSTEM', + 1154170062: 'IFCDOCUMENTINFORMATION', + 770865208: 'IFCDOCUMENTINFORMATIONRELATIONSHIP', + 3732053477: 'IFCDOCUMENTREFERENCE', + 395920057: 'IFCDOOR', + 2963535650: 'IFCDOORLININGPROPERTIES', + 1714330368: 'IFCDOORPANELPROPERTIES', + 3242481149: 'IFCDOORSTANDARDCASE', + 526551008: 'IFCDOORSTYLE', + 2323601079: 'IFCDOORTYPE', + 445594917: 'IFCDRAUGHTINGPREDEFINEDCOLOUR', + 4006246654: 'IFCDRAUGHTINGPREDEFINEDCURVEFONT', + 342316401: 'IFCDUCTFITTING', + 869906466: 'IFCDUCTFITTINGTYPE', + 3518393246: 'IFCDUCTSEGMENT', + 3760055223: 'IFCDUCTSEGMENTTYPE', + 1360408905: 'IFCDUCTSILENCER', + 2030761528: 'IFCDUCTSILENCERTYPE', + 3900360178: 'IFCEDGE', + 476780140: 'IFCEDGECURVE', + 1472233963: 'IFCEDGELOOP', + 1904799276: 'IFCELECTRICAPPLIANCE', + 663422040: 'IFCELECTRICAPPLIANCETYPE', + 862014818: 'IFCELECTRICDISTRIBUTIONBOARD', + 2417008758: 'IFCELECTRICDISTRIBUTIONBOARDTYPE', + 3310460725: 'IFCELECTRICFLOWSTORAGEDEVICE', + 3277789161: 'IFCELECTRICFLOWSTORAGEDEVICETYPE', + 264262732: 'IFCELECTRICGENERATOR', + 1534661035: 'IFCELECTRICGENERATORTYPE', + 402227799: 'IFCELECTRICMOTOR', + 1217240411: 'IFCELECTRICMOTORTYPE', + 1003880860: 'IFCELECTRICTIMECONTROL', + 712377611: 'IFCELECTRICTIMECONTROLTYPE', + 1758889154: 'IFCELEMENT', + 4123344466: 'IFCELEMENTASSEMBLY', + 2397081782: 'IFCELEMENTASSEMBLYTYPE', + 1623761950: 'IFCELEMENTCOMPONENT', + 2590856083: 'IFCELEMENTCOMPONENTTYPE', + 1883228015: 'IFCELEMENTQUANTITY', + 339256511: 'IFCELEMENTTYPE', + 2777663545: 'IFCELEMENTARYSURFACE', + 1704287377: 'IFCELLIPSE', + 2835456948: 'IFCELLIPSEPROFILEDEF', + 1658829314: 'IFCENERGYCONVERSIONDEVICE', + 2107101300: 'IFCENERGYCONVERSIONDEVICETYPE', + 2814081492: 'IFCENGINE', + 132023988: 'IFCENGINETYPE', + 3747195512: 'IFCEVAPORATIVECOOLER', + 3174744832: 'IFCEVAPORATIVECOOLERTYPE', + 484807127: 'IFCEVAPORATOR', + 3390157468: 'IFCEVAPORATORTYPE', + 4148101412: 'IFCEVENT', + 211053100: 'IFCEVENTTIME', + 4024345920: 'IFCEVENTTYPE', + 297599258: 'IFCEXTENDEDPROPERTIES', + 4294318154: 'IFCEXTERNALINFORMATION', + 3200245327: 'IFCEXTERNALREFERENCE', + 1437805879: 'IFCEXTERNALREFERENCERELATIONSHIP', + 1209101575: 'IFCEXTERNALSPATIALELEMENT', + 2853485674: 'IFCEXTERNALSPATIALSTRUCTUREELEMENT', + 2242383968: 'IFCEXTERNALLYDEFINEDHATCHSTYLE', + 1040185647: 'IFCEXTERNALLYDEFINEDSURFACESTYLE', + 3548104201: 'IFCEXTERNALLYDEFINEDTEXTFONT', + 477187591: 'IFCEXTRUDEDAREASOLID', + 2804161546: 'IFCEXTRUDEDAREASOLIDTAPERED', + 2556980723: 'IFCFACE', + 2047409740: 'IFCFACEBASEDSURFACEMODEL', + 1809719519: 'IFCFACEBOUND', + 803316827: 'IFCFACEOUTERBOUND', + 3008276851: 'IFCFACESURFACE', + 807026263: 'IFCFACETEDBREP', + 3737207727: 'IFCFACETEDBREPWITHVOIDS', + 24185140: 'IFCFACILITY', + 1310830890: 'IFCFACILITYPART', + 4219587988: 'IFCFAILURECONNECTIONCONDITION', + 3415622556: 'IFCFAN', + 346874300: 'IFCFANTYPE', + 647756555: 'IFCFASTENER', + 2489546625: 'IFCFASTENERTYPE', + 2827207264: 'IFCFEATUREELEMENT', + 2143335405: 'IFCFEATUREELEMENTADDITION', + 1287392070: 'IFCFEATUREELEMENTSUBTRACTION', + 738692330: 'IFCFILLAREASTYLE', + 374418227: 'IFCFILLAREASTYLEHATCHING', + 315944413: 'IFCFILLAREASTYLETILES', + 819412036: 'IFCFILTER', + 1810631287: 'IFCFILTERTYPE', + 1426591983: 'IFCFIRESUPPRESSIONTERMINAL', + 4222183408: 'IFCFIRESUPPRESSIONTERMINALTYPE', + 2652556860: 'IFCFIXEDREFERENCESWEPTAREASOLID', + 2058353004: 'IFCFLOWCONTROLLER', + 3907093117: 'IFCFLOWCONTROLLERTYPE', + 4278956645: 'IFCFLOWFITTING', + 3198132628: 'IFCFLOWFITTINGTYPE', + 182646315: 'IFCFLOWINSTRUMENT', + 4037862832: 'IFCFLOWINSTRUMENTTYPE', + 2188021234: 'IFCFLOWMETER', + 3815607619: 'IFCFLOWMETERTYPE', + 3132237377: 'IFCFLOWMOVINGDEVICE', + 1482959167: 'IFCFLOWMOVINGDEVICETYPE', + 987401354: 'IFCFLOWSEGMENT', + 1834744321: 'IFCFLOWSEGMENTTYPE', + 707683696: 'IFCFLOWSTORAGEDEVICE', + 1339347760: 'IFCFLOWSTORAGEDEVICETYPE', + 2223149337: 'IFCFLOWTERMINAL', + 2297155007: 'IFCFLOWTERMINALTYPE', + 3508470533: 'IFCFLOWTREATMENTDEVICE', + 3009222698: 'IFCFLOWTREATMENTDEVICETYPE', + 900683007: 'IFCFOOTING', + 1893162501: 'IFCFOOTINGTYPE', + 263784265: 'IFCFURNISHINGELEMENT', + 4238390223: 'IFCFURNISHINGELEMENTTYPE', + 1509553395: 'IFCFURNITURE', + 1268542332: 'IFCFURNITURETYPE', + 3493046030: 'IFCGEOGRAPHICELEMENT', + 4095422895: 'IFCGEOGRAPHICELEMENTTYPE', + 987898635: 'IFCGEOMETRICCURVESET', + 3448662350: 'IFCGEOMETRICREPRESENTATIONCONTEXT', + 2453401579: 'IFCGEOMETRICREPRESENTATIONITEM', + 4142052618: 'IFCGEOMETRICREPRESENTATIONSUBCONTEXT', + 3590301190: 'IFCGEOMETRICSET', + 3009204131: 'IFCGRID', + 852622518: 'IFCGRIDAXIS', + 178086475: 'IFCGRIDPLACEMENT', + 2706460486: 'IFCGROUP', + 812098782: 'IFCHALFSPACESOLID', + 3319311131: 'IFCHEATEXCHANGER', + 1251058090: 'IFCHEATEXCHANGERTYPE', + 2068733104: 'IFCHUMIDIFIER', + 1806887404: 'IFCHUMIDIFIERTYPE', + 1484403080: 'IFCISHAPEPROFILEDEF', + 3905492369: 'IFCIMAGETEXTURE', + 3570813810: 'IFCINDEXEDCOLOURMAP', + 2571569899: 'IFCINDEXEDPOLYCURVE', + 178912537: 'IFCINDEXEDPOLYGONALFACE', + 2294589976: 'IFCINDEXEDPOLYGONALFACEWITHVOIDS', + 1437953363: 'IFCINDEXEDTEXTUREMAP', + 2133299955: 'IFCINDEXEDTRIANGLETEXTUREMAP', + 4175244083: 'IFCINTERCEPTOR', + 3946677679: 'IFCINTERCEPTORTYPE', + 3113134337: 'IFCINTERSECTIONCURVE', + 2391368822: 'IFCINVENTORY', + 3741457305: 'IFCIRREGULARTIMESERIES', + 3020489413: 'IFCIRREGULARTIMESERIESVALUE', + 2176052936: 'IFCJUNCTIONBOX', + 4288270099: 'IFCJUNCTIONBOXTYPE', + 572779678: 'IFCLSHAPEPROFILEDEF', + 3827777499: 'IFCLABORRESOURCE', + 428585644: 'IFCLABORRESOURCETYPE', + 1585845231: 'IFCLAGTIME', + 76236018: 'IFCLAMP', + 1051575348: 'IFCLAMPTYPE', + 2655187982: 'IFCLIBRARYINFORMATION', + 3452421091: 'IFCLIBRARYREFERENCE', + 4162380809: 'IFCLIGHTDISTRIBUTIONDATA', + 629592764: 'IFCLIGHTFIXTURE', + 1161773419: 'IFCLIGHTFIXTURETYPE', + 1566485204: 'IFCLIGHTINTENSITYDISTRIBUTION', + 1402838566: 'IFCLIGHTSOURCE', + 125510826: 'IFCLIGHTSOURCEAMBIENT', + 2604431987: 'IFCLIGHTSOURCEDIRECTIONAL', + 4266656042: 'IFCLIGHTSOURCEGONIOMETRIC', + 1520743889: 'IFCLIGHTSOURCEPOSITIONAL', + 3422422726: 'IFCLIGHTSOURCESPOT', + 1281925730: 'IFCLINE', + 3092502836: 'IFCLINESEGMENT2D', + 388784114: 'IFCLINEARPLACEMENT', + 1154579445: 'IFCLINEARPOSITIONINGELEMENT', + 2624227202: 'IFCLOCALPLACEMENT', + 1008929658: 'IFCLOOP', + 1425443689: 'IFCMANIFOLDSOLIDBREP', + 3057273783: 'IFCMAPCONVERSION', + 2347385850: 'IFCMAPPEDITEM', + 1838606355: 'IFCMATERIAL', + 1847130766: 'IFCMATERIALCLASSIFICATIONRELATIONSHIP', + 3708119000: 'IFCMATERIALCONSTITUENT', + 2852063980: 'IFCMATERIALCONSTITUENTSET', + 760658860: 'IFCMATERIALDEFINITION', + 2022407955: 'IFCMATERIALDEFINITIONREPRESENTATION', + 248100487: 'IFCMATERIALLAYER', + 3303938423: 'IFCMATERIALLAYERSET', + 1303795690: 'IFCMATERIALLAYERSETUSAGE', + 1847252529: 'IFCMATERIALLAYERWITHOFFSETS', + 2199411900: 'IFCMATERIALLIST', + 2235152071: 'IFCMATERIALPROFILE', + 164193824: 'IFCMATERIALPROFILESET', + 3079605661: 'IFCMATERIALPROFILESETUSAGE', + 3404854881: 'IFCMATERIALPROFILESETUSAGETAPERING', + 552965576: 'IFCMATERIALPROFILEWITHOFFSETS', + 3265635763: 'IFCMATERIALPROPERTIES', + 853536259: 'IFCMATERIALRELATIONSHIP', + 1507914824: 'IFCMATERIALUSAGEDEFINITION', + 2597039031: 'IFCMEASUREWITHUNIT', + 377706215: 'IFCMECHANICALFASTENER', + 2108223431: 'IFCMECHANICALFASTENERTYPE', + 1437502449: 'IFCMEDICALDEVICE', + 1114901282: 'IFCMEDICALDEVICETYPE', + 1073191201: 'IFCMEMBER', + 1911478936: 'IFCMEMBERSTANDARDCASE', + 3181161470: 'IFCMEMBERTYPE', + 3368373690: 'IFCMETRIC', + 2998442950: 'IFCMIRROREDPROFILEDEF', + 2706619895: 'IFCMONETARYUNIT', + 2474470126: 'IFCMOTORCONNECTION', + 977012517: 'IFCMOTORCONNECTIONTYPE', + 1918398963: 'IFCNAMEDUNIT', + 3888040117: 'IFCOBJECT', + 219451334: 'IFCOBJECTDEFINITION', + 3701648758: 'IFCOBJECTPLACEMENT', + 2251480897: 'IFCOBJECTIVE', + 4143007308: 'IFCOCCUPANT', + 590820931: 'IFCOFFSETCURVE', + 3388369263: 'IFCOFFSETCURVE2D', + 3505215534: 'IFCOFFSETCURVE3D', + 2485787929: 'IFCOFFSETCURVEBYDISTANCES', + 2665983363: 'IFCOPENSHELL', + 3588315303: 'IFCOPENINGELEMENT', + 3079942009: 'IFCOPENINGSTANDARDCASE', + 4251960020: 'IFCORGANIZATION', + 1411181986: 'IFCORGANIZATIONRELATIONSHIP', + 643959842: 'IFCORIENTATIONEXPRESSION', + 1029017970: 'IFCORIENTEDEDGE', + 144952367: 'IFCOUTERBOUNDARYCURVE', + 3694346114: 'IFCOUTLET', + 2837617999: 'IFCOUTLETTYPE', + 1207048766: 'IFCOWNERHISTORY', + 2529465313: 'IFCPARAMETERIZEDPROFILEDEF', + 2519244187: 'IFCPATH', + 1682466193: 'IFCPCURVE', + 2382730787: 'IFCPERFORMANCEHISTORY', + 3566463478: 'IFCPERMEABLECOVERINGPROPERTIES', + 3327091369: 'IFCPERMIT', + 2077209135: 'IFCPERSON', + 101040310: 'IFCPERSONANDORGANIZATION', + 3021840470: 'IFCPHYSICALCOMPLEXQUANTITY', + 2483315170: 'IFCPHYSICALQUANTITY', + 2226359599: 'IFCPHYSICALSIMPLEQUANTITY', + 1687234759: 'IFCPILE', + 1158309216: 'IFCPILETYPE', + 310824031: 'IFCPIPEFITTING', + 804291784: 'IFCPIPEFITTINGTYPE', + 3612865200: 'IFCPIPESEGMENT', + 4231323485: 'IFCPIPESEGMENTTYPE', + 597895409: 'IFCPIXELTEXTURE', + 2004835150: 'IFCPLACEMENT', + 603570806: 'IFCPLANARBOX', + 1663979128: 'IFCPLANAREXTENT', + 220341763: 'IFCPLANE', + 3171933400: 'IFCPLATE', + 1156407060: 'IFCPLATESTANDARDCASE', + 4017108033: 'IFCPLATETYPE', + 2067069095: 'IFCPOINT', + 4022376103: 'IFCPOINTONCURVE', + 1423911732: 'IFCPOINTONSURFACE', + 2924175390: 'IFCPOLYLOOP', + 2775532180: 'IFCPOLYGONALBOUNDEDHALFSPACE', + 2839578677: 'IFCPOLYGONALFACESET', + 3724593414: 'IFCPOLYLINE', + 3740093272: 'IFCPORT', + 1946335990: 'IFCPOSITIONINGELEMENT', + 3355820592: 'IFCPOSTALADDRESS', + 759155922: 'IFCPREDEFINEDCOLOUR', + 2559016684: 'IFCPREDEFINEDCURVEFONT', + 3727388367: 'IFCPREDEFINEDITEM', + 3778827333: 'IFCPREDEFINEDPROPERTIES', + 3967405729: 'IFCPREDEFINEDPROPERTYSET', + 1775413392: 'IFCPREDEFINEDTEXTFONT', + 677532197: 'IFCPRESENTATIONITEM', + 2022622350: 'IFCPRESENTATIONLAYERASSIGNMENT', + 1304840413: 'IFCPRESENTATIONLAYERWITHSTYLE', + 3119450353: 'IFCPRESENTATIONSTYLE', + 2417041796: 'IFCPRESENTATIONSTYLEASSIGNMENT', + 2744685151: 'IFCPROCEDURE', + 569719735: 'IFCPROCEDURETYPE', + 2945172077: 'IFCPROCESS', + 4208778838: 'IFCPRODUCT', + 673634403: 'IFCPRODUCTDEFINITIONSHAPE', + 2095639259: 'IFCPRODUCTREPRESENTATION', + 3958567839: 'IFCPROFILEDEF', + 2802850158: 'IFCPROFILEPROPERTIES', + 103090709: 'IFCPROJECT', + 653396225: 'IFCPROJECTLIBRARY', + 2904328755: 'IFCPROJECTORDER', + 3843373140: 'IFCPROJECTEDCRS', + 3651124850: 'IFCPROJECTIONELEMENT', + 2598011224: 'IFCPROPERTY', + 986844984: 'IFCPROPERTYABSTRACTION', + 871118103: 'IFCPROPERTYBOUNDEDVALUE', + 1680319473: 'IFCPROPERTYDEFINITION', + 148025276: 'IFCPROPERTYDEPENDENCYRELATIONSHIP', + 4166981789: 'IFCPROPERTYENUMERATEDVALUE', + 3710013099: 'IFCPROPERTYENUMERATION', + 2752243245: 'IFCPROPERTYLISTVALUE', + 941946838: 'IFCPROPERTYREFERENCEVALUE', + 1451395588: 'IFCPROPERTYSET', + 3357820518: 'IFCPROPERTYSETDEFINITION', + 492091185: 'IFCPROPERTYSETTEMPLATE', + 3650150729: 'IFCPROPERTYSINGLEVALUE', + 110355661: 'IFCPROPERTYTABLEVALUE', + 3521284610: 'IFCPROPERTYTEMPLATE', + 1482703590: 'IFCPROPERTYTEMPLATEDEFINITION', + 738039164: 'IFCPROTECTIVEDEVICE', + 2295281155: 'IFCPROTECTIVEDEVICETRIPPINGUNIT', + 655969474: 'IFCPROTECTIVEDEVICETRIPPINGUNITTYPE', + 1842657554: 'IFCPROTECTIVEDEVICETYPE', + 3219374653: 'IFCPROXY', + 90941305: 'IFCPUMP', + 2250791053: 'IFCPUMPTYPE', + 2044713172: 'IFCQUANTITYAREA', + 2093928680: 'IFCQUANTITYCOUNT', + 931644368: 'IFCQUANTITYLENGTH', + 2090586900: 'IFCQUANTITYSET', + 3252649465: 'IFCQUANTITYTIME', + 2405470396: 'IFCQUANTITYVOLUME', + 825690147: 'IFCQUANTITYWEIGHT', + 2262370178: 'IFCRAILING', + 2893384427: 'IFCRAILINGTYPE', + 3024970846: 'IFCRAMP', + 3283111854: 'IFCRAMPFLIGHT', + 2324767716: 'IFCRAMPFLIGHTTYPE', + 1469900589: 'IFCRAMPTYPE', + 1232101972: 'IFCRATIONALBSPLINECURVEWITHKNOTS', + 683857671: 'IFCRATIONALBSPLINESURFACEWITHKNOTS', + 2770003689: 'IFCRECTANGLEHOLLOWPROFILEDEF', + 3615266464: 'IFCRECTANGLEPROFILEDEF', + 2798486643: 'IFCRECTANGULARPYRAMID', + 3454111270: 'IFCRECTANGULARTRIMMEDSURFACE', + 3915482550: 'IFCRECURRENCEPATTERN', + 2433181523: 'IFCREFERENCE', + 4021432810: 'IFCREFERENT', + 3413951693: 'IFCREGULARTIMESERIES', + 1580146022: 'IFCREINFORCEMENTBARPROPERTIES', + 3765753017: 'IFCREINFORCEMENTDEFINITIONPROPERTIES', + 979691226: 'IFCREINFORCINGBAR', + 2572171363: 'IFCREINFORCINGBARTYPE', + 3027567501: 'IFCREINFORCINGELEMENT', + 964333572: 'IFCREINFORCINGELEMENTTYPE', + 2320036040: 'IFCREINFORCINGMESH', + 2310774935: 'IFCREINFORCINGMESHTYPE', + 160246688: 'IFCRELAGGREGATES', + 3939117080: 'IFCRELASSIGNS', + 1683148259: 'IFCRELASSIGNSTOACTOR', + 2495723537: 'IFCRELASSIGNSTOCONTROL', + 1307041759: 'IFCRELASSIGNSTOGROUP', + 1027710054: 'IFCRELASSIGNSTOGROUPBYFACTOR', + 4278684876: 'IFCRELASSIGNSTOPROCESS', + 2857406711: 'IFCRELASSIGNSTOPRODUCT', + 205026976: 'IFCRELASSIGNSTORESOURCE', + 1865459582: 'IFCRELASSOCIATES', + 4095574036: 'IFCRELASSOCIATESAPPROVAL', + 919958153: 'IFCRELASSOCIATESCLASSIFICATION', + 2728634034: 'IFCRELASSOCIATESCONSTRAINT', + 982818633: 'IFCRELASSOCIATESDOCUMENT', + 3840914261: 'IFCRELASSOCIATESLIBRARY', + 2655215786: 'IFCRELASSOCIATESMATERIAL', + 826625072: 'IFCRELCONNECTS', + 1204542856: 'IFCRELCONNECTSELEMENTS', + 3945020480: 'IFCRELCONNECTSPATHELEMENTS', + 4201705270: 'IFCRELCONNECTSPORTTOELEMENT', + 3190031847: 'IFCRELCONNECTSPORTS', + 2127690289: 'IFCRELCONNECTSSTRUCTURALACTIVITY', + 1638771189: 'IFCRELCONNECTSSTRUCTURALMEMBER', + 504942748: 'IFCRELCONNECTSWITHECCENTRICITY', + 3678494232: 'IFCRELCONNECTSWITHREALIZINGELEMENTS', + 3242617779: 'IFCRELCONTAINEDINSPATIALSTRUCTURE', + 886880790: 'IFCRELCOVERSBLDGELEMENTS', + 2802773753: 'IFCRELCOVERSSPACES', + 2565941209: 'IFCRELDECLARES', + 2551354335: 'IFCRELDECOMPOSES', + 693640335: 'IFCRELDEFINES', + 1462361463: 'IFCRELDEFINESBYOBJECT', + 4186316022: 'IFCRELDEFINESBYPROPERTIES', + 307848117: 'IFCRELDEFINESBYTEMPLATE', + 781010003: 'IFCRELDEFINESBYTYPE', + 3940055652: 'IFCRELFILLSELEMENT', + 279856033: 'IFCRELFLOWCONTROLELEMENTS', + 427948657: 'IFCRELINTERFERESELEMENTS', + 3268803585: 'IFCRELNESTS', + 1441486842: 'IFCRELPOSITIONS', + 750771296: 'IFCRELPROJECTSELEMENT', + 1245217292: 'IFCRELREFERENCEDINSPATIALSTRUCTURE', + 4122056220: 'IFCRELSEQUENCE', + 366585022: 'IFCRELSERVICESBUILDINGS', + 3451746338: 'IFCRELSPACEBOUNDARY', + 3523091289: 'IFCRELSPACEBOUNDARY1STLEVEL', + 1521410863: 'IFCRELSPACEBOUNDARY2NDLEVEL', + 1401173127: 'IFCRELVOIDSELEMENT', + 478536968: 'IFCRELATIONSHIP', + 816062949: 'IFCREPARAMETRISEDCOMPOSITECURVESEGMENT', + 1076942058: 'IFCREPRESENTATION', + 3377609919: 'IFCREPRESENTATIONCONTEXT', + 3008791417: 'IFCREPRESENTATIONITEM', + 1660063152: 'IFCREPRESENTATIONMAP', + 2914609552: 'IFCRESOURCE', + 2943643501: 'IFCRESOURCEAPPROVALRELATIONSHIP', + 1608871552: 'IFCRESOURCECONSTRAINTRELATIONSHIP', + 2439245199: 'IFCRESOURCELEVELRELATIONSHIP', + 1042787934: 'IFCRESOURCETIME', + 1856042241: 'IFCREVOLVEDAREASOLID', + 3243963512: 'IFCREVOLVEDAREASOLIDTAPERED', + 4158566097: 'IFCRIGHTCIRCULARCONE', + 3626867408: 'IFCRIGHTCIRCULARCYLINDER', + 2016517767: 'IFCROOF', + 2781568857: 'IFCROOFTYPE', + 2341007311: 'IFCROOT', + 2778083089: 'IFCROUNDEDRECTANGLEPROFILEDEF', + 448429030: 'IFCSIUNIT', + 3053780830: 'IFCSANITARYTERMINAL', + 1768891740: 'IFCSANITARYTERMINALTYPE', + 1054537805: 'IFCSCHEDULINGTIME', + 2157484638: 'IFCSEAMCURVE', + 2042790032: 'IFCSECTIONPROPERTIES', + 4165799628: 'IFCSECTIONREINFORCEMENTPROPERTIES', + 1862484736: 'IFCSECTIONEDSOLID', + 1290935644: 'IFCSECTIONEDSOLIDHORIZONTAL', + 1509187699: 'IFCSECTIONEDSPINE', + 4086658281: 'IFCSENSOR', + 1783015770: 'IFCSENSORTYPE', + 1329646415: 'IFCSHADINGDEVICE', + 4074543187: 'IFCSHADINGDEVICETYPE', + 867548509: 'IFCSHAPEASPECT', + 3982875396: 'IFCSHAPEMODEL', + 4240577450: 'IFCSHAPEREPRESENTATION', + 4124623270: 'IFCSHELLBASEDSURFACEMODEL', + 3692461612: 'IFCSIMPLEPROPERTY', + 3663146110: 'IFCSIMPLEPROPERTYTEMPLATE', + 4097777520: 'IFCSITE', + 1529196076: 'IFCSLAB', + 3127900445: 'IFCSLABELEMENTEDCASE', + 3027962421: 'IFCSLABSTANDARDCASE', + 2533589738: 'IFCSLABTYPE', + 2609359061: 'IFCSLIPPAGECONNECTIONCONDITION', + 3420628829: 'IFCSOLARDEVICE', + 1072016465: 'IFCSOLARDEVICETYPE', + 723233188: 'IFCSOLIDMODEL', + 3856911033: 'IFCSPACE', + 1999602285: 'IFCSPACEHEATER', + 1305183839: 'IFCSPACEHEATERTYPE', + 3812236995: 'IFCSPACETYPE', + 1412071761: 'IFCSPATIALELEMENT', + 710998568: 'IFCSPATIALELEMENTTYPE', + 2706606064: 'IFCSPATIALSTRUCTUREELEMENT', + 3893378262: 'IFCSPATIALSTRUCTUREELEMENTTYPE', + 463610769: 'IFCSPATIALZONE', + 2481509218: 'IFCSPATIALZONETYPE', + 451544542: 'IFCSPHERE', + 4015995234: 'IFCSPHERICALSURFACE', + 1404847402: 'IFCSTACKTERMINAL', + 3112655638: 'IFCSTACKTERMINALTYPE', + 331165859: 'IFCSTAIR', + 4252922144: 'IFCSTAIRFLIGHT', + 1039846685: 'IFCSTAIRFLIGHTTYPE', + 338393293: 'IFCSTAIRTYPE', + 682877961: 'IFCSTRUCTURALACTION', + 3544373492: 'IFCSTRUCTURALACTIVITY', + 2515109513: 'IFCSTRUCTURALANALYSISMODEL', + 1179482911: 'IFCSTRUCTURALCONNECTION', + 2273995522: 'IFCSTRUCTURALCONNECTIONCONDITION', + 1004757350: 'IFCSTRUCTURALCURVEACTION', + 4243806635: 'IFCSTRUCTURALCURVECONNECTION', + 214636428: 'IFCSTRUCTURALCURVEMEMBER', + 2445595289: 'IFCSTRUCTURALCURVEMEMBERVARYING', + 2757150158: 'IFCSTRUCTURALCURVEREACTION', + 3136571912: 'IFCSTRUCTURALITEM', + 1807405624: 'IFCSTRUCTURALLINEARACTION', + 2162789131: 'IFCSTRUCTURALLOAD', + 385403989: 'IFCSTRUCTURALLOADCASE', + 3478079324: 'IFCSTRUCTURALLOADCONFIGURATION', + 1252848954: 'IFCSTRUCTURALLOADGROUP', + 1595516126: 'IFCSTRUCTURALLOADLINEARFORCE', + 609421318: 'IFCSTRUCTURALLOADORRESULT', + 2668620305: 'IFCSTRUCTURALLOADPLANARFORCE', + 2473145415: 'IFCSTRUCTURALLOADSINGLEDISPLACEMENT', + 1973038258: 'IFCSTRUCTURALLOADSINGLEDISPLACEMENTDISTORTION', + 1597423693: 'IFCSTRUCTURALLOADSINGLEFORCE', + 1190533807: 'IFCSTRUCTURALLOADSINGLEFORCEWARPING', + 2525727697: 'IFCSTRUCTURALLOADSTATIC', + 3408363356: 'IFCSTRUCTURALLOADTEMPERATURE', + 530289379: 'IFCSTRUCTURALMEMBER', + 1621171031: 'IFCSTRUCTURALPLANARACTION', + 2082059205: 'IFCSTRUCTURALPOINTACTION', + 734778138: 'IFCSTRUCTURALPOINTCONNECTION', + 1235345126: 'IFCSTRUCTURALPOINTREACTION', + 3689010777: 'IFCSTRUCTURALREACTION', + 2986769608: 'IFCSTRUCTURALRESULTGROUP', + 3657597509: 'IFCSTRUCTURALSURFACEACTION', + 1975003073: 'IFCSTRUCTURALSURFACECONNECTION', + 3979015343: 'IFCSTRUCTURALSURFACEMEMBER', + 2218152070: 'IFCSTRUCTURALSURFACEMEMBERVARYING', + 603775116: 'IFCSTRUCTURALSURFACEREACTION', + 2830218821: 'IFCSTYLEMODEL', + 3958052878: 'IFCSTYLEDITEM', + 3049322572: 'IFCSTYLEDREPRESENTATION', + 148013059: 'IFCSUBCONTRACTRESOURCE', + 4095615324: 'IFCSUBCONTRACTRESOURCETYPE', + 2233826070: 'IFCSUBEDGE', + 2513912981: 'IFCSURFACE', + 699246055: 'IFCSURFACECURVE', + 2028607225: 'IFCSURFACECURVESWEPTAREASOLID', + 3101698114: 'IFCSURFACEFEATURE', + 2809605785: 'IFCSURFACEOFLINEAREXTRUSION', + 4124788165: 'IFCSURFACEOFREVOLUTION', + 2934153892: 'IFCSURFACEREINFORCEMENTAREA', + 1300840506: 'IFCSURFACESTYLE', + 3303107099: 'IFCSURFACESTYLELIGHTING', + 1607154358: 'IFCSURFACESTYLEREFRACTION', + 1878645084: 'IFCSURFACESTYLERENDERING', + 846575682: 'IFCSURFACESTYLESHADING', + 1351298697: 'IFCSURFACESTYLEWITHTEXTURES', + 626085974: 'IFCSURFACETEXTURE', + 2247615214: 'IFCSWEPTAREASOLID', + 1260650574: 'IFCSWEPTDISKSOLID', + 1096409881: 'IFCSWEPTDISKSOLIDPOLYGONAL', + 230924584: 'IFCSWEPTSURFACE', + 1162798199: 'IFCSWITCHINGDEVICE', + 2315554128: 'IFCSWITCHINGDEVICETYPE', + 2254336722: 'IFCSYSTEM', + 413509423: 'IFCSYSTEMFURNITUREELEMENT', + 1580310250: 'IFCSYSTEMFURNITUREELEMENTTYPE', + 3071757647: 'IFCTSHAPEPROFILEDEF', + 985171141: 'IFCTABLE', + 2043862942: 'IFCTABLECOLUMN', + 531007025: 'IFCTABLEROW', + 812556717: 'IFCTANK', + 5716631: 'IFCTANKTYPE', + 3473067441: 'IFCTASK', + 1549132990: 'IFCTASKTIME', + 2771591690: 'IFCTASKTIMERECURRING', + 3206491090: 'IFCTASKTYPE', + 912023232: 'IFCTELECOMADDRESS', + 3824725483: 'IFCTENDON', + 2347447852: 'IFCTENDONANCHOR', + 3081323446: 'IFCTENDONANCHORTYPE', + 3663046924: 'IFCTENDONCONDUIT', + 2281632017: 'IFCTENDONCONDUITTYPE', + 2415094496: 'IFCTENDONTYPE', + 2387106220: 'IFCTESSELLATEDFACESET', + 901063453: 'IFCTESSELLATEDITEM', + 4282788508: 'IFCTEXTLITERAL', + 3124975700: 'IFCTEXTLITERALWITHEXTENT', + 1447204868: 'IFCTEXTSTYLE', + 1983826977: 'IFCTEXTSTYLEFONTMODEL', + 2636378356: 'IFCTEXTSTYLEFORDEFINEDFONT', + 1640371178: 'IFCTEXTSTYLETEXTMODEL', + 280115917: 'IFCTEXTURECOORDINATE', + 1742049831: 'IFCTEXTURECOORDINATEGENERATOR', + 2552916305: 'IFCTEXTUREMAP', + 1210645708: 'IFCTEXTUREVERTEX', + 3611470254: 'IFCTEXTUREVERTEXLIST', + 1199560280: 'IFCTIMEPERIOD', + 3101149627: 'IFCTIMESERIES', + 581633288: 'IFCTIMESERIESVALUE', + 1377556343: 'IFCTOPOLOGICALREPRESENTATIONITEM', + 1735638870: 'IFCTOPOLOGYREPRESENTATION', + 1935646853: 'IFCTOROIDALSURFACE', + 3825984169: 'IFCTRANSFORMER', + 1692211062: 'IFCTRANSFORMERTYPE', + 2595432518: 'IFCTRANSITIONCURVESEGMENT2D', + 1620046519: 'IFCTRANSPORTELEMENT', + 2097647324: 'IFCTRANSPORTELEMENTTYPE', + 2715220739: 'IFCTRAPEZIUMPROFILEDEF', + 2916149573: 'IFCTRIANGULATEDFACESET', + 1229763772: 'IFCTRIANGULATEDIRREGULARNETWORK', + 3593883385: 'IFCTRIMMEDCURVE', + 3026737570: 'IFCTUBEBUNDLE', + 1600972822: 'IFCTUBEBUNDLETYPE', + 1628702193: 'IFCTYPEOBJECT', + 3736923433: 'IFCTYPEPROCESS', + 2347495698: 'IFCTYPEPRODUCT', + 3698973494: 'IFCTYPERESOURCE', + 427810014: 'IFCUSHAPEPROFILEDEF', + 180925521: 'IFCUNITASSIGNMENT', + 630975310: 'IFCUNITARYCONTROLELEMENT', + 3179687236: 'IFCUNITARYCONTROLELEMENTTYPE', + 4292641817: 'IFCUNITARYEQUIPMENT', + 1911125066: 'IFCUNITARYEQUIPMENTTYPE', + 4207607924: 'IFCVALVE', + 728799441: 'IFCVALVETYPE', + 1417489154: 'IFCVECTOR', + 2799835756: 'IFCVERTEX', + 2759199220: 'IFCVERTEXLOOP', + 1907098498: 'IFCVERTEXPOINT', + 1530820697: 'IFCVIBRATIONDAMPER', + 3956297820: 'IFCVIBRATIONDAMPERTYPE', + 2391383451: 'IFCVIBRATIONISOLATOR', + 3313531582: 'IFCVIBRATIONISOLATORTYPE', + 2769231204: 'IFCVIRTUALELEMENT', + 891718957: 'IFCVIRTUALGRIDINTERSECTION', + 926996030: 'IFCVOIDINGFEATURE', + 2391406946: 'IFCWALL', + 4156078855: 'IFCWALLELEMENTEDCASE', + 3512223829: 'IFCWALLSTANDARDCASE', + 1898987631: 'IFCWALLTYPE', + 4237592921: 'IFCWASTETERMINAL', + 1133259667: 'IFCWASTETERMINALTYPE', + 3304561284: 'IFCWINDOW', + 336235671: 'IFCWINDOWLININGPROPERTIES', + 512836454: 'IFCWINDOWPANELPROPERTIES', + 486154966: 'IFCWINDOWSTANDARDCASE', + 1299126871: 'IFCWINDOWSTYLE', + 4009809668: 'IFCWINDOWTYPE', + 4088093105: 'IFCWORKCALENDAR', + 1028945134: 'IFCWORKCONTROL', + 4218914973: 'IFCWORKPLAN', + 3342526732: 'IFCWORKSCHEDULE', + 1236880293: 'IFCWORKTIME', + 2543172580: 'IFCZSHAPEPROFILEDEF', + 1033361043: 'IFCZONE', +}; + +class PropertyManager { + + constructor( state ) { + + this.state = state; + + } + + getExpressId( geometry, faceIndex ) { + + if ( ! geometry.index ) + return; + const geoIndex = geometry.index.array; + return geometry.attributes[ IdAttrName ].getX( geoIndex[ 3 * faceIndex ] ); + + } + + getItemProperties( modelID, id, recursive = false ) { + + return this.state.useJSON ? + { + ...this.state.models[ modelID ].jsonData[ id ] + } : + this.state.api.GetLine( modelID, id, recursive ); + + } + + getAllItemsOfType( modelID, type, verbose ) { + + return this.state.useJSON ? + this.getAllItemsOfTypeJSON( modelID, type, verbose ) : + this.getAllItemsOfTypeWebIfcAPI( modelID, type, verbose ); + + } + + getPropertySets( modelID, elementID, recursive = false ) { + + return this.state.useJSON ? + this.getPropertyJSON( modelID, elementID, recursive, PropsNames.psets ) : + this.getPropertyWebIfcAPI( modelID, elementID, recursive, PropsNames.psets ); + + } + + getTypeProperties( modelID, elementID, recursive = false ) { + + return this.state.useJSON ? + this.getPropertyJSON( modelID, elementID, recursive, PropsNames.type ) : + this.getPropertyWebIfcAPI( modelID, elementID, recursive, PropsNames.type ); + + } + + getMaterialsProperties( modelID, elementID, recursive = false ) { + + return this.state.useJSON ? + this.getPropertyJSON( modelID, elementID, recursive, PropsNames.materials ) : + this.getPropertyWebIfcAPI( modelID, elementID, recursive, PropsNames.materials ); + + } + + getSpatialStructure( modelID ) { + + return this.state.useJSON ? + this.getSpatialStructureJSON( modelID ) : + this.getSpatialStructureWebIfcAPI( modelID ); + + } + + getSpatialStructureJSON( modelID ) { + + const chunks = this.getSpatialTreeChunks( modelID ); + const projectID = this.getAllItemsOfTypeJSON( modelID, IFCPROJECT, false )[ 0 ]; + const project = this.newIfcProject( projectID ); + this.getSpatialNode( modelID, project, chunks ); + return { + ...project + }; + + } + + getSpatialStructureWebIfcAPI( modelID ) { + + const chunks = this.getSpatialTreeChunks( modelID ); + const projectID = this.state.api.GetLineIDsWithType( modelID, IFCPROJECT ).get( 0 ); + const project = this.newIfcProject( projectID ); + this.getSpatialNode( modelID, project, chunks ); + return project; + + } + + getAllItemsOfTypeJSON( modelID, type, verbose ) { + + const data = this.state.models[ modelID ].jsonData; + const typeName = IfcTypesMap[ type ]; + if ( ! typeName ) { + + throw new Error( `Type not found: ${type}` ); + + } + + return this.filterJSONItemsByType( data, typeName, verbose ); + + } + + filterJSONItemsByType( data, typeName, verbose ) { + + const result = []; + Object.keys( data ).forEach( key => { + + const numKey = parseInt( key ); + if ( data[ numKey ].type.toUpperCase() === typeName ) { + + result.push( verbose ? { + ...data[ numKey ] + } : numKey ); + + } + + } ); + return result; + + } + + getItemsByIDJSON( modelID, ids ) { + + const data = this.state.models[ modelID ].jsonData; + const result = []; + ids.forEach( id => result.push( { + ...data[ id ] + } ) ); + return result; + + } + + getPropertyJSON( modelID, elementID, recursive = false, propName ) { + + const resultIDs = this.getAllRelatedItemsOfTypeJSON( modelID, elementID, propName ); + const result = this.getItemsByIDJSON( modelID, resultIDs ); + if ( recursive ) { + + result.forEach( result => this.getJSONReferencesRecursively( modelID, result ) ); + + } + + return result; + + } + + getJSONReferencesRecursively( modelID, jsonObject ) { + + if ( jsonObject == undefined ) + return; + const keys = Object.keys( jsonObject ); + for ( let i = 0; i < keys.length; i ++ ) { + + const key = keys[ i ]; + this.getJSONItem( modelID, jsonObject, key ); + + } + + } + + getJSONItem( modelID, jsonObject, key ) { + + if ( Array.isArray( jsonObject[ key ] ) ) { + + return this.getMultipleJSONItems( modelID, jsonObject, key ); + + } + + if ( jsonObject[ key ] && jsonObject[ key ].type === 5 ) { + + jsonObject[ key ] = this.getItemsByIDJSON( modelID, [ jsonObject[ key ].value ] )[ 0 ]; + this.getJSONReferencesRecursively( modelID, jsonObject[ key ] ); + + } + + } + + getMultipleJSONItems( modelID, jsonObject, key ) { + + jsonObject[ key ] = jsonObject[ key ].map( ( item ) => { + + if ( item.type === 5 ) { + + item = this.getItemsByIDJSON( modelID, [ item.value ] )[ 0 ]; + this.getJSONReferencesRecursively( modelID, item ); + + } + + return item; + + } ); + + } + + getPropertyWebIfcAPI( modelID, elementID, recursive = false, propName ) { + + const propSetIds = this.getAllRelatedItemsOfTypeWebIfcAPI( modelID, elementID, propName ); + return propSetIds.map( ( id ) => this.state.api.GetLine( modelID, id, recursive ) ); + + } + + getAllItemsOfTypeWebIfcAPI( modelID, type, verbose ) { + + const items = []; + const lines = this.state.api.GetLineIDsWithType( modelID, type ); + for ( let i = 0; i < lines.size(); i ++ ) + items.push( lines.get( i ) ); + if ( verbose ) + return items.map( ( id ) => this.state.api.GetLine( modelID, id ) ); + return items; + + } + + newIfcProject( id ) { + + return { + expressID: id, + type: 'IFCPROJECT', + children: [] + }; + + } + + getSpatialTreeChunks( modelID ) { + + const treeChunks = {}; + const json = this.state.useJSON; + if ( json ) { + + this.getChunksJSON( modelID, treeChunks, PropsNames.aggregates ); + this.getChunksJSON( modelID, treeChunks, PropsNames.spatial ); + + } else { + + this.getChunksWebIfcAPI( modelID, treeChunks, PropsNames.aggregates ); + this.getChunksWebIfcAPI( modelID, treeChunks, PropsNames.spatial ); + + } + + return treeChunks; + + } + + getChunksJSON( modelID, chunks, propNames ) { + + const relation = this.getAllItemsOfTypeJSON( modelID, propNames.name, true ); + relation.forEach( rel => { + + this.saveChunk( chunks, propNames, rel ); + + } ); + + } + + getChunksWebIfcAPI( modelID, chunks, propNames ) { + + const relation = this.state.api.GetLineIDsWithType( modelID, propNames.name ); + for ( let i = 0; i < relation.size(); i ++ ) { + + const rel = this.state.api.GetLine( modelID, relation.get( i ), false ); + this.saveChunk( chunks, propNames, rel ); + + } + + } + + saveChunk( chunks, propNames, rel ) { + + const relating = rel[ propNames.relating ].value; + const related = rel[ propNames.related ].map( ( r ) => r.value ); + if ( chunks[ relating ] == undefined ) { + + chunks[ relating ] = related; + + } else { + + chunks[ relating ] = chunks[ relating ].concat( related ); + + } + + } + + getSpatialNode( modelID, node, treeChunks ) { + + this.getChildren( modelID, node, treeChunks, PropsNames.aggregates ); + this.getChildren( modelID, node, treeChunks, PropsNames.spatial ); + + } + + getChildren( modelID, node, treeChunks, propNames ) { + + const children = treeChunks[ node.expressID ]; + if ( children == undefined ) + return; + const prop = propNames.key; + node[ prop ] = children.map( ( child ) => { + + const node = this.newNode( modelID, child ); + this.getSpatialNode( modelID, node, treeChunks ); + return node; + + } ); + + } + + newNode( modelID, id ) { + + const typeName = this.getNodeType( modelID, id ); + return { + expressID: id, + type: typeName, + children: [] + }; + + } + + getNodeType( modelID, id ) { + + if ( this.state.useJSON ) + return this.state.models[ modelID ].jsonData[ id ].type; + const typeID = this.state.models[ modelID ].types[ id ]; + return IfcElements[ typeID ]; + + } + + getAllRelatedItemsOfTypeJSON( modelID, id, propNames ) { + + const lines = this.getAllItemsOfTypeJSON( modelID, propNames.name, true ); + const IDs = []; + lines.forEach( line => { + + const isRelated = this.isRelated( id, line, propNames ); + if ( isRelated ) + this.getRelated( line, propNames, IDs ); + + } ); + return IDs; + + } + + getAllRelatedItemsOfTypeWebIfcAPI( modelID, id, propNames ) { + + const lines = this.state.api.GetLineIDsWithType( modelID, propNames.name ); + const IDs = []; + for ( let i = 0; i < lines.size(); i ++ ) { + + const rel = this.state.api.GetLine( modelID, lines.get( i ) ); + const isRelated = this.isRelated( id, rel, propNames ); + if ( isRelated ) + this.getRelated( rel, propNames, IDs ); + + } + + return IDs; + + } + + getRelated( rel, propNames, IDs ) { + + const element = rel[ propNames.relating ]; + if ( ! Array.isArray( element ) ) + IDs.push( element.value ); + else + element.forEach( ( ele ) => IDs.push( ele.value ) ); + + } + + isRelated( id, rel, propNames ) { + + const relatedItems = rel[ propNames.related ]; + if ( Array.isArray( relatedItems ) ) { + + const values = relatedItems.map( ( item ) => item.value ); + return values.includes( id ); + + } + + return relatedItems.value === id; + + } + +} + +class TypeManager { + + constructor( state ) { + + this.state = state; + + } + + getAllTypes() { + + for ( const modelID in this.state.models ) { + + const types = this.state.models[ modelID ].types; + if ( Object.keys( types ).length == 0 ) + this.getAllTypesOfModel( parseInt( modelID ) ); + + } + + } + + getAllTypesOfModel( modelID ) { + + this.state.models[ modelID ].types; + const elements = Object.keys( IfcElements ).map( ( e ) => parseInt( e ) ); + const types = this.state.models[ modelID ].types; + elements.forEach( ( type ) => { + + const lines = this.state.api.GetLineIDsWithType( modelID, type ); + for ( let i = 0; i < lines.size(); i ++ ) + types[ lines.get( i ) ] = type; + + } ); + + } + +} + +let modelIdCounter = 0; +const nullIfcManagerErrorMessage = 'IfcManager is null!'; + +class IFCModel extends Mesh { + + constructor() { + + super( ...arguments ); + this.modelID = modelIdCounter ++; + this.ifcManager = null; + this.mesh = this; + + } + + setIFCManager( manager ) { + + this.ifcManager = manager; + + } + + setWasmPath( path ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.setWasmPath( path ); + + } + + close( scene ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.close( this.modelID, scene ); + + } + + getExpressId( geometry, faceIndex ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getExpressId( geometry, faceIndex ); + + } + + getAllItemsOfType( type, verbose ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getAllItemsOfType( this.modelID, type, verbose ); + + } + + getItemProperties( id, recursive = false ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getItemProperties( this.modelID, id, recursive ); + + } + + getPropertySets( id, recursive = false ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getPropertySets( this.modelID, id, recursive ); + + } + + getTypeProperties( id, recursive = false ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getTypeProperties( this.modelID, id, recursive ); + + } + + getIfcType( id ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getIfcType( this.modelID, id ); + + } + + getSpatialStructure() { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getSpatialStructure( this.modelID ); + + } + + getSubset( material ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getSubset( this.modelID, material ); + + } + + removeSubset( parent, material ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.removeSubset( this.modelID, parent, material ); + + } + + createSubset( config ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + const modelConfig = { + ...config, + modelID: this.modelID + }; + return this.ifcManager.createSubset( modelConfig ); + + } + + hideItems( ids ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.hideItems( this.modelID, ids ); + + } + + hideAllItems() { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.hideAllItems( this.modelID ); + + } + + showItems( ids ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.showItems( this.modelID, ids ); + + } + + showAllItems() { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.showAllItems( this.modelID ); + + } + +} + +class BvhManager { + + initializeMeshBVH( computeBoundsTree, disposeBoundsTree, acceleratedRaycast ) { + + this.computeBoundsTree = computeBoundsTree; + this.disposeBoundsTree = disposeBoundsTree; + this.acceleratedRaycast = acceleratedRaycast; + this.setupThreeMeshBVH(); + + } + + applyThreeMeshBVH( geometry ) { + + if ( this.computeBoundsTree ) + geometry.computeBoundsTree(); + + } + + setupThreeMeshBVH() { + + if ( ! this.computeBoundsTree || ! this.disposeBoundsTree || ! this.acceleratedRaycast ) + return; + BufferGeometry.prototype.computeBoundsTree = this.computeBoundsTree; + BufferGeometry.prototype.disposeBoundsTree = this.disposeBoundsTree; + Mesh.prototype.raycast = this.acceleratedRaycast; + + } + +} + +class ItemsHider { + + constructor( state ) { + + this.modelCoordinates = {}; + this.expressIDCoordinatesMap = {}; + this.state = state; + + } + + + + processCoordinates( modelID ) { + + const attributes = this.getAttributes( modelID ); + const ids = Array.from( attributes.expressID.array ); + this.expressIDCoordinatesMap[ modelID ] = {}; + for ( let i = 0; i < ids.length; i ++ ) { + + if ( ! this.expressIDCoordinatesMap[ modelID ][ ids[ i ] ] ) { + + this.expressIDCoordinatesMap[ modelID ][ ids[ i ] ] = []; + + } + + const current = this.expressIDCoordinatesMap[ modelID ]; + current[ ids[ i ] ].push( 3 * i ); + + } + + this.initializeCoordinates( modelID ); + + } + + hideItems( modelID, ids ) { + + this.editCoordinates( modelID, ids, true ); + + } + + showItems( modelID, ids ) { + + this.editCoordinates( modelID, ids, false ); + + } + + editCoordinates( modelID, ids, hide ) { + + const current = this.expressIDCoordinatesMap[ modelID ]; + const indices = []; + ids.forEach( ( id ) => { + + if ( current[ id ] ) + indices.push( ...current[ id ] ); + + } ); + const coords = this.getCoordinates( modelID ); + const initial = this.modelCoordinates[ modelID ]; + if ( hide ) + indices.forEach( i => coords.set( [ 0, 0, 0 ], i ) ); + else + indices.forEach( i => coords.set( [ initial[ i ], initial[ i + 1 ], initial[ i + 2 ] ], i ) ); + this.getAttributes( modelID ).position.needsUpdate = true; + + } + + showAllItems( modelID ) { + + if ( this.modelCoordinates[ modelID ] ) { + + this.resetCoordinates( modelID ); + this.getAttributes( modelID ).position.needsUpdate = true; + + } + + } + + hideAllItems( modelID ) { + + this.getCoordinates( modelID ).fill( 0 ); + this.getAttributes( modelID ).position.needsUpdate = true; + + } + + initializeCoordinates( modelID ) { + + const coordinates = this.getCoordinates( modelID ); + if ( ! this.modelCoordinates[ modelID ] ) { + + this.modelCoordinates[ modelID ] = new Float32Array( coordinates ); + + } + + } + + resetCoordinates( modelID ) { + + const initial = this.modelCoordinates[ modelID ]; + this.getCoordinates( modelID ).set( initial ); + + } + + getCoordinates( modelID ) { + + return this.getAttributes( modelID ).position.array; + + } + + getAttributes( modelID ) { + + return this.state.models[ modelID ].mesh.geometry.attributes; + + } + +} + +class IFCManager { + + constructor() { + + this.state = { + models: [], + api: new IfcAPI(), + useJSON: false + }; + this.BVH = new BvhManager(); + this.parser = new IFCParser( this.state, this.BVH ); + this.subsets = new SubsetManager( this.state, this.BVH ); + this.properties = new PropertyManager( this.state ); + this.types = new TypeManager( this.state ); + this.hider = new ItemsHider( this.state ); + + } + + async parse( buffer ) { + + const mesh = await this.parser.parse( buffer ); + this.state.useJSON ? this.disposeMemory() : this.types.getAllTypes(); + this.hider.processCoordinates( mesh.modelID ); + const model = new IFCModel( mesh.geometry, mesh.material ); + model.setIFCManager( this ); + return model; + + } + + setWasmPath( path ) { + + this.state.api.SetWasmPath( path ); + + } + + applyWebIfcConfig( settings ) { + + this.state.webIfcSettings = settings; + + } + + useJSONData( useJSON = true ) { + + this.state.useJSON = useJSON; + this.disposeMemory(); + + } + + addModelJSONData( modelID, data ) { + + const model = this.state.models[ modelID ]; + if ( model ) { + + model.jsonData = data; + + } + + } + + disposeMemory() { + + this.state.api = null; + this.state.api = new IfcAPI(); + + } + + setupThreeMeshBVH( computeBoundsTree, disposeBoundsTree, acceleratedRaycast ) { + + this.BVH.initializeMeshBVH( computeBoundsTree, disposeBoundsTree, acceleratedRaycast ); + + } + + close( modelID, scene ) { + + this.state.api.CloseModel( modelID ); + if ( scene ) { + + scene.remove( this.state.models[ modelID ].mesh ); + + } + + delete this.state.models[ modelID ]; + + } + + getExpressId( geometry, faceIndex ) { + + return this.properties.getExpressId( geometry, faceIndex ); + + } + + getAllItemsOfType( modelID, type, verbose ) { + + return this.properties.getAllItemsOfType( modelID, type, verbose ); + + } + + getItemProperties( modelID, id, recursive = false ) { + + return this.properties.getItemProperties( modelID, id, recursive ); + + } + + getPropertySets( modelID, id, recursive = false ) { + + return this.properties.getPropertySets( modelID, id, recursive ); + + } + + getTypeProperties( modelID, id, recursive = false ) { + + return this.properties.getTypeProperties( modelID, id, recursive ); + + } + + getMaterialsProperties( modelID, id, recursive = false ) { + + return this.properties.getMaterialsProperties( modelID, id, recursive ); + + } + + getIfcType( modelID, id ) { + + const typeID = this.state.models[ modelID ].types[ id ]; + return IfcElements[ typeID ]; + + } + + getSpatialStructure( modelID ) { + + return this.properties.getSpatialStructure( modelID ); + + } + + getSubset( modelID, material ) { + + return this.subsets.getSubset( modelID, material ); + + } + + removeSubset( modelID, parent, material ) { + + this.subsets.removeSubset( modelID, parent, material ); + + } + + createSubset( config ) { + + return this.subsets.createSubset( config ); + + } + + hideItems( modelID, ids ) { + + this.hider.hideItems( modelID, ids ); + + } + + hideAllItems( modelID ) { + + this.hider.hideAllItems( modelID ); + + } + + showItems( modelID, ids ) { + + this.hider.showItems( modelID, ids ); + + } + + showAllItems( modelID ) { + + this.hider.showAllItems( modelID ); + + } + +} + +class IFCLoader extends Loader { + + constructor( manager ) { + + super( manager ); + this.ifcManager = new IFCManager(); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, async function ( buffer ) { + + try { + + if ( typeof buffer == 'string' ) { + + throw new Error( 'IFC files must be given as a buffer!' ); + + } + + onLoad( await scope.parse( buffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( buffer ) { + + return this.ifcManager.parse( buffer ); + + } + +} + +export { IFCLoader }; +//# sourceMappingURL=IFCLoader.js.map diff --git a/jsm/loaders/KMZLoader.js b/jsm/loaders/KMZLoader.js new file mode 100644 index 0000000..5f43eb3 --- /dev/null +++ b/jsm/loaders/KMZLoader.js @@ -0,0 +1,130 @@ +import { + FileLoader, + Group, + Loader, + LoadingManager +} from 'three'; +import { ColladaLoader } from '../loaders/ColladaLoader.js'; +import * as fflate from '../libs/fflate.module.js'; + +class KMZLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + function findFile( url ) { + + for ( const path in zip ) { + + if ( path.slice( - url.length ) === url ) { + + return zip[ path ]; + + } + + } + + } + + const manager = new LoadingManager(); + manager.setURLModifier( function ( url ) { + + const image = findFile( url ); + + if ( image ) { + + console.log( 'Loading', url ); + + const blob = new Blob( [ image.buffer ], { type: 'application/octet-stream' } ); + return URL.createObjectURL( blob ); + + } + + return url; + + } ); + + // + + const zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef + + if ( zip[ 'doc.kml' ] ) { + + const xml = new DOMParser().parseFromString( fflate.strFromU8( zip[ 'doc.kml' ] ), 'application/xml' ); // eslint-disable-line no-undef + + const model = xml.querySelector( 'Placemark Model Link href' ); + + if ( model ) { + + const loader = new ColladaLoader( manager ); + return loader.parse( fflate.strFromU8( zip[ model.textContent ] ) ); // eslint-disable-line no-undef + + } + + } else { + + console.warn( 'KMZLoader: Missing doc.kml file.' ); + + for ( const path in zip ) { + + const extension = path.split( '.' ).pop().toLowerCase(); + + if ( extension === 'dae' ) { + + const loader = new ColladaLoader( manager ); + return loader.parse( fflate.strFromU8( zip[ path ] ) ); // eslint-disable-line no-undef + + } + + } + + } + + console.error( 'KMZLoader: Couldn\'t find .dae file.' ); + return { scene: new Group() }; + + } + +} + +export { KMZLoader }; diff --git a/jsm/loaders/KTX2Loader.js b/jsm/loaders/KTX2Loader.js new file mode 100644 index 0000000..3aaa757 --- /dev/null +++ b/jsm/loaders/KTX2Loader.js @@ -0,0 +1,591 @@ +/** + * Loader for KTX 2.0 GPU Texture containers. + * + * KTX 2.0 is a container format for various GPU texture formats. The loader + * supports Basis Universal GPU textures, which can be quickly transcoded to + * a wide variety of GPU texture compression formats. While KTX 2.0 also allows + * other hardware-specific formats, this loader does not yet parse them. + * + * References: + * - KTX: http://github.khronos.org/KTX-Specification/ + * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor + */ + +import { + CompressedTexture, + FileLoader, + LinearEncoding, + LinearFilter, + LinearMipmapLinearFilter, + Loader, + RGBAFormat, + RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format, + RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format, + sRGBEncoding, + UnsignedByteType +} from 'three'; +import { WorkerPool } from '../utils/WorkerPool.js'; + +const KTX2TransferSRGB = 2; +const KTX2_ALPHA_PREMULTIPLIED = 1; +const _taskCache = new WeakMap(); + +let _activeLoaders = 0; + +class KTX2Loader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.transcoderPath = ''; + this.transcoderBinary = null; + this.transcoderPending = null; + + this.workerPool = new WorkerPool(); + this.workerSourceURL = ''; + this.workerConfig = null; + + if ( typeof MSC_TRANSCODER !== 'undefined' ) { + + console.warn( + + 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' + + ); + + } + + } + + setTranscoderPath( path ) { + + this.transcoderPath = path; + + return this; + + } + + setWorkerLimit( num ) { + + this.workerPool.setWorkerLimit( num ); + + return this; + + } + + detectSupport( renderer ) { + + this.workerConfig = { + astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), + etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), + dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), + bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), + pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) + || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) + }; + + + if ( renderer.capabilities.isWebGL2 ) { + + // https://github.com/mrdoob/three.js/pull/22928 + this.workerConfig.etc1Supported = false; + + } + + return this; + + } + + init() { + + if ( ! this.transcoderPending ) { + + // Load transcoder wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.transcoderPath ); + jsLoader.setWithCredentials( this.withCredentials ); + const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); + + // Load transcoder WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.transcoderPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + binaryLoader.setWithCredentials( this.withCredentials ); + const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); + + this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { + + const fn = KTX2Loader.BasisWorker.toString(); + + const body = [ + '/* constants */', + 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), + 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), + 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), + '/* basis_transcoder.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + this.transcoderBinary = binaryContent; + + this.workerPool.setWorkerCreator( () => { + + const worker = new Worker( this.workerSourceURL ); + const transcoderBinary = this.transcoderBinary.slice( 0 ); + + worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); + + return worker; + + } ); + + } ); + + if ( _activeLoaders > 0 ) { + + // Each instance loads a transcoder and allocates workers, increasing network and memory cost. + + console.warn( + + 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' + + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' + + ); + + } + + _activeLoaders ++; + + } + + return this.transcoderPending; + + } + + load( url, onLoad, onProgress, onError ) { + + if ( this.workerConfig === null ) { + + throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); + + } + + const loader = new FileLoader( this.manager ); + + loader.setResponseType( 'arraybuffer' ); + loader.setWithCredentials( this.withCredentials ); + + const texture = new CompressedTexture(); + + loader.load( url, ( buffer ) => { + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + return cachedTask.promise.then( onLoad ).catch( onError ); + + } + + this._createTexture( [ buffer ] ) + .then( function ( _texture ) { + + texture.copy( _texture ); + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } ) + .catch( onError ); + + }, onProgress, onError ); + + return texture; + + } + + _createTextureFrom( transcodeResult ) { + + const { mipmaps, width, height, format, type, error, dfdTransferFn, dfdFlags } = transcodeResult; + + if ( type === 'error' ) return Promise.reject( error ); + + const texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + texture.encoding = dfdTransferFn === KTX2TransferSRGB ? sRGBEncoding : LinearEncoding; + texture.premultiplyAlpha = !! ( dfdFlags & KTX2_ALPHA_PREMULTIPLIED ); + + return texture; + + } + + /** + * @param {ArrayBuffer[]} buffers + * @param {object?} config + * @return {Promise} + */ + _createTexture( buffers, config = {} ) { + + const taskConfig = config; + const texturePending = this.init().then( () => { + + return this.workerPool.postMessage( { type: 'transcode', buffers, taskConfig: taskConfig }, buffers ); + + } ).then( ( e ) => this._createTextureFrom( e.data ) ); + + // Cache the task result. + _taskCache.set( buffers[ 0 ], { promise: texturePending } ); + + return texturePending; + + } + + dispose() { + + this.workerPool.dispose(); + if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); + + _activeLoaders --; + + return this; + + } + +} + + +/* CONSTANTS */ + +KTX2Loader.BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, +}; + +KTX2Loader.TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, +}; + +KTX2Loader.EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, +}; + + +/* WEB WORKER */ + +KTX2Loader.BasisWorker = function () { + + let config; + let transcoderPending; + let BasisModule; + + const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + + self.addEventListener( 'message', function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; + + case 'transcode': + transcoderPending.then( () => { + + try { + + const { width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags } = transcode( message.buffers[ 0 ] ); + + const buffers = []; + + for ( let i = 0; i < mipmaps.length; ++ i ) { + + buffers.push( mipmaps[ i ].data.buffer ); + + } + + self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags }, buffers ); + + } catch ( error ) { + + console.error( error ); + + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } + + } ); + break; + + } + + } ); + + function init( wasmBinary ) { + + transcoderPending = new Promise( ( resolve ) => { + + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef + + } ).then( () => { + + BasisModule.initializeBasis(); + + if ( BasisModule.KTX2File === undefined ) { + + console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' ); + + } + + } ); + + } + + function transcode( buffer ) { + + const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); + + function cleanup() { + + ktx2File.close(); + ktx2File.delete(); + + } + + if ( ! ktx2File.isValid() ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid or unsupported .ktx2 file' ); + + } + + const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + const width = ktx2File.getWidth(); + const height = ktx2File.getHeight(); + const levels = ktx2File.getLevels(); + const hasAlpha = ktx2File.getHasAlpha(); + const dfdTransferFn = ktx2File.getDFDTransferFunc(); + const dfdFlags = ktx2File.getDFDFlags(); + + const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + + if ( ! width || ! height || ! levels ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: Invalid texture' ); + + } + + if ( ! ktx2File.startTranscoding() ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' ); + + } + + const mipmaps = []; + + for ( let mip = 0; mip < levels; mip ++ ) { + + const levelInfo = ktx2File.getImageLevelInfo( mip, 0, 0 ); + const mipWidth = levelInfo.origWidth; + const mipHeight = levelInfo.origHeight; + const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, 0, 0, transcoderFormat ) ); + + const status = ktx2File.transcodeImage( + dst, + mip, + 0, + 0, + transcoderFormat, + 0, + - 1, + - 1, + ); + + if ( ! status ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); + + } + + mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } ); + + } + + cleanup(); + + return { width, height, hasAlpha, mipmaps, format: engineFormat, dfdTransferFn, dfdFlags }; + + } + + // + + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], + engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ]; + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityETC1S - b.priorityETC1S; + + } ); + const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + + return a.priorityUASTC - b.priorityUASTC; + + } ); + + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + + let transcoderFormat; + let engineFormat; + + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + + for ( let i = 0; i < options.length; i ++ ) { + + const opt = options[ i ]; + + if ( ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + + transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + + return { transcoderFormat, engineFormat }; + + } + + console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); + + transcoderFormat = TranscoderFormat.RGBA32; + engineFormat = EngineFormat.RGBAFormat; + + return { transcoderFormat, engineFormat }; + + } + + function isPowerOfTwo( value ) { + + if ( value <= 2 ) return true; + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + + } + +}; + +export { KTX2Loader }; diff --git a/jsm/loaders/KTXLoader.js b/jsm/loaders/KTXLoader.js new file mode 100644 index 0000000..4e4e1c1 --- /dev/null +++ b/jsm/loaders/KTXLoader.js @@ -0,0 +1,176 @@ +import { + CompressedTextureLoader +} from 'three'; + +/** + * for description see https://www.khronos.org/opengles/sdk/tools/KTX/ + * for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + * + * ported from https://github.com/BabylonJS/Babylon.js/blob/master/src/Misc/khronosTextureContainer.ts + */ + + +class KTXLoader extends CompressedTextureLoader { + + constructor( manager ) { + + super( manager ); + + } + + parse( buffer, loadMipmaps ) { + + const ktx = new KhronosTextureContainer( buffer, 1 ); + + return { + mipmaps: ktx.mipmaps( loadMipmaps ), + width: ktx.pixelWidth, + height: ktx.pixelHeight, + format: ktx.glInternalFormat, + isCubemap: ktx.numberOfFaces === 6, + mipmapCount: ktx.numberOfMipmapLevels + }; + + } + +} + + +const HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs) +// load types +const COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() +//const COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D() +//const TEX_2D = 2; // uses a gl.texImage2D() +//const TEX_3D = 3; // uses a gl.texImage3D() + +class KhronosTextureContainer { + + /** + * @param {ArrayBuffer} arrayBuffer- contents of the KTX container file + * @param {number} facesExpected- should be either 1 or 6, based whether a cube texture or or + * @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented + * @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented + */ + constructor( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) { + + this.arrayBuffer = arrayBuffer; + + // Test that it is a ktx formatted file, based on the first 12 bytes, character representation is: + // '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n' + // 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A + const identifier = new Uint8Array( this.arrayBuffer, 0, 12 ); + if ( identifier[ 0 ] !== 0xAB || + identifier[ 1 ] !== 0x4B || + identifier[ 2 ] !== 0x54 || + identifier[ 3 ] !== 0x58 || + identifier[ 4 ] !== 0x20 || + identifier[ 5 ] !== 0x31 || + identifier[ 6 ] !== 0x31 || + identifier[ 7 ] !== 0xBB || + identifier[ 8 ] !== 0x0D || + identifier[ 9 ] !== 0x0A || + identifier[ 10 ] !== 0x1A || + identifier[ 11 ] !== 0x0A ) { + + console.error( 'texture missing KTX identifier' ); + return; + + } + + // load the reset of the header in native 32 bit uint + const dataSize = Uint32Array.BYTES_PER_ELEMENT; + const headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize ); + const endianness = headerDataView.getUint32( 0, true ); + const littleEndian = endianness === 0x04030201; + + this.glType = headerDataView.getUint32( 1 * dataSize, littleEndian ); // must be 0 for compressed textures + this.glTypeSize = headerDataView.getUint32( 2 * dataSize, littleEndian ); // must be 1 for compressed textures + this.glFormat = headerDataView.getUint32( 3 * dataSize, littleEndian ); // must be 0 for compressed textures + this.glInternalFormat = headerDataView.getUint32( 4 * dataSize, littleEndian ); // the value of arg passed to gl.compressedTexImage2D(,,x,,,,) + this.glBaseInternalFormat = headerDataView.getUint32( 5 * dataSize, littleEndian ); // specify GL_RGB, GL_RGBA, GL_ALPHA, etc (un-compressed only) + this.pixelWidth = headerDataView.getUint32( 6 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,x,,,) + this.pixelHeight = headerDataView.getUint32( 7 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,,x,,) + this.pixelDepth = headerDataView.getUint32( 8 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage3D(,,,,,x,,) + this.numberOfArrayElements = headerDataView.getUint32( 9 * dataSize, littleEndian ); // used for texture arrays + this.numberOfFaces = headerDataView.getUint32( 10 * dataSize, littleEndian ); // used for cubemap textures, should either be 1 or 6 + this.numberOfMipmapLevels = headerDataView.getUint32( 11 * dataSize, littleEndian ); // number of levels; disregard possibility of 0 for compressed textures + this.bytesOfKeyValueData = headerDataView.getUint32( 12 * dataSize, littleEndian ); // the amount of space after the header for meta-data + + // Make sure we have a compressed type. Not only reduces work, but probably better to let dev know they are not compressing. + if ( this.glType !== 0 ) { + + console.warn( 'only compressed formats currently supported' ); + return; + + } else { + + // value of zero is an indication to generate mipmaps @ runtime. Not usually allowed for compressed, so disregard. + this.numberOfMipmapLevels = Math.max( 1, this.numberOfMipmapLevels ); + + } + + if ( this.pixelHeight === 0 || this.pixelDepth !== 0 ) { + + console.warn( 'only 2D textures currently supported' ); + return; + + } + + if ( this.numberOfArrayElements !== 0 ) { + + console.warn( 'texture arrays not currently supported' ); + return; + + } + + if ( this.numberOfFaces !== facesExpected ) { + + console.warn( 'number of faces expected' + facesExpected + ', but found ' + this.numberOfFaces ); + return; + + } + + // we now have a completely validated file, so could use existence of loadType as success + // would need to make this more elaborate & adjust checks above to support more than one load type + this.loadType = COMPRESSED_2D; + + } + + mipmaps( loadMipmaps ) { + + const mipmaps = []; + + // initialize width & height for level 1 + let dataOffset = HEADER_LEN + this.bytesOfKeyValueData; + let width = this.pixelWidth; + let height = this.pixelHeight; + const mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1; + + for ( let level = 0; level < mipmapCount; level ++ ) { + + const imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps + dataOffset += 4; // size of the image + 4 for the imageSize field + + for ( let face = 0; face < this.numberOfFaces; face ++ ) { + + const byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize ); + + mipmaps.push( { 'data': byteArray, 'width': width, 'height': height } ); + + dataOffset += imageSize; + dataOffset += 3 - ( ( imageSize + 3 ) % 4 ); // add padding for odd sized image + + } + + width = Math.max( 1.0, width * 0.5 ); + height = Math.max( 1.0, height * 0.5 ); + + } + + return mipmaps; + + } + +} + +export { KTXLoader }; diff --git a/jsm/loaders/LDrawLoader.js b/jsm/loaders/LDrawLoader.js new file mode 100644 index 0000000..cb60c16 --- /dev/null +++ b/jsm/loaders/LDrawLoader.js @@ -0,0 +1,2403 @@ +import { + BufferAttribute, + BufferGeometry, + Color, + FileLoader, + Group, + LineBasicMaterial, + LineSegments, + Loader, + Matrix4, + Mesh, + MeshStandardMaterial, + ShaderMaterial, + UniformsLib, + UniformsUtils, + Vector3, + Ray +} from 'three'; + +// Special surface finish tag types. +// Note: "MATERIAL" tag (e.g. GLITTER, SPECKLE) is not implemented +const FINISH_TYPE_DEFAULT = 0; +const FINISH_TYPE_CHROME = 1; +const FINISH_TYPE_PEARLESCENT = 2; +const FINISH_TYPE_RUBBER = 3; +const FINISH_TYPE_MATTE_METALLIC = 4; +const FINISH_TYPE_METAL = 5; + +// State machine to search a subobject path. +// The LDraw standard establishes these various possible subfolders. +const FILE_LOCATION_AS_IS = 0; +const FILE_LOCATION_TRY_PARTS = 1; +const FILE_LOCATION_TRY_P = 2; +const FILE_LOCATION_TRY_MODELS = 3; +const FILE_LOCATION_TRY_RELATIVE = 4; +const FILE_LOCATION_TRY_ABSOLUTE = 5; +const FILE_LOCATION_NOT_FOUND = 6; + +const MAIN_COLOUR_CODE = '16'; +const MAIN_EDGE_COLOUR_CODE = '24'; + +const _tempVec0 = new Vector3(); +const _tempVec1 = new Vector3(); + +class LDrawConditionalLineMaterial extends ShaderMaterial { + + constructor( parameters ) { + + super( { + + uniforms: UniformsUtils.merge( [ + UniformsLib.fog, + { + diffuse: { + value: new Color() + }, + opacity: { + value: 1.0 + } + } + ] ), + + vertexShader: /* glsl */` + attribute vec3 control0; + attribute vec3 control1; + attribute vec3 direction; + varying float discardFlag; + + #include + #include + #include + #include + #include + void main() { + #include + + vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * mvPosition; + + // Transform the line segment ends and control points into camera clip space + vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 ); + vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 ); + vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 ); + + c0.xy /= c0.w; + c1.xy /= c1.w; + p0.xy /= p0.w; + p1.xy /= p1.w; + + // Get the direction of the segment and an orthogonal vector + vec2 dir = p1.xy - p0.xy; + vec2 norm = vec2( -dir.y, dir.x ); + + // Get control point directions from the line + vec2 c0dir = c0.xy - p1.xy; + vec2 c1dir = c1.xy - p1.xy; + + // If the vectors to the controls points are pointed in different directions away + // from the line segment then the line should not be drawn. + float d0 = dot( normalize( norm ), normalize( c0dir ) ); + float d1 = dot( normalize( norm ), normalize( c1dir ) ); + discardFlag = float( sign( d0 ) != sign( d1 ) ); + + #include + #include + #include + } + `, + + fragmentShader: /* glsl */` + uniform vec3 diffuse; + uniform float opacity; + varying float discardFlag; + + #include + #include + #include + #include + #include + void main() { + + if ( discardFlag > 0.5 ) discard; + + #include + vec3 outgoingLight = vec3( 0.0 ); + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + #include + outgoingLight = diffuseColor.rgb; // simple shader + gl_FragColor = vec4( outgoingLight, diffuseColor.a ); + #include + #include + #include + #include + } + `, + + } ); + + Object.defineProperties( this, { + + opacity: { + get: function () { + + return this.uniforms.opacity.value; + + }, + + set: function ( value ) { + + this.uniforms.opacity.value = value; + + } + }, + + color: { + get: function () { + + return this.uniforms.diffuse.value; + + } + } + + } ); + + this.setValues( parameters ); + this.isLDrawConditionalLineMaterial = true; + + } + +} + +class ConditionalLineSegments extends LineSegments { + + constructor( geometry, material ) { + + super( geometry, material ); + this.isConditionalLine = true; + + } + +} + +function generateFaceNormals( faces ) { + + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const face = faces[ i ]; + const vertices = face.vertices; + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + const v2 = vertices[ 2 ]; + + _tempVec0.subVectors( v1, v0 ); + _tempVec1.subVectors( v2, v1 ); + face.faceNormal = new Vector3() + .crossVectors( _tempVec0, _tempVec1 ) + .normalize(); + + } + +} + +const _ray = new Ray(); +function smoothNormals( faces, lineSegments, checkSubSegments = false ) { + + // NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because + // it allows edges to be smoothed as expected (see minifig arms). + // -- + // And the vector values are initialize multiplied by 1 + 1e-10 to account for floating + // point errors on vertices along quantization boundaries. Ie after matrix multiplication + // vertices that should be merged might be set to "1.7" and "1.6999..." meaning they won't + // get merged. This added epsilon attempts to push these error values to the same quantized + // value for the sake of hashing. See "AT-ST mini" dishes. See mrdoob/three#23169. + + const hashMultiplier = ( 1 + 1e-10 ) * 1e2; + function hashVertex( v ) { + + const x = ~ ~ ( v.x * hashMultiplier ); + const y = ~ ~ ( v.y * hashMultiplier ); + const z = ~ ~ ( v.z * hashMultiplier ); + + return `${ x },${ y },${ z }`; + + } + + function hashEdge( v0, v1 ) { + + return `${ hashVertex( v0 ) }_${ hashVertex( v1 ) }`; + + } + + // converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected + // onto the original line. + function toNormalizedRay( v0, v1, targetRay ) { + + targetRay.direction.subVectors( v1, v0 ).normalize(); + + const scalar = v0.dot( targetRay.direction ); + targetRay.origin.copy( v0 ).addScaledVector( targetRay.direction, - scalar ); + + return targetRay; + + } + + function hashRay( ray ) { + + return hashEdge( ray.origin, ray.direction ); + + } + + const hardEdges = new Set(); + const hardEdgeRays = new Map(); + const halfEdgeList = {}; + const normals = []; + + // Save the list of hard edges by hash + for ( let i = 0, l = lineSegments.length; i < l; i ++ ) { + + const ls = lineSegments[ i ]; + const vertices = ls.vertices; + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + hardEdges.add( hashEdge( v0, v1 ) ); + hardEdges.add( hashEdge( v1, v0 ) ); + + // only generate the hard edge ray map if we're checking subsegments because it's more expensive to check + // and requires more memory. + if ( checkSubSegments ) { + + // add both ray directions to the map + const ray = toNormalizedRay( v0, v1, new Ray() ); + const rh1 = hashRay( ray ); + if ( ! hardEdgeRays.has( rh1 ) ) { + + toNormalizedRay( v1, v0, ray ); + const rh2 = hashRay( ray ); + + const info = { + ray, + distances: [], + }; + + hardEdgeRays.set( rh1, info ); + hardEdgeRays.set( rh2, info ); + + } + + // store both segments ends in min, max order in the distances array to check if a face edge is a + // subsegment later. + const info = hardEdgeRays.get( rh1 ); + let d0 = info.ray.direction.dot( v0 ); + let d1 = info.ray.direction.dot( v1 ); + if ( d0 > d1 ) { + + [ d0, d1 ] = [ d1, d0 ]; + + } + + info.distances.push( d0, d1 ); + + } + + } + + // track the half edges associated with each triangle + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const tri = faces[ i ]; + const vertices = tri.vertices; + const vertCount = vertices.length; + for ( let i2 = 0; i2 < vertCount; i2 ++ ) { + + const index = i2; + const next = ( i2 + 1 ) % vertCount; + const v0 = vertices[ index ]; + const v1 = vertices[ next ]; + const hash = hashEdge( v0, v1 ); + + // don't add the triangle if the edge is supposed to be hard + if ( hardEdges.has( hash ) ) { + + continue; + + } + + // if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds + if ( checkSubSegments ) { + + toNormalizedRay( v0, v1, _ray ); + + const rayHash = hashRay( _ray ); + if ( hardEdgeRays.has( rayHash ) ) { + + const info = hardEdgeRays.get( rayHash ); + const { ray, distances } = info; + let d0 = ray.direction.dot( v0 ); + let d1 = ray.direction.dot( v1 ); + + if ( d0 > d1 ) { + + [ d0, d1 ] = [ d1, d0 ]; + + } + + // return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals + let found = false; + for ( let i = 0, l = distances.length; i < l; i += 2 ) { + + if ( d0 >= distances[ i ] && d1 <= distances[ i + 1 ] ) { + + found = true; + break; + + } + + } + + if ( found ) { + + continue; + + } + + } + + } + + const info = { + index: index, + tri: tri + }; + halfEdgeList[ hash ] = info; + + } + + } + + // Iterate until we've tried to connect all faces to share normals + while ( true ) { + + // Stop if there are no more faces left + let halfEdge = null; + for ( const key in halfEdgeList ) { + + halfEdge = halfEdgeList[ key ]; + break; + + } + + if ( halfEdge === null ) { + + break; + + } + + // Exhaustively find all connected faces + const queue = [ halfEdge ]; + while ( queue.length > 0 ) { + + // initialize all vertex normals in this triangle + const tri = queue.pop().tri; + const vertices = tri.vertices; + const vertNormals = tri.normals; + const faceNormal = tri.faceNormal; + + // Check if any edge is connected to another triangle edge + const vertCount = vertices.length; + for ( let i2 = 0; i2 < vertCount; i2 ++ ) { + + const index = i2; + const next = ( i2 + 1 ) % vertCount; + const v0 = vertices[ index ]; + const v1 = vertices[ next ]; + + // delete this triangle from the list so it won't be found again + const hash = hashEdge( v0, v1 ); + delete halfEdgeList[ hash ]; + + const reverseHash = hashEdge( v1, v0 ); + const otherInfo = halfEdgeList[ reverseHash ]; + if ( otherInfo ) { + + const otherTri = otherInfo.tri; + const otherIndex = otherInfo.index; + const otherNormals = otherTri.normals; + const otherVertCount = otherNormals.length; + const otherFaceNormal = otherTri.faceNormal; + + // NOTE: If the angle between faces is > 67.5 degrees then assume it's + // hard edge. There are some cases where the line segments do not line up exactly + // with or span multiple triangle edges (see Lunar Vehicle wheels). + if ( Math.abs( otherTri.faceNormal.dot( tri.faceNormal ) ) < 0.25 ) { + + continue; + + } + + // if this triangle has already been traversed then it won't be in + // the halfEdgeList. If it has not then add it to the queue and delete + // it so it won't be found again. + if ( reverseHash in halfEdgeList ) { + + queue.push( otherInfo ); + delete halfEdgeList[ reverseHash ]; + + } + + // share the first normal + const otherNext = ( otherIndex + 1 ) % otherVertCount; + if ( + vertNormals[ index ] && otherNormals[ otherNext ] && + vertNormals[ index ] !== otherNormals[ otherNext ] + ) { + + otherNormals[ otherNext ].norm.add( vertNormals[ index ].norm ); + vertNormals[ index ].norm = otherNormals[ otherNext ].norm; + + } + + let sharedNormal1 = vertNormals[ index ] || otherNormals[ otherNext ]; + if ( sharedNormal1 === null ) { + + // it's possible to encounter an edge of a triangle that has already been traversed meaning + // both edges already have different normals defined and shared. To work around this we create + // a wrapper object so when those edges are merged the normals can be updated everywhere. + sharedNormal1 = { norm: new Vector3() }; + normals.push( sharedNormal1.norm ); + + } + + if ( vertNormals[ index ] === null ) { + + vertNormals[ index ] = sharedNormal1; + sharedNormal1.norm.add( faceNormal ); + + } + + if ( otherNormals[ otherNext ] === null ) { + + otherNormals[ otherNext ] = sharedNormal1; + sharedNormal1.norm.add( otherFaceNormal ); + + } + + // share the second normal + if ( + vertNormals[ next ] && otherNormals[ otherIndex ] && + vertNormals[ next ] !== otherNormals[ otherIndex ] + ) { + + otherNormals[ otherIndex ].norm.add( vertNormals[ next ].norm ); + vertNormals[ next ].norm = otherNormals[ otherIndex ].norm; + + } + + let sharedNormal2 = vertNormals[ next ] || otherNormals[ otherIndex ]; + if ( sharedNormal2 === null ) { + + sharedNormal2 = { norm: new Vector3() }; + normals.push( sharedNormal2.norm ); + + } + + if ( vertNormals[ next ] === null ) { + + vertNormals[ next ] = sharedNormal2; + sharedNormal2.norm.add( faceNormal ); + + } + + if ( otherNormals[ otherIndex ] === null ) { + + otherNormals[ otherIndex ] = sharedNormal2; + sharedNormal2.norm.add( otherFaceNormal ); + + } + + } + + } + + } + + } + + // The normals of each face have been added up so now we average them by normalizing the vector. + for ( let i = 0, l = normals.length; i < l; i ++ ) { + + normals[ i ].normalize(); + + } + +} + +function isPartType( type ) { + + return type === 'Part' || type === 'Unofficial_Part'; + +} + +function isPrimitiveType( type ) { + + return /primitive/i.test( type ) || type === 'Subpart'; + +} + +class LineParser { + + constructor( line, lineNumber ) { + + this.line = line; + this.lineLength = line.length; + this.currentCharIndex = 0; + this.currentChar = ' '; + this.lineNumber = lineNumber; + + } + + seekNonSpace() { + + while ( this.currentCharIndex < this.lineLength ) { + + this.currentChar = this.line.charAt( this.currentCharIndex ); + + if ( this.currentChar !== ' ' && this.currentChar !== '\t' ) { + + return; + + } + + this.currentCharIndex ++; + + } + + } + + getToken() { + + const pos0 = this.currentCharIndex ++; + + // Seek space + while ( this.currentCharIndex < this.lineLength ) { + + this.currentChar = this.line.charAt( this.currentCharIndex ); + + if ( this.currentChar === ' ' || this.currentChar === '\t' ) { + + break; + + } + + this.currentCharIndex ++; + + } + + const pos1 = this.currentCharIndex; + + this.seekNonSpace(); + + return this.line.substring( pos0, pos1 ); + + } + + getVector() { + + return new Vector3( parseFloat( this.getToken() ), parseFloat( this.getToken() ), parseFloat( this.getToken() ) ); + + } + + getRemainingString() { + + return this.line.substring( this.currentCharIndex, this.lineLength ); + + } + + isAtTheEnd() { + + return this.currentCharIndex >= this.lineLength; + + } + + setToEnd() { + + this.currentCharIndex = this.lineLength; + + } + + getLineNumberString() { + + return this.lineNumber >= 0 ? ' at line ' + this.lineNumber : ''; + + } + +} + +// Fetches and parses an intermediate representation of LDraw parts files. +class LDrawParsedCache { + + constructor( loader ) { + + this.loader = loader; + this._cache = {}; + + } + + cloneResult( original ) { + + const result = {}; + + // vertices are transformed and normals computed before being converted to geometry + // so these pieces must be cloned. + result.faces = original.faces.map( face => { + + return { + colorCode: face.colorCode, + material: face.material, + vertices: face.vertices.map( v => v.clone() ), + normals: face.normals.map( () => null ), + faceNormal: null + }; + + } ); + + result.conditionalSegments = original.conditionalSegments.map( face => { + + return { + colorCode: face.colorCode, + material: face.material, + vertices: face.vertices.map( v => v.clone() ), + controlPoints: face.controlPoints.map( v => v.clone() ) + }; + + } ); + + result.lineSegments = original.lineSegments.map( face => { + + return { + colorCode: face.colorCode, + material: face.material, + vertices: face.vertices.map( v => v.clone() ) + }; + + } ); + + // none if this is subsequently modified + result.type = original.type; + result.category = original.category; + result.keywords = original.keywords; + result.subobjects = original.subobjects; + result.totalFaces = original.totalFaces; + result.startingConstructionStep = original.startingConstructionStep; + result.materials = original.materials; + result.group = null; + return result; + + } + + async fetchData( fileName ) { + + let triedLowerCase = false; + let locationState = FILE_LOCATION_AS_IS; + while ( locationState !== FILE_LOCATION_NOT_FOUND ) { + + let subobjectURL = fileName; + switch ( locationState ) { + + case FILE_LOCATION_AS_IS: + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_PARTS: + subobjectURL = 'parts/' + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_P: + subobjectURL = 'p/' + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_MODELS: + subobjectURL = 'models/' + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_RELATIVE: + subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_ABSOLUTE: + + if ( triedLowerCase ) { + + // Try absolute path + locationState = FILE_LOCATION_NOT_FOUND; + + } else { + + // Next attempt is lower case + fileName = fileName.toLowerCase(); + subobjectURL = fileName; + triedLowerCase = true; + locationState = FILE_LOCATION_AS_IS; + + } + + break; + + } + + const loader = this.loader; + const fileLoader = new FileLoader( loader.manager ); + fileLoader.setPath( loader.partsLibraryPath ); + fileLoader.setRequestHeader( loader.requestHeader ); + fileLoader.setWithCredentials( loader.withCredentials ); + + try { + + const text = await fileLoader.loadAsync( subobjectURL ); + return text; + + } catch { + + continue; + + } + + } + + throw new Error( 'LDrawLoader: Subobject "' + fileName + '" could not be loaded.' ); + + } + + parse( text, fileName = null ) { + + const loader = this.loader; + + // final results + const faces = []; + const lineSegments = []; + const conditionalSegments = []; + const subobjects = []; + const materials = {}; + + const getLocalMaterial = colorCode => { + + return materials[ colorCode ] || null; + + }; + + let type = 'Model'; + let category = null; + let keywords = null; + let totalFaces = 0; + + // split into lines + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + const lines = text.split( '\n' ); + const numLines = lines.length; + + let parsingEmbeddedFiles = false; + let currentEmbeddedFileName = null; + let currentEmbeddedText = null; + + let bfcCertified = false; + let bfcCCW = true; + let bfcInverted = false; + let bfcCull = true; + + let startingConstructionStep = false; + + // Parse all line commands + for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) { + + const line = lines[ lineIndex ]; + + if ( line.length === 0 ) continue; + + if ( parsingEmbeddedFiles ) { + + if ( line.startsWith( '0 FILE ' ) ) { + + // Save previous embedded file in the cache + this.setData( currentEmbeddedFileName, currentEmbeddedText ); + + // New embedded text file + currentEmbeddedFileName = line.substring( 7 ); + currentEmbeddedText = ''; + + } else { + + currentEmbeddedText += line + '\n'; + + } + + continue; + + } + + const lp = new LineParser( line, lineIndex + 1 ); + lp.seekNonSpace(); + + if ( lp.isAtTheEnd() ) { + + // Empty line + continue; + + } + + // Parse the line type + const lineType = lp.getToken(); + + let material; + let colorCode; + let segment; + let ccw; + let doubleSided; + let v0, v1, v2, v3, c0, c1; + + switch ( lineType ) { + + // Line type 0: Comment or META + case '0': + + // Parse meta directive + const meta = lp.getToken(); + + if ( meta ) { + + switch ( meta ) { + + case '!LDRAW_ORG': + + type = lp.getToken(); + break; + + case '!COLOUR': + + material = loader.parseColorMetaDirective( lp ); + if ( material ) { + + materials[ material.userData.code ] = material; + + } else { + + console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() ); + + } + + break; + + case '!CATEGORY': + + category = lp.getToken(); + break; + + case '!KEYWORDS': + + const newKeywords = lp.getRemainingString().split( ',' ); + if ( newKeywords.length > 0 ) { + + if ( ! keywords ) { + + keywords = []; + + } + + newKeywords.forEach( function ( keyword ) { + + keywords.push( keyword.trim() ); + + } ); + + } + + break; + + case 'FILE': + + if ( lineIndex > 0 ) { + + // Start embedded text files parsing + parsingEmbeddedFiles = true; + currentEmbeddedFileName = lp.getRemainingString(); + currentEmbeddedText = ''; + + bfcCertified = false; + bfcCCW = true; + + } + + break; + + case 'BFC': + + // Changes to the backface culling state + while ( ! lp.isAtTheEnd() ) { + + const token = lp.getToken(); + + switch ( token ) { + + case 'CERTIFY': + case 'NOCERTIFY': + + bfcCertified = token === 'CERTIFY'; + bfcCCW = true; + + break; + + case 'CW': + case 'CCW': + + bfcCCW = token === 'CCW'; + + break; + + case 'INVERTNEXT': + + bfcInverted = true; + + break; + + case 'CLIP': + case 'NOCLIP': + + bfcCull = token === 'CLIP'; + + break; + + default: + + console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' ); + + break; + + } + + } + + break; + + case 'STEP': + + startingConstructionStep = true; + + break; + + default: + // Other meta directives are not implemented + break; + + } + + } + + break; + + // Line type 1: Sub-object file + case '1': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + + const posX = parseFloat( lp.getToken() ); + const posY = parseFloat( lp.getToken() ); + const posZ = parseFloat( lp.getToken() ); + const m0 = parseFloat( lp.getToken() ); + const m1 = parseFloat( lp.getToken() ); + const m2 = parseFloat( lp.getToken() ); + const m3 = parseFloat( lp.getToken() ); + const m4 = parseFloat( lp.getToken() ); + const m5 = parseFloat( lp.getToken() ); + const m6 = parseFloat( lp.getToken() ); + const m7 = parseFloat( lp.getToken() ); + const m8 = parseFloat( lp.getToken() ); + + const matrix = new Matrix4().set( + m0, m1, m2, posX, + m3, m4, m5, posY, + m6, m7, m8, posZ, + 0, 0, 0, 1 + ); + + let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' ); + + if ( loader.fileMap[ fileName ] ) { + + // Found the subobject path in the preloaded file path map + fileName = loader.fileMap[ fileName ]; + + } else { + + // Standardized subfolders + if ( fileName.startsWith( 's/' ) ) { + + fileName = 'parts/' + fileName; + + } else if ( fileName.startsWith( '48/' ) ) { + + fileName = 'p/' + fileName; + + } + + } + + subobjects.push( { + material: material, + colorCode: colorCode, + matrix: matrix, + fileName: fileName, + inverted: bfcInverted, + startingConstructionStep: startingConstructionStep + } ); + + bfcInverted = false; + + break; + + // Line type 2: Line segment + case '2': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + v0 = lp.getVector(); + v1 = lp.getVector(); + + segment = { + material: material, + colorCode: colorCode, + vertices: [ v0, v1 ], + }; + + lineSegments.push( segment ); + + break; + + // Line type 5: Conditional Line segment + case '5': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + v0 = lp.getVector(); + v1 = lp.getVector(); + c0 = lp.getVector(); + c1 = lp.getVector(); + + segment = { + material: material, + colorCode: colorCode, + vertices: [ v0, v1 ], + controlPoints: [ c0, c1 ], + }; + + conditionalSegments.push( segment ); + + break; + + // Line type 3: Triangle + case '3': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + ccw = bfcCCW; + doubleSided = ! bfcCertified || ! bfcCull; + + if ( ccw === true ) { + + v0 = lp.getVector(); + v1 = lp.getVector(); + v2 = lp.getVector(); + + } else { + + v2 = lp.getVector(); + v1 = lp.getVector(); + v0 = lp.getVector(); + + } + + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v0, v1, v2 ], + normals: [ null, null, null ], + } ); + totalFaces ++; + + if ( doubleSided === true ) { + + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v2, v1, v0 ], + normals: [ null, null, null ], + } ); + totalFaces ++; + + } + + break; + + // Line type 4: Quadrilateral + case '4': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + ccw = bfcCCW; + doubleSided = ! bfcCertified || ! bfcCull; + + if ( ccw === true ) { + + v0 = lp.getVector(); + v1 = lp.getVector(); + v2 = lp.getVector(); + v3 = lp.getVector(); + + } else { + + v3 = lp.getVector(); + v2 = lp.getVector(); + v1 = lp.getVector(); + v0 = lp.getVector(); + + } + + // specifically place the triangle diagonal in the v0 and v1 slots so we can + // account for the doubling of vertices later when smoothing normals. + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v0, v1, v2, v3 ], + normals: [ null, null, null, null ], + } ); + totalFaces += 2; + + if ( doubleSided === true ) { + + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v3, v2, v1, v0 ], + normals: [ null, null, null, null ], + } ); + totalFaces += 2; + + } + + break; + + default: + throw new Error( 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.' ); + + } + + } + + if ( parsingEmbeddedFiles ) { + + this.setData( currentEmbeddedFileName, currentEmbeddedText ); + + } + + return { + faces, + conditionalSegments, + lineSegments, + type, + category, + keywords, + subobjects, + totalFaces, + startingConstructionStep, + materials, + fileName, + group: null + }; + + } + + // returns an (optionally cloned) instance of the data + getData( fileName, clone = true ) { + + const key = fileName.toLowerCase(); + const result = this._cache[ key ]; + if ( result === null || result instanceof Promise ) { + + return null; + + } + + if ( clone ) { + + return this.cloneResult( result ); + + } else { + + return result; + + } + + } + + // kicks off a fetch and parse of the requested data if it hasn't already been loaded. Returns when + // the data is ready to use and can be retrieved synchronously with "getData". + async ensureDataLoaded( fileName ) { + + const key = fileName.toLowerCase(); + if ( ! ( key in this._cache ) ) { + + // replace the promise with a copy of the parsed data for immediate processing + this._cache[ key ] = this.fetchData( fileName ).then( text => { + + const info = this.parse( text, fileName ); + this._cache[ key ] = info; + return info; + + } ); + + } + + await this._cache[ key ]; + + } + + // sets the data in the cache from parsed data + setData( fileName, text ) { + + const key = fileName.toLowerCase(); + this._cache[ key ] = this.parse( text, fileName ); + + } + +} + +// returns the material for an associated color code. If the color code is 16 for a face or 24 for +// an edge then the passthroughColorCode is used. +function getMaterialFromCode( colorCode, parentColorCode, materialHierarchy, forEdge ) { + + const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE; + if ( isPassthrough ) { + + colorCode = parentColorCode; + + } + + return materialHierarchy[ colorCode ] || null; + +} + +// Class used to parse and build LDraw parts as three.js objects and cache them if they're a "Part" type. +class LDrawPartsGeometryCache { + + constructor( loader ) { + + this.loader = loader; + this.parseCache = new LDrawParsedCache( loader ); + this._cache = {}; + + } + + // Convert the given file information into a mesh by processing subobjects. + async processIntoMesh( info ) { + + const loader = this.loader; + const parseCache = this.parseCache; + const faceMaterials = new Set(); + + // Processes the part subobject information to load child parts and merge geometry onto part + // piece object. + const processInfoSubobjects = async ( info, subobject = null ) => { + + const subobjects = info.subobjects; + const promises = []; + + // Trigger load of all subobjects. If a subobject isn't a primitive then load it as a separate + // group which lets instruction steps apply correctly. + for ( let i = 0, l = subobjects.length; i < l; i ++ ) { + + const subobject = subobjects[ i ]; + const promise = parseCache.ensureDataLoaded( subobject.fileName ).then( () => { + + const subobjectInfo = parseCache.getData( subobject.fileName, false ); + if ( ! isPrimitiveType( subobjectInfo.type ) ) { + + return this.loadModel( subobject.fileName ).catch( error => { + + console.warn( error ); + return null; + + } ); + + } + + return processInfoSubobjects( parseCache.getData( subobject.fileName ), subobject ); + + } ); + + promises.push( promise ); + + } + + const group = new Group(); + group.userData.category = info.category; + group.userData.keywords = info.keywords; + info.group = group; + + const subobjectInfos = await Promise.all( promises ); + for ( let i = 0, l = subobjectInfos.length; i < l; i ++ ) { + + const subobject = info.subobjects[ i ]; + const subobjectInfo = subobjectInfos[ i ]; + + if ( subobjectInfo === null ) { + + // the subobject failed to load + continue; + + } + + // if the subobject was loaded as a separate group then apply the parent scopes materials + if ( subobjectInfo.isGroup ) { + + const subobjectGroup = subobjectInfo; + subobject.matrix.decompose( subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale ); + subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep; + subobjectGroup.name = subobject.fileName; + + loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials ); + + group.add( subobjectGroup ); + continue; + + } + + // add the subobject group if it has children in case it has both children and primitives + if ( subobjectInfo.group.children.length ) { + + group.add( subobjectInfo.group ); + + } + + // transform the primitives into the local space of the parent piece and append them to + // to the parent primitives list. + const parentLineSegments = info.lineSegments; + const parentConditionalSegments = info.conditionalSegments; + const parentFaces = info.faces; + + const lineSegments = subobjectInfo.lineSegments; + const conditionalSegments = subobjectInfo.conditionalSegments; + + const faces = subobjectInfo.faces; + const matrix = subobject.matrix; + const inverted = subobject.inverted; + const matrixScaleInverted = matrix.determinant() < 0; + const colorCode = subobject.colorCode; + + const lineColorCode = colorCode === MAIN_COLOUR_CODE ? MAIN_EDGE_COLOUR_CODE : colorCode; + for ( let i = 0, l = lineSegments.length; i < l; i ++ ) { + + const ls = lineSegments[ i ]; + const vertices = ls.vertices; + vertices[ 0 ].applyMatrix4( matrix ); + vertices[ 1 ].applyMatrix4( matrix ); + ls.colorCode = ls.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : ls.colorCode; + ls.material = ls.material || getMaterialFromCode( ls.colorCode, ls.colorCode, info.materials, true ); + + parentLineSegments.push( ls ); + + } + + for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) { + + const os = conditionalSegments[ i ]; + const vertices = os.vertices; + const controlPoints = os.controlPoints; + vertices[ 0 ].applyMatrix4( matrix ); + vertices[ 1 ].applyMatrix4( matrix ); + controlPoints[ 0 ].applyMatrix4( matrix ); + controlPoints[ 1 ].applyMatrix4( matrix ); + os.colorCode = os.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : os.colorCode; + os.material = os.material || getMaterialFromCode( os.colorCode, os.colorCode, info.materials, true ); + + parentConditionalSegments.push( os ); + + } + + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const tri = faces[ i ]; + const vertices = tri.vertices; + for ( let i = 0, l = vertices.length; i < l; i ++ ) { + + vertices[ i ].applyMatrix4( matrix ); + + } + + tri.colorCode = tri.colorCode === MAIN_COLOUR_CODE ? colorCode : tri.colorCode; + tri.material = tri.material || getMaterialFromCode( tri.colorCode, colorCode, info.materials, false ); + faceMaterials.add( tri.colorCode ); + + // If the scale of the object is negated then the triangle winding order + // needs to be flipped. + if ( matrixScaleInverted !== inverted ) { + + vertices.reverse(); + + } + + parentFaces.push( tri ); + + } + + info.totalFaces += subobjectInfo.totalFaces; + + } + + // Apply the parent subobjects pass through material code to this object. This is done several times due + // to material scoping. + if ( subobject ) { + + loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials ); + + } + + return info; + + }; + + // Track material use to see if we need to use the normal smooth slow path for hard edges. + for ( let i = 0, l = info.faces; i < l; i ++ ) { + + faceMaterials.add( info.faces[ i ].colorCode ); + + } + + await processInfoSubobjects( info ); + + if ( loader.smoothNormals ) { + + const checkSubSegments = faceMaterials.size > 1; + generateFaceNormals( info.faces ); + smoothNormals( info.faces, info.lineSegments, checkSubSegments ); + + } + + // Add the primitive objects and metadata. + const group = info.group; + if ( info.faces.length > 0 ) { + + group.add( createObject( info.faces, 3, false, info.totalFaces ) ); + + } + + if ( info.lineSegments.length > 0 ) { + + group.add( createObject( info.lineSegments, 2 ) ); + + } + + if ( info.conditionalSegments.length > 0 ) { + + group.add( createObject( info.conditionalSegments, 2, true ) ); + + } + + return group; + + } + + hasCachedModel( fileName ) { + + return fileName !== null && fileName.toLowerCase() in this._cache; + + } + + async getCachedModel( fileName ) { + + if ( fileName !== null && this.hasCachedModel( fileName ) ) { + + const key = fileName.toLowerCase(); + const group = await this._cache[ key ]; + return group.clone(); + + } else { + + return null; + + } + + } + + // Loads and parses the model with the given file name. Returns a cached copy if available. + async loadModel( fileName ) { + + const parseCache = this.parseCache; + const key = fileName.toLowerCase(); + if ( this.hasCachedModel( fileName ) ) { + + // Return cached model if available. + return this.getCachedModel( fileName ); + + } else { + + // Otherwise parse a new model. + // Ensure the file data is loaded and pre parsed. + await parseCache.ensureDataLoaded( fileName ); + + const info = parseCache.getData( fileName ); + const promise = this.processIntoMesh( info ); + + // Now that the file has loaded it's possible that another part parse has been waiting in parallel + // so check the cache again to see if it's been added since the last async operation so we don't + // do unnecessary work. + if ( this.hasCachedModel( fileName ) ) { + + return this.getCachedModel( fileName ); + + } + + // Cache object if it's a part so it can be reused later. + if ( isPartType( info.type ) ) { + + this._cache[ key ] = promise; + + } + + // return a copy + const group = await promise; + return group.clone(); + + } + + } + + // parses the given model text into a renderable object. Returns cached copy if available. + async parseModel( text ) { + + const parseCache = this.parseCache; + const info = parseCache.parse( text ); + if ( isPartType( info.type ) && this.hasCachedModel( info.fileName ) ) { + + return this.getCachedModel( info.fileName ); + + } + + return this.processIntoMesh( info ); + + } + +} + +function sortByMaterial( a, b ) { + + if ( a.colorCode === b.colorCode ) { + + return 0; + + } + + if ( a.colorCode < b.colorCode ) { + + return - 1; + + } + + return 1; + +} + +function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) { + + // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 ) + // With per face / segment material, implemented with mesh groups and materials array + + // Sort the faces or line segments by color code to make later the mesh groups + elements.sort( sortByMaterial ); + + if ( totalElements === null ) { + + totalElements = elements.length; + + } + + const positions = new Float32Array( elementSize * totalElements * 3 ); + const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null; + const materials = []; + + const quadArray = new Array( 6 ); + const bufferGeometry = new BufferGeometry(); + let prevMaterial = null; + let index0 = 0; + let numGroupVerts = 0; + let offset = 0; + + for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) { + + const elem = elements[ iElem ]; + let vertices = elem.vertices; + if ( vertices.length === 4 ) { + + quadArray[ 0 ] = vertices[ 0 ]; + quadArray[ 1 ] = vertices[ 1 ]; + quadArray[ 2 ] = vertices[ 2 ]; + quadArray[ 3 ] = vertices[ 0 ]; + quadArray[ 4 ] = vertices[ 2 ]; + quadArray[ 5 ] = vertices[ 3 ]; + vertices = quadArray; + + } + + for ( let j = 0, l = vertices.length; j < l; j ++ ) { + + const v = vertices[ j ]; + const index = offset + j * 3; + positions[ index + 0 ] = v.x; + positions[ index + 1 ] = v.y; + positions[ index + 2 ] = v.z; + + } + + // create the normals array if this is a set of faces + if ( elementSize === 3 ) { + + if ( ! elem.faceNormal ) { + + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + const v2 = vertices[ 2 ]; + _tempVec0.subVectors( v1, v0 ); + _tempVec1.subVectors( v2, v1 ); + elem.faceNormal = new Vector3() + .crossVectors( _tempVec0, _tempVec1 ) + .normalize(); + + } + + let elemNormals = elem.normals; + if ( elemNormals.length === 4 ) { + + quadArray[ 0 ] = elemNormals[ 0 ]; + quadArray[ 1 ] = elemNormals[ 1 ]; + quadArray[ 2 ] = elemNormals[ 2 ]; + quadArray[ 3 ] = elemNormals[ 0 ]; + quadArray[ 4 ] = elemNormals[ 2 ]; + quadArray[ 5 ] = elemNormals[ 3 ]; + elemNormals = quadArray; + + } + + for ( let j = 0, l = elemNormals.length; j < l; j ++ ) { + + // use face normal if a vertex normal is not provided + let n = elem.faceNormal; + if ( elemNormals[ j ] ) { + + n = elemNormals[ j ].norm; + + } + + const index = offset + j * 3; + normals[ index + 0 ] = n.x; + normals[ index + 1 ] = n.y; + normals[ index + 2 ] = n.z; + + } + + } + + if ( prevMaterial !== elem.colorCode ) { + + if ( prevMaterial !== null ) { + + bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 ); + + } + + const material = elem.material; + + if ( material !== null ) { + + if ( elementSize === 3 ) { + + materials.push( material ); + + } else if ( elementSize === 2 ) { + + if ( isConditionalSegments ) { + + materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial ); + + } else { + + materials.push( material.userData.edgeMaterial ); + + } + + } + + } else { + + // If a material has not been made available yet then keep the color code string in the material array + // to save the spot for the material once a parent scopes materials are being applied to the object. + materials.push( elem.colorCode ); + + } + + prevMaterial = elem.colorCode; + index0 = offset / 3; + numGroupVerts = vertices.length; + + } else { + + numGroupVerts += vertices.length; + + } + + offset += 3 * vertices.length; + + } + + if ( numGroupVerts > 0 ) { + + bufferGeometry.addGroup( index0, Infinity, materials.length - 1 ); + + } + + bufferGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); + + if ( normals !== null ) { + + bufferGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); + + } + + let object3d = null; + + if ( elementSize === 2 ) { + + if ( isConditionalSegments ) { + + object3d = new ConditionalLineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); + + } else { + + object3d = new LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); + + } + + } else if ( elementSize === 3 ) { + + object3d = new Mesh( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); + + } + + if ( isConditionalSegments ) { + + object3d.isConditionalLine = true; + + const controlArray0 = new Float32Array( elements.length * 3 * 2 ); + const controlArray1 = new Float32Array( elements.length * 3 * 2 ); + const directionArray = new Float32Array( elements.length * 3 * 2 ); + for ( let i = 0, l = elements.length; i < l; i ++ ) { + + const os = elements[ i ]; + const vertices = os.vertices; + const controlPoints = os.controlPoints; + const c0 = controlPoints[ 0 ]; + const c1 = controlPoints[ 1 ]; + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + const index = i * 3 * 2; + controlArray0[ index + 0 ] = c0.x; + controlArray0[ index + 1 ] = c0.y; + controlArray0[ index + 2 ] = c0.z; + controlArray0[ index + 3 ] = c0.x; + controlArray0[ index + 4 ] = c0.y; + controlArray0[ index + 5 ] = c0.z; + + controlArray1[ index + 0 ] = c1.x; + controlArray1[ index + 1 ] = c1.y; + controlArray1[ index + 2 ] = c1.z; + controlArray1[ index + 3 ] = c1.x; + controlArray1[ index + 4 ] = c1.y; + controlArray1[ index + 5 ] = c1.z; + + directionArray[ index + 0 ] = v1.x - v0.x; + directionArray[ index + 1 ] = v1.y - v0.y; + directionArray[ index + 2 ] = v1.z - v0.z; + directionArray[ index + 3 ] = v1.x - v0.x; + directionArray[ index + 4 ] = v1.y - v0.y; + directionArray[ index + 5 ] = v1.z - v0.z; + + } + + bufferGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) ); + bufferGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) ); + bufferGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) ); + + } + + return object3d; + +} + +// + +class LDrawLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + // Array of THREE.Material + this.materials = []; + this.materialLibrary = {}; + + // This also allows to handle the embedded text files ("0 FILE" lines) + this.partsCache = new LDrawPartsGeometryCache( this ); + + // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error. + this.fileMap = {}; + + // Initializes the materials library with default materials + this.setMaterials( [] ); + + // If this flag is set to true the vertex normals will be smoothed. + this.smoothNormals = true; + + // The path to load parts from the LDraw parts library from. + this.partsLibraryPath = ''; + + } + + setPartsLibraryPath( path ) { + + this.partsLibraryPath = path; + return this; + + } + + async preloadMaterials( url ) { + + const fileLoader = new FileLoader( this.manager ); + fileLoader.setPath( this.path ); + fileLoader.setRequestHeader( this.requestHeader ); + fileLoader.setWithCredentials( this.withCredentials ); + + const text = await fileLoader.loadAsync( url ); + const colorLineRegex = /^0 !COLOUR/; + const lines = text.split( /[\n\r]/g ); + const materials = []; + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + const line = lines[ i ]; + if ( colorLineRegex.test( line ) ) { + + const directive = line.replace( colorLineRegex, '' ); + const material = this.parseColorMetaDirective( new LineParser( directive ) ); + materials.push( material ); + + } + + } + + this.setMaterials( materials ); + + } + + load( url, onLoad, onProgress, onError ) { + + const fileLoader = new FileLoader( this.manager ); + fileLoader.setPath( this.path ); + fileLoader.setRequestHeader( this.requestHeader ); + fileLoader.setWithCredentials( this.withCredentials ); + fileLoader.load( url, text => { + + this.partsCache + .parseModel( text, this.materialLibrary ) + .then( group => { + + this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true ); + this.computeConstructionSteps( group ); + onLoad( group ); + + } ) + .catch( onError ); + + }, onProgress, onError ); + + } + + parse( text, onLoad ) { + + this.partsCache + .parseModel( text, this.materialLibrary ) + .then( group => { + + this.computeConstructionSteps( group ); + onLoad( group ); + + } ); + + } + + setMaterials( materials ) { + + this.materialLibrary = {}; + this.materials = []; + for ( let i = 0, l = materials.length; i < l; i ++ ) { + + this.addMaterial( materials[ i ] ); + + } + + // Add default main triangle and line edge materials (used in pieces that can be colored with a main color) + this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ) ); + this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ); + + return this; + + } + + setFileMap( fileMap ) { + + this.fileMap = fileMap; + + return this; + + } + + addMaterial( material ) { + + // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array + + const matLib = this.materialLibrary; + if ( ! matLib[ material.userData.code ] ) { + + this.materials.push( material ); + matLib[ material.userData.code ] = material; + + } + + return this; + + } + + getMaterial( colorCode ) { + + if ( colorCode.startsWith( '0x2' ) ) { + + // Special 'direct' material value (RGB color) + const color = colorCode.substring( 3 ); + + return this.parseColorMetaDirective( new LineParser( 'Direct_Color_' + color + ' CODE -1 VALUE #' + color + ' EDGE #' + color + '' ) ); + + } + + return this.materialLibrary[ colorCode ] || null; + + } + + // Applies the appropriate materials to a prebuilt hierarchy of geometry. Assumes that color codes are present + // in the material array if they need to be filled in. + applyMaterialsToMesh( group, parentColorCode, materialHierarchy, finalMaterialPass = false ) { + + // find any missing materials as indicated by a color code string and replace it with a material from the current material lib + const loader = this; + const parentIsPassthrough = parentColorCode === MAIN_COLOUR_CODE; + group.traverse( c => { + + if ( c.isMesh || c.isLineSegments ) { + + if ( Array.isArray( c.material ) ) { + + for ( let i = 0, l = c.material.length; i < l; i ++ ) { + + if ( ! c.material[ i ].isMaterial ) { + + c.material[ i ] = getMaterial( c, c.material[ i ] ); + + } + + } + + } else if ( ! c.material.isMaterial ) { + + c.material = getMaterial( c, c.material ); + + } + + } + + } ); + + + // Returns the appropriate material for the object (line or face) given color code. If the code is "pass through" + // (24 for lines, 16 for edges) then the pass through color code is used. If that is also pass through then it's + // simply returned for the subsequent material application. + function getMaterial( c, colorCode ) { + + // if our parent is a passthrough color code and we don't have the current material color available then + // return early. + if ( parentIsPassthrough && ! ( colorCode in materialHierarchy ) && ! finalMaterialPass ) { + + return colorCode; + + } + + const forEdge = c.isLineSegments || c.isConditionalLine; + const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE; + if ( isPassthrough ) { + + colorCode = parentColorCode; + + } + + let material = null; + if ( colorCode in materialHierarchy ) { + + material = materialHierarchy[ colorCode ]; + + } else if ( finalMaterialPass ) { + + // see if we can get the final material from from the "getMaterial" function which will attempt to + // parse the "direct" colors + material = loader.getMaterial( colorCode ); + if ( material === null ) { + + // otherwise throw an error if this is final opportunity to set the material + throw new Error( `LDrawLoader: Material properties for code ${ colorCode } not available.` ); + + } + + + } else { + + return colorCode; + + } + + if ( c.isLineSegments ) { + + material = material.userData.edgeMaterial; + + if ( c.isConditionalLine ) { + + material = material.userData.conditionalEdgeMaterial; + + } + + } + + return material; + + } + + } + + getMainMaterial() { + + return this.getMaterial( MAIN_COLOUR_CODE ); + + } + + getMainEdgeMaterial() { + + const mainMat = this.getMainMaterial(); + return mainMat && mainMat.userData ? mainMat.userData.edgeMaterial : null; + + } + + parseColorMetaDirective( lineParser ) { + + // Parses a color definition and returns a THREE.Material + + let code = null; + + // Triangle and line colors + let color = 0xFF00FF; + let edgeColor = 0xFF00FF; + + // Transparency + let alpha = 1; + let isTransparent = false; + // Self-illumination: + let luminance = 0; + + let finishType = FINISH_TYPE_DEFAULT; + + let edgeMaterial = null; + + const name = lineParser.getToken(); + if ( ! name ) { + + throw new Error( 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.' ); + + } + + // Parse tag tokens and their parameters + let token = null; + while ( true ) { + + token = lineParser.getToken(); + + if ( ! token ) { + + break; + + } + + switch ( token.toUpperCase() ) { + + case 'CODE': + + code = lineParser.getToken(); + break; + + case 'VALUE': + + color = lineParser.getToken(); + if ( color.startsWith( '0x' ) ) { + + color = '#' + color.substring( 2 ); + + } else if ( ! color.startsWith( '#' ) ) { + + throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' ); + + } + + break; + + case 'EDGE': + + edgeColor = lineParser.getToken(); + if ( edgeColor.startsWith( '0x' ) ) { + + edgeColor = '#' + edgeColor.substring( 2 ); + + } else if ( ! edgeColor.startsWith( '#' ) ) { + + // Try to see if edge color is a color code + edgeMaterial = this.getMaterial( edgeColor ); + if ( ! edgeMaterial ) { + + throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' ); + + } + + // Get the edge material for this triangle material + edgeMaterial = edgeMaterial.userData.edgeMaterial; + + } + + break; + + case 'ALPHA': + + alpha = parseInt( lineParser.getToken() ); + + if ( isNaN( alpha ) ) { + + throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' ); + + } + + alpha = Math.max( 0, Math.min( 1, alpha / 255 ) ); + + if ( alpha < 1 ) { + + isTransparent = true; + + } + + break; + + case 'LUMINANCE': + + luminance = parseInt( lineParser.getToken() ); + + if ( isNaN( luminance ) ) { + + throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' ); + + } + + luminance = Math.max( 0, Math.min( 1, luminance / 255 ) ); + + break; + + case 'CHROME': + finishType = FINISH_TYPE_CHROME; + break; + + case 'PEARLESCENT': + finishType = FINISH_TYPE_PEARLESCENT; + break; + + case 'RUBBER': + finishType = FINISH_TYPE_RUBBER; + break; + + case 'MATTE_METALLIC': + finishType = FINISH_TYPE_MATTE_METALLIC; + break; + + case 'METAL': + finishType = FINISH_TYPE_METAL; + break; + + case 'MATERIAL': + // Not implemented + lineParser.setToEnd(); + break; + + default: + throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' ); + + } + + } + + let material = null; + + switch ( finishType ) { + + case FINISH_TYPE_DEFAULT: + + material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0 } ); + break; + + case FINISH_TYPE_PEARLESCENT: + + // Try to imitate pearlescency by making the surface glossy + material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0.25 } ); + break; + + case FINISH_TYPE_CHROME: + + // Mirror finish surface + material = new MeshStandardMaterial( { color: color, roughness: 0, metalness: 1 } ); + break; + + case FINISH_TYPE_RUBBER: + + // Rubber finish + material = new MeshStandardMaterial( { color: color, roughness: 0.9, metalness: 0 } ); + break; + + case FINISH_TYPE_MATTE_METALLIC: + + // Brushed metal finish + material = new MeshStandardMaterial( { color: color, roughness: 0.8, metalness: 0.4 } ); + break; + + case FINISH_TYPE_METAL: + + // Average metal finish + material = new MeshStandardMaterial( { color: color, roughness: 0.2, metalness: 0.85 } ); + break; + + default: + // Should not happen + break; + + } + + material.transparent = isTransparent; + material.premultipliedAlpha = true; + material.opacity = alpha; + material.depthWrite = ! isTransparent; + material.color.convertSRGBToLinear(); + + material.polygonOffset = true; + material.polygonOffsetFactor = 1; + + if ( luminance !== 0 ) { + + material.emissive.set( material.color ).multiplyScalar( luminance ); + + } + + if ( ! edgeMaterial ) { + + // This is the material used for edges + edgeMaterial = new LineBasicMaterial( { + color: edgeColor, + transparent: isTransparent, + opacity: alpha, + depthWrite: ! isTransparent + } ); + edgeMaterial.userData.code = code; + edgeMaterial.name = name + ' - Edge'; + edgeMaterial.color.convertSRGBToLinear(); + + // This is the material used for conditional edges + edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( { + + fog: true, + transparent: isTransparent, + depthWrite: ! isTransparent, + color: edgeColor, + opacity: alpha, + + } ); + edgeMaterial.userData.conditionalEdgeMaterial.color.convertSRGBToLinear(); + + } + + material.userData.code = code; + material.name = name; + + material.userData.edgeMaterial = edgeMaterial; + + this.addMaterial( material ); + + return material; + + } + + computeConstructionSteps( model ) { + + // Sets userdata.constructionStep number in Group objects and userData.numConstructionSteps number in the root Group object. + + let stepNumber = 0; + + model.traverse( c => { + + if ( c.isGroup ) { + + if ( c.userData.startingConstructionStep ) { + + stepNumber ++; + + } + + c.userData.constructionStep = stepNumber; + + } + + } ); + + model.userData.numConstructionSteps = stepNumber + 1; + + } + +} + +export { LDrawLoader }; diff --git a/jsm/loaders/LUT3dlLoader.js b/jsm/loaders/LUT3dlLoader.js new file mode 100644 index 0000000..bddeb25 --- /dev/null +++ b/jsm/loaders/LUT3dlLoader.js @@ -0,0 +1,151 @@ +// http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492 +// https://community.foundry.com/discuss/topic/103636/format-spec-for-3dl?mode=Post&postID=895258 +import { + Loader, + FileLoader, + DataTexture, + Data3DTexture, + RGBAFormat, + UnsignedByteType, + ClampToEdgeWrapping, + LinearFilter, +} from 'three'; + +export class LUT3dlLoader extends Loader { + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'text' ); + loader.load( url, text => { + + try { + + onLoad( this.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( str ) { + + // remove empty lines and comment lints + str = str + .replace( /^#.*?(\n|\r)/gm, '' ) + .replace( /^\s*?(\n|\r)/gm, '' ) + .trim(); + + const lines = str.split( /[\n\r]+/g ); + + // first line is the positions on the grid that are provided by the LUT + const gridLines = lines[ 0 ].trim().split( /\s+/g ).map( e => parseFloat( e ) ); + const gridStep = gridLines[ 1 ] - gridLines[ 0 ]; + const size = gridLines.length; + + for ( let i = 1, l = gridLines.length; i < l; i ++ ) { + + if ( gridStep !== ( gridLines[ i ] - gridLines[ i - 1 ] ) ) { + + throw new Error( 'LUT3dlLoader: Inconsistent grid size not supported.' ); + + } + + } + + const dataArray = new Array( size * size * size * 4 ); + let index = 0; + let maxOutputValue = 0.0; + for ( let i = 1, l = lines.length; i < l; i ++ ) { + + const line = lines[ i ].trim(); + const split = line.split( /\s/g ); + + const r = parseFloat( split[ 0 ] ); + const g = parseFloat( split[ 1 ] ); + const b = parseFloat( split[ 2 ] ); + maxOutputValue = Math.max( maxOutputValue, r, g, b ); + + const bLayer = index % size; + const gLayer = Math.floor( index / size ) % size; + const rLayer = Math.floor( index / ( size * size ) ) % size; + + // b grows first, then g, then r + const pixelIndex = bLayer * size * size + gLayer * size + rLayer; + dataArray[ 4 * pixelIndex + 0 ] = r; + dataArray[ 4 * pixelIndex + 1 ] = g; + dataArray[ 4 * pixelIndex + 2 ] = b; + dataArray[ 4 * pixelIndex + 3 ] = 1.0; + index += 1; + + } + + // Find the apparent bit depth of the stored RGB values and map the + // values to [ 0, 255 ]. + const bits = Math.ceil( Math.log2( maxOutputValue ) ); + const maxBitValue = Math.pow( 2.0, bits ); + for ( let i = 0, l = dataArray.length; i < l; i += 4 ) { + + const r = dataArray[ i + 0 ]; + const g = dataArray[ i + 1 ]; + const b = dataArray[ i + 2 ]; + dataArray[ i + 0 ] = 255 * r / maxBitValue; // r + dataArray[ i + 1 ] = 255 * g / maxBitValue; // g + dataArray[ i + 2 ] = 255 * b / maxBitValue; // b + + } + + const data = new Uint8Array( dataArray ); + const texture = new DataTexture(); + texture.image.data = data; + texture.image.width = size; + texture.image.height = size * size; + texture.format = RGBAFormat; + texture.type = UnsignedByteType; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.wrapS = ClampToEdgeWrapping; + texture.wrapT = ClampToEdgeWrapping; + texture.generateMipmaps = false; + texture.needsUpdate = true; + + const texture3D = new Data3DTexture(); + texture3D.image.data = data; + texture3D.image.width = size; + texture3D.image.height = size; + texture3D.image.depth = size; + texture3D.format = RGBAFormat; + texture3D.type = UnsignedByteType; + texture3D.magFilter = LinearFilter; + texture3D.minFilter = LinearFilter; + texture3D.wrapS = ClampToEdgeWrapping; + texture3D.wrapT = ClampToEdgeWrapping; + texture3D.wrapR = ClampToEdgeWrapping; + texture3D.generateMipmaps = false; + texture3D.needsUpdate = true; + + return { + size, + texture, + texture3D, + }; + + } + +} diff --git a/jsm/loaders/LUTCubeLoader.js b/jsm/loaders/LUTCubeLoader.js new file mode 100644 index 0000000..4a3dac7 --- /dev/null +++ b/jsm/loaders/LUTCubeLoader.js @@ -0,0 +1,153 @@ +// https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf + +import { + Loader, + FileLoader, + Vector3, + DataTexture, + Data3DTexture, + UnsignedByteType, + ClampToEdgeWrapping, + LinearFilter, +} from 'three'; + +export class LUTCubeLoader extends Loader { + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'text' ); + loader.load( url, text => { + + try { + + onLoad( this.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( str ) { + + // Remove empty lines and comments + str = str + .replace( /^#.*?(\n|\r)/gm, '' ) + .replace( /^\s*?(\n|\r)/gm, '' ) + .trim(); + + let title = null; + let size = null; + const domainMin = new Vector3( 0, 0, 0 ); + const domainMax = new Vector3( 1, 1, 1 ); + + const lines = str.split( /[\n\r]+/g ); + let data = null; + + let currIndex = 0; + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + const line = lines[ i ].trim(); + const split = line.split( /\s/g ); + + switch ( split[ 0 ] ) { + + case 'TITLE': + title = line.substring( 7, line.length - 1 ); + break; + case 'LUT_3D_SIZE': + // TODO: A .CUBE LUT file specifies floating point values and could be represented with + // more precision than can be captured with Uint8Array. + const sizeToken = split[ 1 ]; + size = parseFloat( sizeToken ); + data = new Uint8Array( size * size * size * 4 ); + break; + case 'DOMAIN_MIN': + domainMin.x = parseFloat( split[ 1 ] ); + domainMin.y = parseFloat( split[ 2 ] ); + domainMin.z = parseFloat( split[ 3 ] ); + break; + case 'DOMAIN_MAX': + domainMax.x = parseFloat( split[ 1 ] ); + domainMax.y = parseFloat( split[ 2 ] ); + domainMax.z = parseFloat( split[ 3 ] ); + break; + default: + const r = parseFloat( split[ 0 ] ); + const g = parseFloat( split[ 1 ] ); + const b = parseFloat( split[ 2 ] ); + + if ( + r > 1.0 || r < 0.0 || + g > 1.0 || g < 0.0 || + b > 1.0 || b < 0.0 + ) { + + throw new Error( 'LUTCubeLoader : Non normalized values not supported.' ); + + } + + data[ currIndex + 0 ] = r * 255; + data[ currIndex + 1 ] = g * 255; + data[ currIndex + 2 ] = b * 255; + data[ currIndex + 3 ] = 255; + currIndex += 4; + + } + + } + + const texture = new DataTexture(); + texture.image.data = data; + texture.image.width = size; + texture.image.height = size * size; + texture.type = UnsignedByteType; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.wrapS = ClampToEdgeWrapping; + texture.wrapT = ClampToEdgeWrapping; + texture.generateMipmaps = false; + texture.needsUpdate = true; + + const texture3D = new Data3DTexture(); + texture3D.image.data = data; + texture3D.image.width = size; + texture3D.image.height = size; + texture3D.image.depth = size; + texture3D.type = UnsignedByteType; + texture3D.magFilter = LinearFilter; + texture3D.minFilter = LinearFilter; + texture3D.wrapS = ClampToEdgeWrapping; + texture3D.wrapT = ClampToEdgeWrapping; + texture3D.wrapR = ClampToEdgeWrapping; + texture3D.generateMipmaps = false; + texture3D.needsUpdate = true; + + return { + title, + size, + domainMin, + domainMax, + texture, + texture3D, + }; + + } + +} diff --git a/jsm/loaders/LWOLoader.js b/jsm/loaders/LWOLoader.js new file mode 100644 index 0000000..56a013d --- /dev/null +++ b/jsm/loaders/LWOLoader.js @@ -0,0 +1,1067 @@ +/** + * @version 1.1.1 + * + * @desc Load files in LWO3 and LWO2 format on Three.js + * + * LWO3 format specification: + * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo3.html + * + * LWO2 format specification: + * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo2.html + * + **/ + +import { + AddOperation, + BackSide, + BufferAttribute, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DoubleSide, + EquirectangularReflectionMapping, + EquirectangularRefractionMapping, + FileLoader, + Float32BufferAttribute, + FrontSide, + LineBasicMaterial, + LineSegments, + Loader, + Mesh, + MeshPhongMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MirroredRepeatWrapping, + Points, + PointsMaterial, + RepeatWrapping, + TextureLoader, + Vector2 +} from 'three'; + +import { IFFParser } from './lwo/IFFParser.js'; + +let _lwoTree; + +class LWOLoader extends Loader { + + constructor( manager, parameters = {} ) { + + super( manager ); + + this.resourcePath = ( parameters.resourcePath !== undefined ) ? parameters.resourcePath : ''; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( scope.path === '' ) ? extractParentUrl( url, 'Objects' ) : scope.path; + + // give the mesh a default name based on the filename + const modelName = url.split( path ).pop().split( '.' )[ 0 ]; + + const loader = new FileLoader( this.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + + loader.load( url, function ( buffer ) { + + // console.time( 'Total parsing: ' ); + + try { + + onLoad( scope.parse( buffer, path, modelName ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + // console.timeEnd( 'Total parsing: ' ); + + }, onProgress, onError ); + + } + + parse( iffBuffer, path, modelName ) { + + _lwoTree = new IFFParser().parse( iffBuffer ); + + // console.log( 'lwoTree', lwoTree ); + + const textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + + return new LWOTreeParser( textureLoader ).parse( modelName ); + + } + +} + +// Parse the lwoTree object +class LWOTreeParser { + + constructor( textureLoader ) { + + this.textureLoader = textureLoader; + + } + + parse( modelName ) { + + this.materials = new MaterialParser( this.textureLoader ).parse(); + this.defaultLayerName = modelName; + + this.meshes = this.parseLayers(); + + return { + materials: this.materials, + meshes: this.meshes, + }; + + } + + parseLayers() { + + // array of all meshes for building hierarchy + const meshes = []; + + // final array containing meshes with scene graph hierarchy set up + const finalMeshes = []; + + const geometryParser = new GeometryParser(); + + const scope = this; + _lwoTree.layers.forEach( function ( layer ) { + + const geometry = geometryParser.parse( layer.geometry, layer ); + + const mesh = scope.parseMesh( geometry, layer ); + + meshes[ layer.number ] = mesh; + + if ( layer.parent === - 1 ) finalMeshes.push( mesh ); + else meshes[ layer.parent ].add( mesh ); + + + } ); + + this.applyPivots( finalMeshes ); + + return finalMeshes; + + } + + parseMesh( geometry, layer ) { + + let mesh; + + const materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type ); + + this.duplicateUVs( geometry, materials ); + + if ( layer.geometry.type === 'points' ) mesh = new Points( geometry, materials ); + else if ( layer.geometry.type === 'lines' ) mesh = new LineSegments( geometry, materials ); + else mesh = new Mesh( geometry, materials ); + + if ( layer.name ) mesh.name = layer.name; + else mesh.name = this.defaultLayerName + '_layer_' + layer.number; + + mesh.userData.pivot = layer.pivot; + + return mesh; + + } + + // TODO: may need to be reversed in z to convert LWO to three.js coordinates + applyPivots( meshes ) { + + meshes.forEach( function ( mesh ) { + + mesh.traverse( function ( child ) { + + const pivot = child.userData.pivot; + + child.position.x += pivot[ 0 ]; + child.position.y += pivot[ 1 ]; + child.position.z += pivot[ 2 ]; + + if ( child.parent ) { + + const parentPivot = child.parent.userData.pivot; + + child.position.x -= parentPivot[ 0 ]; + child.position.y -= parentPivot[ 1 ]; + child.position.z -= parentPivot[ 2 ]; + + } + + } ); + + } ); + + } + + getMaterials( namesArray, type ) { + + const materials = []; + + const scope = this; + + namesArray.forEach( function ( name, i ) { + + materials[ i ] = scope.getMaterialByName( name ); + + } ); + + // convert materials to line or point mats if required + if ( type === 'points' || type === 'lines' ) { + + materials.forEach( function ( mat, i ) { + + const spec = { + color: mat.color, + }; + + if ( type === 'points' ) { + + spec.size = 0.1; + spec.map = mat.map; + materials[ i ] = new PointsMaterial( spec ); + + } else if ( type === 'lines' ) { + + materials[ i ] = new LineBasicMaterial( spec ); + + } + + } ); + + } + + // if there is only one material, return that directly instead of array + const filtered = materials.filter( Boolean ); + if ( filtered.length === 1 ) return filtered[ 0 ]; + + return materials; + + } + + getMaterialByName( name ) { + + return this.materials.filter( function ( m ) { + + return m.name === name; + + } )[ 0 ]; + + } + + // If the material has an aoMap, duplicate UVs + duplicateUVs( geometry, materials ) { + + let duplicateUVs = false; + + if ( ! Array.isArray( materials ) ) { + + if ( materials.aoMap ) duplicateUVs = true; + + } else { + + materials.forEach( function ( material ) { + + if ( material.aoMap ) duplicateUVs = true; + + } ); + + } + + if ( ! duplicateUVs ) return; + + geometry.setAttribute( 'uv2', new BufferAttribute( geometry.attributes.uv.array, 2 ) ); + + } + +} + +class MaterialParser { + + constructor( textureLoader ) { + + this.textureLoader = textureLoader; + + } + + parse() { + + const materials = []; + this.textures = {}; + + for ( const name in _lwoTree.materials ) { + + if ( _lwoTree.format === 'LWO3' ) { + + materials.push( this.parseMaterial( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); + + } else if ( _lwoTree.format === 'LWO2' ) { + + materials.push( this.parseMaterialLwo2( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); + + } + + } + + return materials; + + } + + parseMaterial( materialData, name, textures ) { + + let params = { + name: name, + side: this.getSide( materialData.attributes ), + flatShading: this.getSmooth( materialData.attributes ), + }; + + const connections = this.parseConnections( materialData.connections, materialData.nodes ); + + const maps = this.parseTextureNodes( connections.maps ); + + this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps ); + + const attributes = this.parseAttributes( connections.attributes, maps ); + + this.parseEnvMap( connections, maps, attributes ); + + params = Object.assign( maps, params ); + params = Object.assign( params, attributes ); + + const materialType = this.getMaterialType( connections.attributes ); + + return new materialType( params ); + + } + + parseMaterialLwo2( materialData, name/*, textures*/ ) { + + let params = { + name: name, + side: this.getSide( materialData.attributes ), + flatShading: this.getSmooth( materialData.attributes ), + }; + + const attributes = this.parseAttributes( materialData.attributes, {} ); + params = Object.assign( params, attributes ); + return new MeshPhongMaterial( params ); + + } + + // Note: converting from left to right handed coords by switching x -> -x in vertices, and + // then switching mat FrontSide -> BackSide + // NB: this means that FrontSide and BackSide have been switched! + getSide( attributes ) { + + if ( ! attributes.side ) return BackSide; + + switch ( attributes.side ) { + + case 0: + case 1: + return BackSide; + case 2: return FrontSide; + case 3: return DoubleSide; + + } + + } + + getSmooth( attributes ) { + + if ( ! attributes.smooth ) return true; + return ! attributes.smooth; + + } + + parseConnections( connections, nodes ) { + + const materialConnections = { + maps: {} + }; + + const inputName = connections.inputName; + const inputNodeName = connections.inputNodeName; + const nodeName = connections.nodeName; + + const scope = this; + inputName.forEach( function ( name, index ) { + + if ( name === 'Material' ) { + + const matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes ); + materialConnections.attributes = matNode.attributes; + materialConnections.envMap = matNode.fileName; + materialConnections.name = inputNodeName[ index ]; + + } + + } ); + + nodeName.forEach( function ( name, index ) { + + if ( name === materialConnections.name ) { + + materialConnections.maps[ inputName[ index ] ] = scope.getNodeByRefName( inputNodeName[ index ], nodes ); + + } + + } ); + + return materialConnections; + + } + + getNodeByRefName( refName, nodes ) { + + for ( const name in nodes ) { + + if ( nodes[ name ].refName === refName ) return nodes[ name ]; + + } + + } + + parseTextureNodes( textureNodes ) { + + const maps = {}; + + for ( const name in textureNodes ) { + + const node = textureNodes[ name ]; + const path = node.fileName; + + if ( ! path ) return; + + const texture = this.loadTexture( path ); + + if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode ); + if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode ); + + switch ( name ) { + + case 'Color': + maps.map = texture; + break; + case 'Roughness': + maps.roughnessMap = texture; + maps.roughness = 1; + break; + case 'Specular': + maps.specularMap = texture; + maps.specular = 0xffffff; + break; + case 'Luminous': + maps.emissiveMap = texture; + maps.emissive = 0x808080; + break; + case 'Luminous Color': + maps.emissive = 0x808080; + break; + case 'Metallic': + maps.metalnessMap = texture; + maps.metalness = 1; + break; + case 'Transparency': + case 'Alpha': + maps.alphaMap = texture; + maps.transparent = true; + break; + case 'Normal': + maps.normalMap = texture; + if ( node.amplitude !== undefined ) maps.normalScale = new Vector2( node.amplitude, node.amplitude ); + break; + case 'Bump': + maps.bumpMap = texture; + break; + + } + + } + + // LWO BSDF materials can have both spec and rough, but this is not valid in three + if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap; + + return maps; + + } + + // maps can also be defined on individual material attributes, parse those here + // This occurs on Standard (Phong) surfaces + parseAttributeImageMaps( attributes, textures, maps ) { + + for ( const name in attributes ) { + + const attribute = attributes[ name ]; + + if ( attribute.maps ) { + + const mapData = attribute.maps[ 0 ]; + + const path = this.getTexturePathByIndex( mapData.imageIndex, textures ); + if ( ! path ) return; + + const texture = this.loadTexture( path ); + + if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w ); + if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h ); + + switch ( name ) { + + case 'Color': + maps.map = texture; + break; + case 'Diffuse': + maps.aoMap = texture; + break; + case 'Roughness': + maps.roughnessMap = texture; + maps.roughness = 1; + break; + case 'Specular': + maps.specularMap = texture; + maps.specular = 0xffffff; + break; + case 'Luminosity': + maps.emissiveMap = texture; + maps.emissive = 0x808080; + break; + case 'Metallic': + maps.metalnessMap = texture; + maps.metalness = 1; + break; + case 'Transparency': + case 'Alpha': + maps.alphaMap = texture; + maps.transparent = true; + break; + case 'Normal': + maps.normalMap = texture; + break; + case 'Bump': + maps.bumpMap = texture; + break; + + } + + } + + } + + } + + parseAttributes( attributes, maps ) { + + const params = {}; + + // don't use color data if color map is present + if ( attributes.Color && ! maps.map ) { + + params.color = new Color().fromArray( attributes.Color.value ); + + } else params.color = new Color(); + + + if ( attributes.Transparency && attributes.Transparency.value !== 0 ) { + + params.opacity = 1 - attributes.Transparency.value; + params.transparent = true; + + } + + if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1; + + if ( attributes[ 'Refraction Index' ] ) params.refractionRatio = 0.98 / attributes[ 'Refraction Index' ].value; + + this.parsePhysicalAttributes( params, attributes, maps ); + this.parseStandardAttributes( params, attributes, maps ); + this.parsePhongAttributes( params, attributes, maps ); + + return params; + + } + + parsePhysicalAttributes( params, attributes/*, maps*/ ) { + + if ( attributes.Clearcoat && attributes.Clearcoat.value > 0 ) { + + params.clearcoat = attributes.Clearcoat.value; + + if ( attributes[ 'Clearcoat Gloss' ] ) { + + params.clearcoatRoughness = 0.5 * ( 1 - attributes[ 'Clearcoat Gloss' ].value ); + + } + + } + + } + + parseStandardAttributes( params, attributes, maps ) { + + + if ( attributes.Luminous ) { + + params.emissiveIntensity = attributes.Luminous.value; + + if ( attributes[ 'Luminous Color' ] && ! maps.emissive ) { + + params.emissive = new Color().fromArray( attributes[ 'Luminous Color' ].value ); + + } else { + + params.emissive = new Color( 0x808080 ); + + } + + } + + if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value; + if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value; + + } + + parsePhongAttributes( params, attributes, maps ) { + + if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value ); + + if ( attributes.Reflection ) { + + params.reflectivity = attributes.Reflection.value; + params.combine = AddOperation; + + } + + if ( attributes.Luminosity ) { + + params.emissiveIntensity = attributes.Luminosity.value; + + if ( ! maps.emissiveMap && ! maps.map ) { + + params.emissive = params.color; + + } else { + + params.emissive = new Color( 0x808080 ); + + } + + } + + // parse specular if there is no roughness - we will interpret the material as 'Phong' in this case + if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) { + + if ( attributes[ 'Color Highlight' ] ) { + + params.specular = new Color().setScalar( attributes.Specular.value ).lerp( params.color.clone().multiplyScalar( attributes.Specular.value ), attributes[ 'Color Highlight' ].value ); + + } else { + + params.specular = new Color().setScalar( attributes.Specular.value ); + + } + + } + + if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 ); + + } + + parseEnvMap( connections, maps, attributes ) { + + if ( connections.envMap ) { + + const envMap = this.loadTexture( connections.envMap ); + + if ( attributes.transparent && attributes.opacity < 0.999 ) { + + envMap.mapping = EquirectangularRefractionMapping; + + // Reflectivity and refraction mapping don't work well together in Phong materials + if ( attributes.reflectivity !== undefined ) { + + delete attributes.reflectivity; + delete attributes.combine; + + } + + if ( attributes.metalness !== undefined ) { + + attributes.metalness = 1; // For most transparent materials metalness should be set to 1 if not otherwise defined. If set to 0 no refraction will be visible + + } + + attributes.opacity = 1; // transparency fades out refraction, forcing opacity to 1 ensures a closer visual match to the material in Lightwave. + + } else envMap.mapping = EquirectangularReflectionMapping; + + maps.envMap = envMap; + + } + + } + + // get texture defined at top level by its index + getTexturePathByIndex( index ) { + + let fileName = ''; + + if ( ! _lwoTree.textures ) return fileName; + + _lwoTree.textures.forEach( function ( texture ) { + + if ( texture.index === index ) fileName = texture.fileName; + + } ); + + return fileName; + + } + + loadTexture( path ) { + + if ( ! path ) return null; + + const texture = this.textureLoader.load( + path, + undefined, + undefined, + function () { + + console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' ); + + } + ); + + return texture; + + } + + // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge + getWrappingType( num ) { + + switch ( num ) { + + case 0: + console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' ); + return ClampToEdgeWrapping; + case 1: return RepeatWrapping; + case 2: return MirroredRepeatWrapping; + case 3: return ClampToEdgeWrapping; + + } + + } + + getMaterialType( nodeData ) { + + if ( nodeData.Clearcoat && nodeData.Clearcoat.value > 0 ) return MeshPhysicalMaterial; + if ( nodeData.Roughness ) return MeshStandardMaterial; + return MeshPhongMaterial; + + } + +} + +class GeometryParser { + + parse( geoData, layer ) { + + const geometry = new BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( geoData.points, 3 ) ); + + const indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions ); + geometry.setIndex( indices ); + + this.parseGroups( geometry, geoData ); + + geometry.computeVertexNormals(); + + this.parseUVs( geometry, layer, indices ); + this.parseMorphTargets( geometry, layer, indices ); + + // TODO: z may need to be reversed to account for coordinate system change + geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); + + // let userData = geometry.userData; + // geometry = geometry.toNonIndexed() + // geometry.userData = userData; + + return geometry; + + } + + // split quads into tris + splitIndices( indices, polygonDimensions ) { + + const remappedIndices = []; + + let i = 0; + polygonDimensions.forEach( function ( dim ) { + + if ( dim < 4 ) { + + for ( let k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] ); + + } else if ( dim === 4 ) { + + remappedIndices.push( + indices[ i ], + indices[ i + 1 ], + indices[ i + 2 ], + + indices[ i ], + indices[ i + 2 ], + indices[ i + 3 ] + + ); + + } else if ( dim > 4 ) { + + for ( let k = 1; k < dim - 1; k ++ ) { + + remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] ); + + } + + console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' ); + + } + + i += dim; + + } ); + + return remappedIndices; + + } + + // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered + parseGroups( geometry, geoData ) { + + const tags = _lwoTree.tags; + const matNames = []; + + let elemSize = 3; + if ( geoData.type === 'lines' ) elemSize = 2; + if ( geoData.type === 'points' ) elemSize = 1; + + const remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices ); + + let indexNum = 0; // create new indices in numerical order + const indexPairs = {}; // original indices mapped to numerical indices + + let prevMaterialIndex; + let materialIndex; + + let prevStart = 0; + let currentCount = 0; + + for ( let i = 0; i < remappedIndices.length; i += 2 ) { + + materialIndex = remappedIndices[ i + 1 ]; + + if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ]; + + if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex; + + if ( materialIndex !== prevMaterialIndex ) { + + let currentIndex; + if ( indexPairs[ tags[ prevMaterialIndex ] ] ) { + + currentIndex = indexPairs[ tags[ prevMaterialIndex ] ]; + + } else { + + currentIndex = indexNum; + indexPairs[ tags[ prevMaterialIndex ] ] = indexNum; + matNames[ indexNum ] = tags[ prevMaterialIndex ]; + indexNum ++; + + } + + geometry.addGroup( prevStart, currentCount, currentIndex ); + + prevStart += currentCount; + + prevMaterialIndex = materialIndex; + currentCount = 0; + + } + + currentCount += elemSize; + + } + + // the loop above doesn't add the last group, do that here. + if ( geometry.groups.length > 0 ) { + + let currentIndex; + if ( indexPairs[ tags[ materialIndex ] ] ) { + + currentIndex = indexPairs[ tags[ materialIndex ] ]; + + } else { + + currentIndex = indexNum; + indexPairs[ tags[ materialIndex ] ] = indexNum; + matNames[ indexNum ] = tags[ materialIndex ]; + + } + + geometry.addGroup( prevStart, currentCount, currentIndex ); + + } + + // Mat names from TAGS chunk, used to build up an array of materials for this geometry + geometry.userData.matNames = matNames; + + } + + splitMaterialIndices( polygonDimensions, indices ) { + + const remappedIndices = []; + + polygonDimensions.forEach( function ( dim, i ) { + + if ( dim <= 3 ) { + + remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] ); + + } else if ( dim === 4 ) { + + remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] ); + + } else { + + // ignore > 4 for now + for ( let k = 0; k < dim - 2; k ++ ) { + + remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] ); + + } + + } + + } ); + + return remappedIndices; + + } + + // UV maps: + // 1: are defined via index into an array of points, not into a geometry + // - the geometry is also defined by an index into this array, but the indexes may not match + // 2: there can be any number of UV maps for a single geometry. Here these are combined, + // with preference given to the first map encountered + // 3: UV maps can be partial - that is, defined for only a part of the geometry + // 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most + // UV maps are defined as partially VMAP and partially VMAD + // VMADs are currently not supported + parseUVs( geometry, layer ) { + + // start by creating a UV map set to zero for the whole geometry + const remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () { + + return 0; + + } ); + + for ( const name in layer.uvs ) { + + const uvs = layer.uvs[ name ].uvs; + const uvIndices = layer.uvs[ name ].uvIndices; + + uvIndices.forEach( function ( i, j ) { + + remappedUVs[ i * 2 ] = uvs[ j * 2 ]; + remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ]; + + } ); + + } + + geometry.setAttribute( 'uv', new Float32BufferAttribute( remappedUVs, 2 ) ); + + } + + parseMorphTargets( geometry, layer ) { + + let num = 0; + for ( const name in layer.morphTargets ) { + + const remappedPoints = geometry.attributes.position.array.slice(); + + if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = []; + + const morphPoints = layer.morphTargets[ name ].points; + const morphIndices = layer.morphTargets[ name ].indices; + const type = layer.morphTargets[ name ].type; + + morphIndices.forEach( function ( i, j ) { + + if ( type === 'relative' ) { + + remappedPoints[ i * 3 ] += morphPoints[ j * 3 ]; + remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ]; + remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ]; + + } else { + + remappedPoints[ i * 3 ] = morphPoints[ j * 3 ]; + remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ]; + remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ]; + + } + + } ); + + geometry.morphAttributes.position[ num ] = new Float32BufferAttribute( remappedPoints, 3 ); + geometry.morphAttributes.position[ num ].name = name; + + num ++; + + } + + geometry.morphTargetsRelative = false; + + } + +} + + +// ************** UTILITY FUNCTIONS ************** + +function extractParentUrl( url, dir ) { + + const index = url.indexOf( dir ); + + if ( index === - 1 ) return './'; + + return url.slice( 0, index ); + +} + +export { LWOLoader }; diff --git a/jsm/loaders/LogLuvLoader.js b/jsm/loaders/LogLuvLoader.js new file mode 100644 index 0000000..297ea0a --- /dev/null +++ b/jsm/loaders/LogLuvLoader.js @@ -0,0 +1,606 @@ +import { + DataUtils, + DataTextureLoader, + FloatType, + HalfFloatType, + RGBAFormat +} from 'three'; + +class LogLuvLoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + + } + + parse( buffer ) { + + const ifds = UTIF.decode( buffer ); + UTIF.decodeImage( buffer, ifds[ 0 ] ); + const rgba = UTIF.toRGBA( ifds[ 0 ], this.type ); + + return { + width: ifds[ 0 ].width, + height: ifds[ 0 ].height, + data: rgba, + format: RGBAFormat, + type: this.type, + flipY: true + }; + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + +} + +// from https://github.com/photopea/UTIF.js (MIT License) + +const UTIF = {}; + +UTIF.decode = function ( buff, prm ) { + + if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug + var data = new Uint8Array( buff ), offset = 0; + + var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; + var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; + bin.readUshort( data, offset ); offset += 2; + + var ifdo = bin.readUint( data, offset ); + var ifds = []; + while ( true ) { + + var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { + + console.log( 'error in TIFF' ); break; + + } + + + UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); + + ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); + if ( ifdo == 0 ) break; + + } + + return ifds; + +}; + +UTIF.decodeImage = function ( buff, img, ifds ) { + + if ( img.data ) return; + var data = new Uint8Array( buff ); + var id = UTIF._binBE.readASCII( data, 0, 2 ); + + if ( img[ 't256' ] == null ) return; // No width => probably not an image + img.isLE = id == 'II'; + img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; + img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; + + var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; + var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; + if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); + if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); + + var bipp; // bits per pixel + if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; + else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { + + bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); + + } + + var bipl = Math.ceil( img.width * bipp / 8 ) * 8; + var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; + var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; + + if ( img[ 't322' ] != null ) { + + var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; + var tx = Math.floor( ( img.width + tw - 1 ) / tw ); + var ty = Math.floor( ( img.height + th - 1 ) / th ); + var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); + for ( var y = 0; y < ty; y ++ ) + for ( var x = 0; x < tx; x ++ ) { + + var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); + // Might be required for 7 too. Need to check + if ( cmpr == 6 ) bytes = tbuff; + else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); + + } + + bilen = bytes.length * 8; + + } else { + + var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); + for ( var i = 0; i < soff.length; i ++ ) { + + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); + bilen += bipl * rps; + + } + + bilen = Math.min( bilen, bytes.length * 8 ); + + } + + img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); + +}; + +UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { + + //console.log("compression", cmpr); + //var time = Date.now(); + if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); + else console.log( 'Unsupported compression', cmpr ); + + //console.log(Date.now()-time); + + var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); + var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); + + // convert to Little Endian /* + if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG + for ( var y = 0; y < h; y ++ ) { + + //console.log("fixing endianity"); + var roff = toff + y * bpl; + for ( var x = 1; x < bpl; x += 2 ) { + + var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; + + } + + } //*/ + + if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { + + for ( var y = 0; y < h; y ++ ) { + + var ntoff = toff + y * bpl; + if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { + + var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); + tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; + + } + else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + + tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; + tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; + tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; + + } + else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; + + } + + } + +}; + +UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { + + var w = img.width, qw = w * 4; + var io = 0, out = new Uint8Array( qw ); + + while ( io < len ) { + + var oo = 0; + while ( oo < qw ) { + + var c = data[ off + io ]; io ++; + if ( c < 128 ) { + + for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; + + } else { + + c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; + + } + + } + + for ( var x = 0; x < w; x ++ ) { + + tgt[ toff + 0 ] = out[ x ]; + tgt[ toff + 1 ] = out[ x + w ]; + tgt[ toff + 2 ] = out[ x + w * 2 ]; + tgt[ toff + 4 ] = out[ x + w * 3 ]; + toff += 6; + + } + + } + +}; + +UTIF.tags = {}; +//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; +// start at tag 250 +UTIF._types = function () { + + var main = new Array( 250 ); main.fill( 0 ); + main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); + var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; + return { + basic: { + main: main, + rest: rest + }, + gps: { + main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], + rest: { 18: 2, 29: 2 } + } + }; + +}(); + +UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { + + var cnt = bin.readUshort( data, offset ); offset += 2; + var ifd = {}; + + if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); + for ( var i = 0; i < cnt; i ++ ) { + + var tag = bin.readUshort( data, offset ); offset += 2; + var type = bin.readUshort( data, offset ); offset += 2; + var num = bin.readUint( data, offset ); offset += 4; + var voff = bin.readUint( data, offset ); offset += 4; + + var arr = []; + //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; + if ( type == 1 || type == 7 ) { + + arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); + + } + + if ( type == 2 ) { + + var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); + if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); + else arr = new Uint8Array( data.buffer, o0, len ); + + } + + if ( type == 3 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + + } + + if ( type == 4 + || type == 13 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + + } + + if ( type == 5 || type == 10 ) { + + var ri = type == 5 ? bin.readUint : bin.readInt; + for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); + + } + + if ( type == 8 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + + } + + if ( type == 9 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + + } + + if ( type == 11 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); + + } + + if ( type == 12 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); + + } + + if ( num != 0 && arr.length == 0 ) { + + console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; + + } + + if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); + + ifd[ 't' + tag ] = arr; + + if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { + + var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; + var subfd = []; + for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); + if ( tag == 330 ) ifd.subIFD = subfd; + if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; + if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } + if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; + if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; + + } + + if ( tag == 37500 && prm.parseMN ) { + + var mn = arr; + //console.log(bin.readASCII(mn,0,mn.length), mn); + if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; + else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { + + var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); + ifd.makerNote = subsub[ 0 ]; + + } + + } + + } + + ifds.push( ifd ); + if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); + return offset; + +}; + +UTIF.toRGBA = function ( out, type ) { + + const w = out.width, h = out.height, area = w * h, data = out.data; + + let img; + + switch ( type ) { + + case HalfFloatType: + + img = new Uint16Array( area * 4 ); + break; + + case FloatType: + + img = new Float32Array( area * 4 ); + break; + + default: + console.error( 'THREE.LogLuvLoader: Unsupported texture data type:', type ); + + } + + let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; + const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; + + if ( out[ 't262' ] == null && bps == 1 ) intp = 0; + + if ( intp == 32845 ) { + + for ( let y = 0; y < h; y ++ ) { + + for ( let x = 0; x < w; x ++ ) { + + const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; + let L = ( data[ si + 1 ] << 8 ) | data[ si ]; + + L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); + const u = ( data[ si + 3 ] + 0.5 ) / 410; + const v = ( data[ si + 5 ] + 0.5 ) / 410; + + // Luv to xyY + const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); + const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); + const bY = L; + + // xyY to XYZ + const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; + + // XYZ to linear RGB + const r = 2.690 * X - 1.276 * Y - 0.414 * Z; + const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; + const b = 0.061 * X - 0.224 * Y + 1.163 * Z; + + if ( type === HalfFloatType ) { + + img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); + img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); + img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); + img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); + + + } else { + + img[ qi ] = r; + img[ qi + 1 ] = g; + img[ qi + 2 ] = b; + img[ qi + 3 ] = 1; + + } + + } + + } + + } else { + + console.log( 'Unsupported Photometric interpretation: ' + intp ); + + } + + return img; + +}; + +UTIF._binBE = +{ + nextZero: function ( data, o ) { + + while ( data[ o ] != 0 ) o ++; return o; + + }, + readUshort: function ( buff, p ) { + + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + + }, + readShort: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; + + }, + readInt: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; + + }, + readUint: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; + + }, + readASCII: function ( buff, p, l ) { + + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + + }, + readFloat: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; + + }, + readDouble: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; + + }, + + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + + }, + writeInt: function ( buff, p, n ) { + + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; + + }, + writeASCII: function ( buff, p, s ) { + + for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); + + }, + writeDouble: function ( buff, p, n ) { + + UTIF._binBE.fl64[ 0 ] = n; + for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; + + } +}; +UTIF._binBE.ui8 = new Uint8Array( 8 ); +UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); + +UTIF._binLE = +{ + nextZero: UTIF._binBE.nextZero, + readUshort: function ( buff, p ) { + + return ( buff[ p + 1 ] << 8 ) | buff[ p ]; + + }, + readShort: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; + + }, + readInt: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; + + }, + readUint: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; + + }, + readASCII: UTIF._binBE.readASCII, + readFloat: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; + + }, + readDouble: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; + + }, + + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; + + }, + writeInt: function ( buff, p, n ) { + + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; + + }, + writeASCII: UTIF._binBE.writeASCII +}; +UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { + + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min( tw, w - xoff ); + var ylim = Math.min( th, h - yoff ); + for ( var y = 0; y < ylim; y ++ ) { + + var tof = ( yoff + y ) * w + xoff; + var sof = y * tw; + for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; + + } + +}; + +export { LogLuvLoader }; diff --git a/jsm/loaders/LottieLoader.js b/jsm/loaders/LottieLoader.js new file mode 100644 index 0000000..d323bf4 --- /dev/null +++ b/jsm/loaders/LottieLoader.js @@ -0,0 +1,73 @@ +import { + FileLoader, + Loader, + CanvasTexture, + NearestFilter +} from 'three'; + +class LottieLoader extends Loader { + + setQuality( value ) { + + this._quality = value; + + } + + load( url, onLoad, onProgress, onError ) { + + const quality = this._quality || 1; + + const texture = new CanvasTexture(); + texture.minFilter = NearestFilter; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, function ( text ) { + + const data = JSON.parse( text ); + + // bodymoving uses container.offetWidth and offsetHeight + // to define width/height + + const container = document.createElement( 'div' ); + container.style.width = data.w + 'px'; + container.style.height = data.h + 'px'; + document.body.appendChild( container ); + + const animation = bodymovin.loadAnimation( { + container: container, + animType: 'canvas', + loop: true, + autoplay: true, + animationData: data, + rendererSettings: { dpr: quality } + } ); + + texture.animation = animation; + texture.image = animation.container; + + animation.addEventListener( 'enterFrame', function () { + + texture.needsUpdate = true; + + } ); + + container.style.display = 'none'; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + }, onProgress, onError ); + + return texture; + + } + +} + +export { LottieLoader }; diff --git a/jsm/loaders/MD2Loader.js b/jsm/loaders/MD2Loader.js new file mode 100644 index 0000000..2d88be4 --- /dev/null +++ b/jsm/loaders/MD2Loader.js @@ -0,0 +1,399 @@ +import { + AnimationClip, + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + Vector3 +} from 'three'; + +const _normalData = [ + [ - 0.525731, 0.000000, 0.850651 ], [ - 0.442863, 0.238856, 0.864188 ], + [ - 0.295242, 0.000000, 0.955423 ], [ - 0.309017, 0.500000, 0.809017 ], + [ - 0.162460, 0.262866, 0.951056 ], [ 0.000000, 0.000000, 1.000000 ], + [ 0.000000, 0.850651, 0.525731 ], [ - 0.147621, 0.716567, 0.681718 ], + [ 0.147621, 0.716567, 0.681718 ], [ 0.000000, 0.525731, 0.850651 ], + [ 0.309017, 0.500000, 0.809017 ], [ 0.525731, 0.000000, 0.850651 ], + [ 0.295242, 0.000000, 0.955423 ], [ 0.442863, 0.238856, 0.864188 ], + [ 0.162460, 0.262866, 0.951056 ], [ - 0.681718, 0.147621, 0.716567 ], + [ - 0.809017, 0.309017, 0.500000 ], [ - 0.587785, 0.425325, 0.688191 ], + [ - 0.850651, 0.525731, 0.000000 ], [ - 0.864188, 0.442863, 0.238856 ], + [ - 0.716567, 0.681718, 0.147621 ], [ - 0.688191, 0.587785, 0.425325 ], + [ - 0.500000, 0.809017, 0.309017 ], [ - 0.238856, 0.864188, 0.442863 ], + [ - 0.425325, 0.688191, 0.587785 ], [ - 0.716567, 0.681718, - 0.147621 ], + [ - 0.500000, 0.809017, - 0.309017 ], [ - 0.525731, 0.850651, 0.000000 ], + [ 0.000000, 0.850651, - 0.525731 ], [ - 0.238856, 0.864188, - 0.442863 ], + [ 0.000000, 0.955423, - 0.295242 ], [ - 0.262866, 0.951056, - 0.162460 ], + [ 0.000000, 1.000000, 0.000000 ], [ 0.000000, 0.955423, 0.295242 ], + [ - 0.262866, 0.951056, 0.162460 ], [ 0.238856, 0.864188, 0.442863 ], + [ 0.262866, 0.951056, 0.162460 ], [ 0.500000, 0.809017, 0.309017 ], + [ 0.238856, 0.864188, - 0.442863 ], [ 0.262866, 0.951056, - 0.162460 ], + [ 0.500000, 0.809017, - 0.309017 ], [ 0.850651, 0.525731, 0.000000 ], + [ 0.716567, 0.681718, 0.147621 ], [ 0.716567, 0.681718, - 0.147621 ], + [ 0.525731, 0.850651, 0.000000 ], [ 0.425325, 0.688191, 0.587785 ], + [ 0.864188, 0.442863, 0.238856 ], [ 0.688191, 0.587785, 0.425325 ], + [ 0.809017, 0.309017, 0.500000 ], [ 0.681718, 0.147621, 0.716567 ], + [ 0.587785, 0.425325, 0.688191 ], [ 0.955423, 0.295242, 0.000000 ], + [ 1.000000, 0.000000, 0.000000 ], [ 0.951056, 0.162460, 0.262866 ], + [ 0.850651, - 0.525731, 0.000000 ], [ 0.955423, - 0.295242, 0.000000 ], + [ 0.864188, - 0.442863, 0.238856 ], [ 0.951056, - 0.162460, 0.262866 ], + [ 0.809017, - 0.309017, 0.500000 ], [ 0.681718, - 0.147621, 0.716567 ], + [ 0.850651, 0.000000, 0.525731 ], [ 0.864188, 0.442863, - 0.238856 ], + [ 0.809017, 0.309017, - 0.500000 ], [ 0.951056, 0.162460, - 0.262866 ], + [ 0.525731, 0.000000, - 0.850651 ], [ 0.681718, 0.147621, - 0.716567 ], + [ 0.681718, - 0.147621, - 0.716567 ], [ 0.850651, 0.000000, - 0.525731 ], + [ 0.809017, - 0.309017, - 0.500000 ], [ 0.864188, - 0.442863, - 0.238856 ], + [ 0.951056, - 0.162460, - 0.262866 ], [ 0.147621, 0.716567, - 0.681718 ], + [ 0.309017, 0.500000, - 0.809017 ], [ 0.425325, 0.688191, - 0.587785 ], + [ 0.442863, 0.238856, - 0.864188 ], [ 0.587785, 0.425325, - 0.688191 ], + [ 0.688191, 0.587785, - 0.425325 ], [ - 0.147621, 0.716567, - 0.681718 ], + [ - 0.309017, 0.500000, - 0.809017 ], [ 0.000000, 0.525731, - 0.850651 ], + [ - 0.525731, 0.000000, - 0.850651 ], [ - 0.442863, 0.238856, - 0.864188 ], + [ - 0.295242, 0.000000, - 0.955423 ], [ - 0.162460, 0.262866, - 0.951056 ], + [ 0.000000, 0.000000, - 1.000000 ], [ 0.295242, 0.000000, - 0.955423 ], + [ 0.162460, 0.262866, - 0.951056 ], [ - 0.442863, - 0.238856, - 0.864188 ], + [ - 0.309017, - 0.500000, - 0.809017 ], [ - 0.162460, - 0.262866, - 0.951056 ], + [ 0.000000, - 0.850651, - 0.525731 ], [ - 0.147621, - 0.716567, - 0.681718 ], + [ 0.147621, - 0.716567, - 0.681718 ], [ 0.000000, - 0.525731, - 0.850651 ], + [ 0.309017, - 0.500000, - 0.809017 ], [ 0.442863, - 0.238856, - 0.864188 ], + [ 0.162460, - 0.262866, - 0.951056 ], [ 0.238856, - 0.864188, - 0.442863 ], + [ 0.500000, - 0.809017, - 0.309017 ], [ 0.425325, - 0.688191, - 0.587785 ], + [ 0.716567, - 0.681718, - 0.147621 ], [ 0.688191, - 0.587785, - 0.425325 ], + [ 0.587785, - 0.425325, - 0.688191 ], [ 0.000000, - 0.955423, - 0.295242 ], + [ 0.000000, - 1.000000, 0.000000 ], [ 0.262866, - 0.951056, - 0.162460 ], + [ 0.000000, - 0.850651, 0.525731 ], [ 0.000000, - 0.955423, 0.295242 ], + [ 0.238856, - 0.864188, 0.442863 ], [ 0.262866, - 0.951056, 0.162460 ], + [ 0.500000, - 0.809017, 0.309017 ], [ 0.716567, - 0.681718, 0.147621 ], + [ 0.525731, - 0.850651, 0.000000 ], [ - 0.238856, - 0.864188, - 0.442863 ], + [ - 0.500000, - 0.809017, - 0.309017 ], [ - 0.262866, - 0.951056, - 0.162460 ], + [ - 0.850651, - 0.525731, 0.000000 ], [ - 0.716567, - 0.681718, - 0.147621 ], + [ - 0.716567, - 0.681718, 0.147621 ], [ - 0.525731, - 0.850651, 0.000000 ], + [ - 0.500000, - 0.809017, 0.309017 ], [ - 0.238856, - 0.864188, 0.442863 ], + [ - 0.262866, - 0.951056, 0.162460 ], [ - 0.864188, - 0.442863, 0.238856 ], + [ - 0.809017, - 0.309017, 0.500000 ], [ - 0.688191, - 0.587785, 0.425325 ], + [ - 0.681718, - 0.147621, 0.716567 ], [ - 0.442863, - 0.238856, 0.864188 ], + [ - 0.587785, - 0.425325, 0.688191 ], [ - 0.309017, - 0.500000, 0.809017 ], + [ - 0.147621, - 0.716567, 0.681718 ], [ - 0.425325, - 0.688191, 0.587785 ], + [ - 0.162460, - 0.262866, 0.951056 ], [ 0.442863, - 0.238856, 0.864188 ], + [ 0.162460, - 0.262866, 0.951056 ], [ 0.309017, - 0.500000, 0.809017 ], + [ 0.147621, - 0.716567, 0.681718 ], [ 0.000000, - 0.525731, 0.850651 ], + [ 0.425325, - 0.688191, 0.587785 ], [ 0.587785, - 0.425325, 0.688191 ], + [ 0.688191, - 0.587785, 0.425325 ], [ - 0.955423, 0.295242, 0.000000 ], + [ - 0.951056, 0.162460, 0.262866 ], [ - 1.000000, 0.000000, 0.000000 ], + [ - 0.850651, 0.000000, 0.525731 ], [ - 0.955423, - 0.295242, 0.000000 ], + [ - 0.951056, - 0.162460, 0.262866 ], [ - 0.864188, 0.442863, - 0.238856 ], + [ - 0.951056, 0.162460, - 0.262866 ], [ - 0.809017, 0.309017, - 0.500000 ], + [ - 0.864188, - 0.442863, - 0.238856 ], [ - 0.951056, - 0.162460, - 0.262866 ], + [ - 0.809017, - 0.309017, - 0.500000 ], [ - 0.681718, 0.147621, - 0.716567 ], + [ - 0.681718, - 0.147621, - 0.716567 ], [ - 0.850651, 0.000000, - 0.525731 ], + [ - 0.688191, 0.587785, - 0.425325 ], [ - 0.587785, 0.425325, - 0.688191 ], + [ - 0.425325, 0.688191, - 0.587785 ], [ - 0.425325, - 0.688191, - 0.587785 ], + [ - 0.587785, - 0.425325, - 0.688191 ], [ - 0.688191, - 0.587785, - 0.425325 ] +]; + +class MD2Loader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( buffer ) { + + try { + + onLoad( scope.parse( buffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( buffer ) { + + const data = new DataView( buffer ); + + // http://tfc.duke.free.fr/coding/md2-specs-en.html + + const header = {}; + const headerNames = [ + 'ident', 'version', + 'skinwidth', 'skinheight', + 'framesize', + 'num_skins', 'num_vertices', 'num_st', 'num_tris', 'num_glcmds', 'num_frames', + 'offset_skins', 'offset_st', 'offset_tris', 'offset_frames', 'offset_glcmds', 'offset_end' + ]; + + for ( let i = 0; i < headerNames.length; i ++ ) { + + header[ headerNames[ i ] ] = data.getInt32( i * 4, true ); + + } + + if ( header.ident !== 844121161 || header.version !== 8 ) { + + console.error( 'Not a valid MD2 file' ); + return; + + } + + if ( header.offset_end !== data.byteLength ) { + + console.error( 'Corrupted MD2 file' ); + return; + + } + + // + + const geometry = new BufferGeometry(); + + // uvs + + const uvsTemp = []; + let offset = header.offset_st; + + for ( let i = 0, l = header.num_st; i < l; i ++ ) { + + const u = data.getInt16( offset + 0, true ); + const v = data.getInt16( offset + 2, true ); + + uvsTemp.push( u / header.skinwidth, 1 - ( v / header.skinheight ) ); + + offset += 4; + + } + + // triangles + + offset = header.offset_tris; + + const vertexIndices = []; + const uvIndices = []; + + for ( let i = 0, l = header.num_tris; i < l; i ++ ) { + + vertexIndices.push( + data.getUint16( offset + 0, true ), + data.getUint16( offset + 2, true ), + data.getUint16( offset + 4, true ) + ); + + uvIndices.push( + data.getUint16( offset + 6, true ), + data.getUint16( offset + 8, true ), + data.getUint16( offset + 10, true ) + ); + + offset += 12; + + } + + // frames + + const translation = new Vector3(); + const scale = new Vector3(); + + const frames = []; + + offset = header.offset_frames; + + for ( let i = 0, l = header.num_frames; i < l; i ++ ) { + + scale.set( + data.getFloat32( offset + 0, true ), + data.getFloat32( offset + 4, true ), + data.getFloat32( offset + 8, true ) + ); + + translation.set( + data.getFloat32( offset + 12, true ), + data.getFloat32( offset + 16, true ), + data.getFloat32( offset + 20, true ) + ); + + offset += 24; + + const string = []; + + for ( let j = 0; j < 16; j ++ ) { + + const character = data.getUint8( offset + j ); + if ( character === 0 ) break; + + string[ j ] = character; + + } + + const frame = { + name: String.fromCharCode.apply( null, string ), + vertices: [], + normals: [] + }; + + offset += 16; + + for ( let j = 0; j < header.num_vertices; j ++ ) { + + let x = data.getUint8( offset ++ ); + let y = data.getUint8( offset ++ ); + let z = data.getUint8( offset ++ ); + const n = _normalData[ data.getUint8( offset ++ ) ]; + + x = x * scale.x + translation.x; + y = y * scale.y + translation.y; + z = z * scale.z + translation.z; + + frame.vertices.push( x, z, y ); // convert to Y-up + frame.normals.push( n[ 0 ], n[ 2 ], n[ 1 ] ); // convert to Y-up + + } + + frames.push( frame ); + + } + + // static + + const positions = []; + const normals = []; + const uvs = []; + + const verticesTemp = frames[ 0 ].vertices; + const normalsTemp = frames[ 0 ].normals; + + for ( let i = 0, l = vertexIndices.length; i < l; i ++ ) { + + const vertexIndex = vertexIndices[ i ]; + let stride = vertexIndex * 3; + + // + + const x = verticesTemp[ stride ]; + const y = verticesTemp[ stride + 1 ]; + const z = verticesTemp[ stride + 2 ]; + + positions.push( x, y, z ); + + // + + const nx = normalsTemp[ stride ]; + const ny = normalsTemp[ stride + 1 ]; + const nz = normalsTemp[ stride + 2 ]; + + normals.push( nx, ny, nz ); + + // + + const uvIndex = uvIndices[ i ]; + stride = uvIndex * 2; + + const u = uvsTemp[ stride ]; + const v = uvsTemp[ stride + 1 ]; + + uvs.push( u, v ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + // animation + + const morphPositions = []; + const morphNormals = []; + + for ( let i = 0, l = frames.length; i < l; i ++ ) { + + const frame = frames[ i ]; + const attributeName = frame.name; + + if ( frame.vertices.length > 0 ) { + + const positions = []; + + for ( let j = 0, jl = vertexIndices.length; j < jl; j ++ ) { + + const vertexIndex = vertexIndices[ j ]; + const stride = vertexIndex * 3; + + const x = frame.vertices[ stride ]; + const y = frame.vertices[ stride + 1 ]; + const z = frame.vertices[ stride + 2 ]; + + positions.push( x, y, z ); + + } + + const positionAttribute = new Float32BufferAttribute( positions, 3 ); + positionAttribute.name = attributeName; + + morphPositions.push( positionAttribute ); + + } + + if ( frame.normals.length > 0 ) { + + const normals = []; + + for ( let j = 0, jl = vertexIndices.length; j < jl; j ++ ) { + + const vertexIndex = vertexIndices[ j ]; + const stride = vertexIndex * 3; + + const nx = frame.normals[ stride ]; + const ny = frame.normals[ stride + 1 ]; + const nz = frame.normals[ stride + 2 ]; + + normals.push( nx, ny, nz ); + + } + + const normalAttribute = new Float32BufferAttribute( normals, 3 ); + normalAttribute.name = attributeName; + + morphNormals.push( normalAttribute ); + + } + + } + + geometry.morphAttributes.position = morphPositions; + geometry.morphAttributes.normal = morphNormals; + geometry.morphTargetsRelative = false; + + geometry.animations = AnimationClip.CreateClipsFromMorphTargetSequences( frames, 10 ); + + return geometry; + + } + +} + +export { MD2Loader }; diff --git a/jsm/loaders/MDDLoader.js b/jsm/loaders/MDDLoader.js new file mode 100644 index 0000000..e70f8b0 --- /dev/null +++ b/jsm/loaders/MDDLoader.js @@ -0,0 +1,102 @@ +/** + * MDD is a special format that stores a position for every vertex in a model for every frame in an animation. + * Similar to BVH, it can be used to transfer animation data between different 3D applications or engines. + * + * MDD stores its data in binary format (big endian) in the following way: + * + * number of frames (a single uint32) + * number of vertices (a single uint32) + * time values for each frame (sequence of float32) + * vertex data for each frame (sequence of float32) + */ + +import { + AnimationClip, + BufferAttribute, + FileLoader, + Loader, + NumberKeyframeTrack +} from 'three'; + +class MDDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.load( url, function ( data ) { + + onLoad( scope.parse( data ) ); + + }, onProgress, onError ); + + } + + parse( data ) { + + const view = new DataView( data ); + + const totalFrames = view.getUint32( 0 ); + const totalPoints = view.getUint32( 4 ); + + let offset = 8; + + // animation clip + + const times = new Float32Array( totalFrames ); + const values = new Float32Array( totalFrames * totalFrames ).fill( 0 ); + + for ( let i = 0; i < totalFrames; i ++ ) { + + times[ i ] = view.getFloat32( offset ); offset += 4; + values[ ( totalFrames * i ) + i ] = 1; + + } + + const track = new NumberKeyframeTrack( '.morphTargetInfluences', times, values ); + const clip = new AnimationClip( 'default', times[ times.length - 1 ], [ track ] ); + + // morph targets + + const morphTargets = []; + + for ( let i = 0; i < totalFrames; i ++ ) { + + const morphTarget = new Float32Array( totalPoints * 3 ); + + for ( let j = 0; j < totalPoints; j ++ ) { + + const stride = ( j * 3 ); + + morphTarget[ stride + 0 ] = view.getFloat32( offset ); offset += 4; // x + morphTarget[ stride + 1 ] = view.getFloat32( offset ); offset += 4; // y + morphTarget[ stride + 2 ] = view.getFloat32( offset ); offset += 4; // z + + } + + const attribute = new BufferAttribute( morphTarget, 3 ); + attribute.name = 'morph_' + i; + + morphTargets.push( attribute ); + + } + + return { + morphTargets: morphTargets, + clip: clip + }; + + } + +} + +export { MDDLoader }; diff --git a/jsm/loaders/MMDLoader.js b/jsm/loaders/MMDLoader.js new file mode 100644 index 0000000..eb83354 --- /dev/null +++ b/jsm/loaders/MMDLoader.js @@ -0,0 +1,2224 @@ +import { + AddOperation, + AnimationClip, + Bone, + BufferGeometry, + Color, + CustomBlending, + TangentSpaceNormalMap, + DoubleSide, + DstAlphaFactor, + Euler, + FileLoader, + Float32BufferAttribute, + FrontSide, + Interpolant, + Loader, + LoaderUtils, + UniformsUtils, + ShaderMaterial, + MultiplyOperation, + NearestFilter, + NumberKeyframeTrack, + OneMinusSrcAlphaFactor, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Skeleton, + SkinnedMesh, + SrcAlphaFactor, + TextureLoader, + Uint16BufferAttribute, + Vector3, + VectorKeyframeTrack, + RGB_S3TC_DXT1_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_PVRTC_2BPPV1_Format, + RGB_ETC1_Format, + RGB_ETC2_Format +} from 'three'; +import { MMDToonShader } from '../shaders/MMDToonShader.js'; +import { TGALoader } from '../loaders/TGALoader.js'; +import { MMDParser } from '../libs/mmdparser.module.js'; + +/** + * Dependencies + * - mmd-parser https://github.com/takahirox/mmd-parser + * - TGALoader + * - OutlineEffect + * + * MMDLoader creates Three.js Objects from MMD resources as + * PMD, PMX, VMD, and VPD files. + * + * PMD/PMX is a model data format, VMD is a motion data format + * VPD is a posing data format used in MMD(Miku Miku Dance). + * + * MMD official site + * - https://sites.google.com/view/evpvp/ + * + * PMD, VMD format (in Japanese) + * - http://blog.goo.ne.jp/torisu_tetosuki/e/209ad341d3ece2b1b4df24abf619d6e4 + * + * PMX format + * - https://gist.github.com/felixjones/f8a06bd48f9da9a4539f + * + * TODO + * - light motion in vmd support. + * - SDEF support. + * - uv/material/bone morphing support. + * - more precise grant skinning support. + * - shadow support. + */ + +/** + * @param {THREE.LoadingManager} manager + */ +class MMDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.loader = new FileLoader( this.manager ); + + this.parser = null; // lazy generation + this.meshBuilder = new MeshBuilder( this.manager ); + this.animationBuilder = new AnimationBuilder(); + + } + + /** + * @param {string} animationPath + * @return {MMDLoader} + */ + setAnimationPath( animationPath ) { + + this.animationPath = animationPath; + return this; + + } + + // Load MMD assets as Three.js Object + + /** + * Loads Model file (.pmd or .pmx) as a SkinnedMesh. + * + * @param {string} url - url to Model(.pmd or .pmx) file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + load( url, onLoad, onProgress, onError ) { + + const builder = this.meshBuilder.setCrossOrigin( this.crossOrigin ); + + // resource path + + let resourcePath; + + if ( this.resourcePath !== '' ) { + + resourcePath = this.resourcePath; + + } else if ( this.path !== '' ) { + + resourcePath = this.path; + + } else { + + resourcePath = LoaderUtils.extractUrlBase( url ); + + } + + const modelExtension = this._extractExtension( url ).toLowerCase(); + + // Should I detect by seeing header? + if ( modelExtension !== 'pmd' && modelExtension !== 'pmx' ) { + + if ( onError ) onError( new Error( 'THREE.MMDLoader: Unknown model file extension .' + modelExtension + '.' ) ); + + return; + + } + + this[ modelExtension === 'pmd' ? 'loadPMD' : 'loadPMX' ]( url, function ( data ) { + + onLoad( builder.build( data, resourcePath, onProgress, onError ) ); + + }, onProgress, onError ); + + } + + /** + * Loads Motion file(s) (.vmd) as a AnimationClip. + * If two or more files are specified, they'll be merged. + * + * @param {string|Array} url - url(s) to animation(.vmd) file(s) + * @param {SkinnedMesh|THREE.Camera} object - tracks will be fitting to this object + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadAnimation( url, object, onLoad, onProgress, onError ) { + + const builder = this.animationBuilder; + + this.loadVMD( url, function ( vmd ) { + + onLoad( object.isCamera + ? builder.buildCameraAnimation( vmd ) + : builder.build( vmd, object ) ); + + }, onProgress, onError ); + + } + + /** + * Loads mode file and motion file(s) as an object containing + * a SkinnedMesh and a AnimationClip. + * Tracks of AnimationClip are fitting to the model. + * + * @param {string} modelUrl - url to Model(.pmd or .pmx) file + * @param {string|Array{string}} vmdUrl - url(s) to animation(.vmd) file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadWithAnimation( modelUrl, vmdUrl, onLoad, onProgress, onError ) { + + const scope = this; + + this.load( modelUrl, function ( mesh ) { + + scope.loadAnimation( vmdUrl, mesh, function ( animation ) { + + onLoad( { + mesh: mesh, + animation: animation + } ); + + }, onProgress, onError ); + + }, onProgress, onError ); + + } + + // Load MMD assets as Object data parsed by MMDParser + + /** + * Loads .pmd file as an Object. + * + * @param {string} url - url to .pmd file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadPMD( url, onLoad, onProgress, onError ) { + + const parser = this._getParser(); + + this.loader + .setMimeType( undefined ) + .setPath( this.path ) + .setResponseType( 'arraybuffer' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ) + .load( url, function ( buffer ) { + + onLoad( parser.parsePmd( buffer, true ) ); + + }, onProgress, onError ); + + } + + /** + * Loads .pmx file as an Object. + * + * @param {string} url - url to .pmx file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadPMX( url, onLoad, onProgress, onError ) { + + const parser = this._getParser(); + + this.loader + .setMimeType( undefined ) + .setPath( this.path ) + .setResponseType( 'arraybuffer' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ) + .load( url, function ( buffer ) { + + onLoad( parser.parsePmx( buffer, true ) ); + + }, onProgress, onError ); + + } + + /** + * Loads .vmd file as an Object. If two or more files are specified + * they'll be merged. + * + * @param {string|Array} url - url(s) to .vmd file(s) + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadVMD( url, onLoad, onProgress, onError ) { + + const urls = Array.isArray( url ) ? url : [ url ]; + + const vmds = []; + const vmdNum = urls.length; + + const parser = this._getParser(); + + this.loader + .setMimeType( undefined ) + .setPath( this.animationPath ) + .setResponseType( 'arraybuffer' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ); + + for ( let i = 0, il = urls.length; i < il; i ++ ) { + + this.loader.load( urls[ i ], function ( buffer ) { + + vmds.push( parser.parseVmd( buffer, true ) ); + + if ( vmds.length === vmdNum ) onLoad( parser.mergeVmds( vmds ) ); + + }, onProgress, onError ); + + } + + } + + /** + * Loads .vpd file as an Object. + * + * @param {string} url - url to .vpd file + * @param {boolean} isUnicode + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadVPD( url, isUnicode, onLoad, onProgress, onError ) { + + const parser = this._getParser(); + + this.loader + .setMimeType( isUnicode ? undefined : 'text/plain; charset=shift_jis' ) + .setPath( this.animationPath ) + .setResponseType( 'text' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ) + .load( url, function ( text ) { + + onLoad( parser.parseVpd( text, true ) ); + + }, onProgress, onError ); + + } + + // private methods + + _extractExtension( url ) { + + const index = url.lastIndexOf( '.' ); + return index < 0 ? '' : url.slice( index + 1 ); + + } + + _getParser() { + + if ( this.parser === null ) { + + if ( typeof MMDParser === 'undefined' ) { + + throw new Error( 'THREE.MMDLoader: Import MMDParser https://github.com/takahirox/mmd-parser' ); + + } + + this.parser = new MMDParser.Parser(); // eslint-disable-line no-undef + + } + + return this.parser; + + } + +} + +// Utilities + +/* + * base64 encoded defalut toon textures toon00.bmp - toon10.bmp. + * We don't need to request external toon image files. + */ +const DEFAULT_TOON_TEXTURES = [ + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/bWiiMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh8aBHZBl14e8wAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOUlEQVRYR+3WMREAMAwDsYY/yoDI7MLwIiP40+RJklfcCCBAgAABAgTqArfb/QMCCBAgQIAAgbbAB3z/e0F3js2cAAAAAElFTkSuQmCC', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/B5ilMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh81dWyx0gFwKAAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOklEQVRYR+3WoREAMAwDsWb/UQtCy9wxTOQJ/oQ8SXKKGwEECBAgQIBAXeDt7f4BAQQIECBAgEBb4AOz8Hzx7WLY4wAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABPUlEQVRYR+1XwW7CMAy1+f9fZOMysSEOEweEOPRNdm3HbdOyIhAcklPrOs/PLy9RygBALxzcCDQFmgJNgaZAU6Ap0BR4PwX8gsRMVLssMRH5HcpzJEaWL7EVg9F1IHRlyqQohgVr4FGUlUcMJSjcUlDw0zvjeun70cLWmneoyf7NgBTQSniBTQQSuJAZsOnnaczjIMb5hCiuHKxokCrJfVnrctyZL0PkJAJe1HMil4nxeyi3Ypfn1kX51jpPvo/JeCNC4PhVdHdJw2XjBR8brF8PEIhNVn12AgP7uHsTBguBn53MUZCqv7Lp07Pn5k1Ro+uWmUNn7D+M57rtk7aG0Vo73xyF/fbFf0bPJjDXngnGocDTdFhygZjwUQrMNrDcmZlQT50VJ/g/UwNyHpu778+yW+/ksOz/BFo54P4AsUXMfRq7XWsAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACMElEQVRYR+2Xv4pTQRTGf2dubhLdICiii2KnYKHVolhauKWPoGAnNr6BD6CvIVaihYuI2i1ia0BY0MZGRHQXjZj/mSPnnskfNWiWZUlzJ5k7M2cm833nO5Mziej2DWWJRUoCpQKlAntSQCqgw39/iUWAGmh37jrRnVsKlgpiqmkoGVABA7E57fvY+pJDdgKqF6HzFCSADkDq+F6AHABtQ+UMVE5D7zXod7fFNhTEckTbj5XQgHzNN+5tQvc5NG7C6BNkp6D3EmpXHDR+dQAjFLchW3VS9rlw3JBh+B7ys5Cf9z0GW1C/7P32AyBAOAz1q4jGliIH3YPuBnSfQX4OGreTIgEYQb/pBDtPnEQ4CivXYPAWBk13oHrB54yA9QuSn2H4AcKRpEILDt0BUzj+RLR1V5EqjD66NPRBVpLcQwjHoHYJOhsQv6U4mnzmrIXJCFr4LDwm/xBUoboG9XX4cc9VKdYoSA2yk5NQLJaKDUjTBoveG3Z2TElTxwjNK4M3LEZgUdDdruvcXzKBpStgp2NPiWi3ks9ZXxIoFVi+AvHLdc9TqtjL3/aYjpPlrzOcEnK62Szhimdd7xX232zFDTgtxezOu3WNMRLjiKgjtOhHVMd1loynVHvOgjuIIJMaELEqhJAV/RCSLbWTcfPFakFgFlALTRRvx+ok6Hlp/Q+v3fmx90bMyUzaEAhmM3KvHlXTL5DxnbGf/1M8RNNACLL5MNtPxP/mypJAqcDSFfgFhpYqWUzhTEAAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=' +]; + +const NON_ALPHA_CHANNEL_FORMATS = [ + RGB_S3TC_DXT1_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_PVRTC_2BPPV1_Format, + RGB_ETC1_Format, + RGB_ETC2_Format +]; + +// Builders. They build Three.js object from Object data parsed by MMDParser. + +/** + * @param {THREE.LoadingManager} manager + */ +class MeshBuilder { + + constructor( manager ) { + + this.crossOrigin = 'anonymous'; + this.geometryBuilder = new GeometryBuilder(); + this.materialBuilder = new MaterialBuilder( manager ); + + } + + /** + * @param {string} crossOrigin + * @return {MeshBuilder} + */ + setCrossOrigin( crossOrigin ) { + + this.crossOrigin = crossOrigin; + return this; + + } + + /** + * @param {Object} data - parsed PMD/PMX data + * @param {string} resourcePath + * @param {function} onProgress + * @param {function} onError + * @return {SkinnedMesh} + */ + build( data, resourcePath, onProgress, onError ) { + + const geometry = this.geometryBuilder.build( data ); + const material = this.materialBuilder + .setCrossOrigin( this.crossOrigin ) + .setResourcePath( resourcePath ) + .build( data, geometry, onProgress, onError ); + + const mesh = new SkinnedMesh( geometry, material ); + + const skeleton = new Skeleton( initBones( mesh ) ); + mesh.bind( skeleton ); + + // console.log( mesh ); // for console debug + + return mesh; + + } + +} + +// TODO: Try to remove this function + +function initBones( mesh ) { + + const geometry = mesh.geometry; + + const bones = []; + + if ( geometry && geometry.bones !== undefined ) { + + // first, create array of 'Bone' objects from geometry data + + for ( let i = 0, il = geometry.bones.length; i < il; i ++ ) { + + const gbone = geometry.bones[ i ]; + + // create new 'Bone' object + + const bone = new Bone(); + bones.push( bone ); + + // apply values + + bone.name = gbone.name; + bone.position.fromArray( gbone.pos ); + bone.quaternion.fromArray( gbone.rotq ); + if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl ); + + } + + // second, create bone hierarchy + + for ( let i = 0, il = geometry.bones.length; i < il; i ++ ) { + + const gbone = geometry.bones[ i ]; + + if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) { + + // subsequent bones in the hierarchy + + bones[ gbone.parent ].add( bones[ i ] ); + + } else { + + // topmost bone, immediate child of the skinned mesh + + mesh.add( bones[ i ] ); + + } + + } + + } + + // now the bones are part of the scene graph and children of the skinned mesh. + // let's update the corresponding matrices + + mesh.updateMatrixWorld( true ); + + return bones; + +} + +// + +class GeometryBuilder { + + /** + * @param {Object} data - parsed PMD/PMX data + * @return {BufferGeometry} + */ + build( data ) { + + // for geometry + const positions = []; + const uvs = []; + const normals = []; + + const indices = []; + + const groups = []; + + const bones = []; + const skinIndices = []; + const skinWeights = []; + + const morphTargets = []; + const morphPositions = []; + + const iks = []; + const grants = []; + + const rigidBodies = []; + const constraints = []; + + // for work + let offset = 0; + const boneTypeTable = {}; + + // positions, normals, uvs, skinIndices, skinWeights + + for ( let i = 0; i < data.metadata.vertexCount; i ++ ) { + + const v = data.vertices[ i ]; + + for ( let j = 0, jl = v.position.length; j < jl; j ++ ) { + + positions.push( v.position[ j ] ); + + } + + for ( let j = 0, jl = v.normal.length; j < jl; j ++ ) { + + normals.push( v.normal[ j ] ); + + } + + for ( let j = 0, jl = v.uv.length; j < jl; j ++ ) { + + uvs.push( v.uv[ j ] ); + + } + + for ( let j = 0; j < 4; j ++ ) { + + skinIndices.push( v.skinIndices.length - 1 >= j ? v.skinIndices[ j ] : 0.0 ); + + } + + for ( let j = 0; j < 4; j ++ ) { + + skinWeights.push( v.skinWeights.length - 1 >= j ? v.skinWeights[ j ] : 0.0 ); + + } + + } + + // indices + + for ( let i = 0; i < data.metadata.faceCount; i ++ ) { + + const face = data.faces[ i ]; + + for ( let j = 0, jl = face.indices.length; j < jl; j ++ ) { + + indices.push( face.indices[ j ] ); + + } + + } + + // groups + + for ( let i = 0; i < data.metadata.materialCount; i ++ ) { + + const material = data.materials[ i ]; + + groups.push( { + offset: offset * 3, + count: material.faceCount * 3 + } ); + + offset += material.faceCount; + + } + + // bones + + for ( let i = 0; i < data.metadata.rigidBodyCount; i ++ ) { + + const body = data.rigidBodies[ i ]; + let value = boneTypeTable[ body.boneIndex ]; + + // keeps greater number if already value is set without any special reasons + value = value === undefined ? body.type : Math.max( body.type, value ); + + boneTypeTable[ body.boneIndex ] = value; + + } + + for ( let i = 0; i < data.metadata.boneCount; i ++ ) { + + const boneData = data.bones[ i ]; + + const bone = { + index: i, + transformationClass: boneData.transformationClass, + parent: boneData.parentIndex, + name: boneData.name, + pos: boneData.position.slice( 0, 3 ), + rotq: [ 0, 0, 0, 1 ], + scl: [ 1, 1, 1 ], + rigidBodyType: boneTypeTable[ i ] !== undefined ? boneTypeTable[ i ] : - 1 + }; + + if ( bone.parent !== - 1 ) { + + bone.pos[ 0 ] -= data.bones[ bone.parent ].position[ 0 ]; + bone.pos[ 1 ] -= data.bones[ bone.parent ].position[ 1 ]; + bone.pos[ 2 ] -= data.bones[ bone.parent ].position[ 2 ]; + + } + + bones.push( bone ); + + } + + // iks + + // TODO: remove duplicated codes between PMD and PMX + if ( data.metadata.format === 'pmd' ) { + + for ( let i = 0; i < data.metadata.ikCount; i ++ ) { + + const ik = data.iks[ i ]; + + const param = { + target: ik.target, + effector: ik.effector, + iteration: ik.iteration, + maxAngle: ik.maxAngle * 4, + links: [] + }; + + for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { + + const link = {}; + link.index = ik.links[ j ].index; + link.enabled = true; + + if ( data.bones[ link.index ].name.indexOf( 'ひざ' ) >= 0 ) { + + link.limitation = new Vector3( 1.0, 0.0, 0.0 ); + + } + + param.links.push( link ); + + } + + iks.push( param ); + + } + + } else { + + for ( let i = 0; i < data.metadata.boneCount; i ++ ) { + + const ik = data.bones[ i ].ik; + + if ( ik === undefined ) continue; + + const param = { + target: i, + effector: ik.effector, + iteration: ik.iteration, + maxAngle: ik.maxAngle, + links: [] + }; + + for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { + + const link = {}; + link.index = ik.links[ j ].index; + link.enabled = true; + + if ( ik.links[ j ].angleLimitation === 1 ) { + + // Revert if rotationMin/Max doesn't work well + // link.limitation = new Vector3( 1.0, 0.0, 0.0 ); + + const rotationMin = ik.links[ j ].lowerLimitationAngle; + const rotationMax = ik.links[ j ].upperLimitationAngle; + + // Convert Left to Right coordinate by myself because + // MMDParser doesn't convert. It's a MMDParser's bug + + const tmp1 = - rotationMax[ 0 ]; + const tmp2 = - rotationMax[ 1 ]; + rotationMax[ 0 ] = - rotationMin[ 0 ]; + rotationMax[ 1 ] = - rotationMin[ 1 ]; + rotationMin[ 0 ] = tmp1; + rotationMin[ 1 ] = tmp2; + + link.rotationMin = new Vector3().fromArray( rotationMin ); + link.rotationMax = new Vector3().fromArray( rotationMax ); + + } + + param.links.push( link ); + + } + + iks.push( param ); + + // Save the reference even from bone data for efficiently + // simulating PMX animation system + bones[ i ].ik = param; + + } + + } + + // grants + + if ( data.metadata.format === 'pmx' ) { + + // bone index -> grant entry map + const grantEntryMap = {}; + + for ( let i = 0; i < data.metadata.boneCount; i ++ ) { + + const boneData = data.bones[ i ]; + const grant = boneData.grant; + + if ( grant === undefined ) continue; + + const param = { + index: i, + parentIndex: grant.parentIndex, + ratio: grant.ratio, + isLocal: grant.isLocal, + affectRotation: grant.affectRotation, + affectPosition: grant.affectPosition, + transformationClass: boneData.transformationClass + }; + + grantEntryMap[ i ] = { parent: null, children: [], param: param, visited: false }; + + } + + const rootEntry = { parent: null, children: [], param: null, visited: false }; + + // Build a tree representing grant hierarchy + + for ( const boneIndex in grantEntryMap ) { + + const grantEntry = grantEntryMap[ boneIndex ]; + const parentGrantEntry = grantEntryMap[ grantEntry.parentIndex ] || rootEntry; + + grantEntry.parent = parentGrantEntry; + parentGrantEntry.children.push( grantEntry ); + + } + + // Sort grant parameters from parents to children because + // grant uses parent's transform that parent's grant is already applied + // so grant should be applied in order from parents to children + + function traverse( entry ) { + + if ( entry.param ) { + + grants.push( entry.param ); + + // Save the reference even from bone data for efficiently + // simulating PMX animation system + bones[ entry.param.index ].grant = entry.param; + + } + + entry.visited = true; + + for ( let i = 0, il = entry.children.length; i < il; i ++ ) { + + const child = entry.children[ i ]; + + // Cut off a loop if exists. (Is a grant loop invalid?) + if ( ! child.visited ) traverse( child ); + + } + + } + + traverse( rootEntry ); + + } + + // morph + + function updateAttributes( attribute, morph, ratio ) { + + for ( let i = 0; i < morph.elementCount; i ++ ) { + + const element = morph.elements[ i ]; + + let index; + + if ( data.metadata.format === 'pmd' ) { + + index = data.morphs[ 0 ].elements[ element.index ].index; + + } else { + + index = element.index; + + } + + attribute.array[ index * 3 + 0 ] += element.position[ 0 ] * ratio; + attribute.array[ index * 3 + 1 ] += element.position[ 1 ] * ratio; + attribute.array[ index * 3 + 2 ] += element.position[ 2 ] * ratio; + + } + + } + + for ( let i = 0; i < data.metadata.morphCount; i ++ ) { + + const morph = data.morphs[ i ]; + const params = { name: morph.name }; + + const attribute = new Float32BufferAttribute( data.metadata.vertexCount * 3, 3 ); + attribute.name = morph.name; + + for ( let j = 0; j < data.metadata.vertexCount * 3; j ++ ) { + + attribute.array[ j ] = positions[ j ]; + + } + + if ( data.metadata.format === 'pmd' ) { + + if ( i !== 0 ) { + + updateAttributes( attribute, morph, 1.0 ); + + } + + } else { + + if ( morph.type === 0 ) { // group + + for ( let j = 0; j < morph.elementCount; j ++ ) { + + const morph2 = data.morphs[ morph.elements[ j ].index ]; + const ratio = morph.elements[ j ].ratio; + + if ( morph2.type === 1 ) { + + updateAttributes( attribute, morph2, ratio ); + + } else { + + // TODO: implement + + } + + } + + } else if ( morph.type === 1 ) { // vertex + + updateAttributes( attribute, morph, 1.0 ); + + } else if ( morph.type === 2 ) { // bone + + // TODO: implement + + } else if ( morph.type === 3 ) { // uv + + // TODO: implement + + } else if ( morph.type === 4 ) { // additional uv1 + + // TODO: implement + + } else if ( morph.type === 5 ) { // additional uv2 + + // TODO: implement + + } else if ( morph.type === 6 ) { // additional uv3 + + // TODO: implement + + } else if ( morph.type === 7 ) { // additional uv4 + + // TODO: implement + + } else if ( morph.type === 8 ) { // material + + // TODO: implement + + } + + } + + morphTargets.push( params ); + morphPositions.push( attribute ); + + } + + // rigid bodies from rigidBodies field. + + for ( let i = 0; i < data.metadata.rigidBodyCount; i ++ ) { + + const rigidBody = data.rigidBodies[ i ]; + const params = {}; + + for ( const key in rigidBody ) { + + params[ key ] = rigidBody[ key ]; + + } + + /* + * RigidBody position parameter in PMX seems global position + * while the one in PMD seems offset from corresponding bone. + * So unify being offset. + */ + if ( data.metadata.format === 'pmx' ) { + + if ( params.boneIndex !== - 1 ) { + + const bone = data.bones[ params.boneIndex ]; + params.position[ 0 ] -= bone.position[ 0 ]; + params.position[ 1 ] -= bone.position[ 1 ]; + params.position[ 2 ] -= bone.position[ 2 ]; + + } + + } + + rigidBodies.push( params ); + + } + + // constraints from constraints field. + + for ( let i = 0; i < data.metadata.constraintCount; i ++ ) { + + const constraint = data.constraints[ i ]; + const params = {}; + + for ( const key in constraint ) { + + params[ key ] = constraint[ key ]; + + } + + const bodyA = rigidBodies[ params.rigidBodyIndex1 ]; + const bodyB = rigidBodies[ params.rigidBodyIndex2 ]; + + // Refer to http://www20.atpages.jp/katwat/wp/?p=4135 + if ( bodyA.type !== 0 && bodyB.type === 2 ) { + + if ( bodyA.boneIndex !== - 1 && bodyB.boneIndex !== - 1 && + data.bones[ bodyB.boneIndex ].parentIndex === bodyA.boneIndex ) { + + bodyB.type = 1; + + } + + } + + constraints.push( params ); + + } + + // build BufferGeometry. + + const geometry = new BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) ); + geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) ); + geometry.setIndex( indices ); + + for ( let i = 0, il = groups.length; i < il; i ++ ) { + + geometry.addGroup( groups[ i ].offset, groups[ i ].count, i ); + + } + + geometry.bones = bones; + + geometry.morphTargets = morphTargets; + geometry.morphAttributes.position = morphPositions; + geometry.morphTargetsRelative = false; + + geometry.userData.MMD = { + bones: bones, + iks: iks, + grants: grants, + rigidBodies: rigidBodies, + constraints: constraints, + format: data.metadata.format + }; + + geometry.computeBoundingSphere(); + + return geometry; + + } + +} + +// + +/** + * @param {THREE.LoadingManager} manager + */ +class MaterialBuilder { + + constructor( manager ) { + + this.manager = manager; + + this.textureLoader = new TextureLoader( this.manager ); + this.tgaLoader = null; // lazy generation + + this.crossOrigin = 'anonymous'; + this.resourcePath = undefined; + + } + + /** + * @param {string} crossOrigin + * @return {MaterialBuilder} + */ + setCrossOrigin( crossOrigin ) { + + this.crossOrigin = crossOrigin; + return this; + + } + + /** + * @param {string} resourcePath + * @return {MaterialBuilder} + */ + setResourcePath( resourcePath ) { + + this.resourcePath = resourcePath; + return this; + + } + + /** + * @param {Object} data - parsed PMD/PMX data + * @param {BufferGeometry} geometry - some properties are dependend on geometry + * @param {function} onProgress + * @param {function} onError + * @return {Array} + */ + build( data, geometry /*, onProgress, onError */ ) { + + const materials = []; + + const textures = {}; + + this.textureLoader.setCrossOrigin( this.crossOrigin ); + + // materials + + for ( let i = 0; i < data.metadata.materialCount; i ++ ) { + + const material = data.materials[ i ]; + + const params = { userData: { MMD: {} } }; + + if ( material.name !== undefined ) params.name = material.name; + + /* + * Color + * + * MMD MMDToonMaterial + * ambient - emissive * a + * (a = 1.0 without map texture or 0.2 with map texture) + * + * MMDToonMaterial doesn't have ambient. Set it to emissive instead. + * It'll be too bright if material has map texture so using coef 0.2. + */ + params.diffuse = new Color().fromArray( material.diffuse ); + params.opacity = material.diffuse[ 3 ]; + params.specular = new Color().fromArray( material.specular ); + params.shininess = material.shininess; + params.emissive = new Color().fromArray( material.ambient ); + params.transparent = params.opacity !== 1.0; + + // + + params.fog = true; + + // blend + + params.blending = CustomBlending; + params.blendSrc = SrcAlphaFactor; + params.blendDst = OneMinusSrcAlphaFactor; + params.blendSrcAlpha = SrcAlphaFactor; + params.blendDstAlpha = DstAlphaFactor; + + // side + + if ( data.metadata.format === 'pmx' && ( material.flag & 0x1 ) === 1 ) { + + params.side = DoubleSide; + + } else { + + params.side = params.opacity === 1.0 ? FrontSide : DoubleSide; + + } + + if ( data.metadata.format === 'pmd' ) { + + // map, envMap + + if ( material.fileName ) { + + const fileName = material.fileName; + const fileNames = fileName.split( '*' ); + + // fileNames[ 0 ]: mapFileName + // fileNames[ 1 ]: envMapFileName( optional ) + + params.map = this._loadTexture( fileNames[ 0 ], textures ); + + if ( fileNames.length > 1 ) { + + const extension = fileNames[ 1 ].slice( - 4 ).toLowerCase(); + + params.envMap = this._loadTexture( + fileNames[ 1 ], + textures + ); + + params.combine = extension === '.sph' + ? MultiplyOperation + : AddOperation; + + } + + } + + // gradientMap + + const toonFileName = ( material.toonIndex === - 1 ) + ? 'toon00.bmp' + : data.toonTextures[ material.toonIndex ].fileName; + + params.gradientMap = this._loadTexture( + toonFileName, + textures, + { + isToonTexture: true, + isDefaultToonTexture: this._isDefaultToonTexture( toonFileName ) + } + ); + + // parameters for OutlineEffect + + params.userData.outlineParameters = { + thickness: material.edgeFlag === 1 ? 0.003 : 0.0, + color: [ 0, 0, 0 ], + alpha: 1.0, + visible: material.edgeFlag === 1 + }; + + } else { + + // map + + if ( material.textureIndex !== - 1 ) { + + params.map = this._loadTexture( data.textures[ material.textureIndex ], textures ); + + // Since PMX spec don't have standard to list map files except color map and env map, + // we need to save file name for further mapping, like matching normal map file names after model loaded. + // ref: https://gist.github.com/felixjones/f8a06bd48f9da9a4539f#texture + params.userData.MMD.mapFileName = data.textures[ material.textureIndex ]; + + } + + // envMap TODO: support m.envFlag === 3 + + if ( material.envTextureIndex !== - 1 && ( material.envFlag === 1 || material.envFlag == 2 ) ) { + + params.matcap = this._loadTexture( + data.textures[ material.envTextureIndex ], + textures + ); + + // Same as color map above, keep file name in userData for further usage. + params.userData.MMD.matcapFileName = data.textures[ material.envTextureIndex ]; + + params.matcapCombine = material.envFlag === 1 + ? MultiplyOperation + : AddOperation; + + } + + // gradientMap + + let toonFileName, isDefaultToon; + + if ( material.toonIndex === - 1 || material.toonFlag !== 0 ) { + + toonFileName = 'toon' + ( '0' + ( material.toonIndex + 1 ) ).slice( - 2 ) + '.bmp'; + isDefaultToon = true; + + } else { + + toonFileName = data.textures[ material.toonIndex ]; + isDefaultToon = false; + + } + + params.gradientMap = this._loadTexture( + toonFileName, + textures, + { + isToonTexture: true, + isDefaultToonTexture: isDefaultToon + } + ); + + // parameters for OutlineEffect + params.userData.outlineParameters = { + thickness: material.edgeSize / 300, // TODO: better calculation? + color: material.edgeColor.slice( 0, 3 ), + alpha: material.edgeColor[ 3 ], + visible: ( material.flag & 0x10 ) !== 0 && material.edgeSize > 0.0 + }; + + } + + if ( params.map !== undefined ) { + + if ( ! params.transparent ) { + + this._checkImageTransparency( params.map, geometry, i ); + + } + + params.emissive.multiplyScalar( 0.2 ); + + } + + materials.push( new MMDToonMaterial( params ) ); + + } + + if ( data.metadata.format === 'pmx' ) { + + // set transparent true if alpha morph is defined. + + function checkAlphaMorph( elements, materials ) { + + for ( let i = 0, il = elements.length; i < il; i ++ ) { + + const element = elements[ i ]; + + if ( element.index === - 1 ) continue; + + const material = materials[ element.index ]; + + if ( material.opacity !== element.diffuse[ 3 ] ) { + + material.transparent = true; + + } + + } + + } + + for ( let i = 0, il = data.morphs.length; i < il; i ++ ) { + + const morph = data.morphs[ i ]; + const elements = morph.elements; + + if ( morph.type === 0 ) { + + for ( let j = 0, jl = elements.length; j < jl; j ++ ) { + + const morph2 = data.morphs[ elements[ j ].index ]; + + if ( morph2.type !== 8 ) continue; + + checkAlphaMorph( morph2.elements, materials ); + + } + + } else if ( morph.type === 8 ) { + + checkAlphaMorph( elements, materials ); + + } + + } + + } + + return materials; + + } + + // private methods + + _getTGALoader() { + + if ( this.tgaLoader === null ) { + + if ( TGALoader === undefined ) { + + throw new Error( 'THREE.MMDLoader: Import TGALoader' ); + + } + + this.tgaLoader = new TGALoader( this.manager ); + + } + + return this.tgaLoader; + + } + + _isDefaultToonTexture( name ) { + + if ( name.length !== 10 ) return false; + + return /toon(10|0[0-9])\.bmp/.test( name ); + + } + + _loadTexture( filePath, textures, params, onProgress, onError ) { + + params = params || {}; + + const scope = this; + + let fullPath; + + if ( params.isDefaultToonTexture === true ) { + + let index; + + try { + + index = parseInt( filePath.match( /toon([0-9]{2})\.bmp$/ )[ 1 ] ); + + } catch ( e ) { + + console.warn( 'THREE.MMDLoader: ' + filePath + ' seems like a ' + + 'not right default texture path. Using toon00.bmp instead.' ); + + index = 0; + + } + + fullPath = DEFAULT_TOON_TEXTURES[ index ]; + + } else { + + fullPath = this.resourcePath + filePath; + + } + + if ( textures[ fullPath ] !== undefined ) return textures[ fullPath ]; + + let loader = this.manager.getHandler( fullPath ); + + if ( loader === null ) { + + loader = ( filePath.slice( - 4 ).toLowerCase() === '.tga' ) + ? this._getTGALoader() + : this.textureLoader; + + } + + const texture = loader.load( fullPath, function ( t ) { + + // MMD toon texture is Axis-Y oriented + // but Three.js gradient map is Axis-X oriented. + // So here replaces the toon texture image with the rotated one. + if ( params.isToonTexture === true ) { + + t.image = scope._getRotatedImage( t.image ); + + t.magFilter = NearestFilter; + t.minFilter = NearestFilter; + + } + + t.flipY = false; + t.wrapS = RepeatWrapping; + t.wrapT = RepeatWrapping; + + for ( let i = 0; i < texture.readyCallbacks.length; i ++ ) { + + texture.readyCallbacks[ i ]( texture ); + + } + + delete texture.readyCallbacks; + + }, onProgress, onError ); + + texture.readyCallbacks = []; + + textures[ fullPath ] = texture; + + return texture; + + } + + _getRotatedImage( image ) { + + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + + const width = image.width; + const height = image.height; + + canvas.width = width; + canvas.height = height; + + context.clearRect( 0, 0, width, height ); + context.translate( width / 2.0, height / 2.0 ); + context.rotate( 0.5 * Math.PI ); // 90.0 * Math.PI / 180.0 + context.translate( - width / 2.0, - height / 2.0 ); + context.drawImage( image, 0, 0 ); + + return context.getImageData( 0, 0, width, height ); + + } + + // Check if the partial image area used by the texture is transparent. + _checkImageTransparency( map, geometry, groupIndex ) { + + map.readyCallbacks.push( function ( texture ) { + + // Is there any efficient ways? + function createImageData( image ) { + + const canvas = document.createElement( 'canvas' ); + canvas.width = image.width; + canvas.height = image.height; + + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0 ); + + return context.getImageData( 0, 0, canvas.width, canvas.height ); + + } + + function detectImageTransparency( image, uvs, indices ) { + + const width = image.width; + const height = image.height; + const data = image.data; + const threshold = 253; + + if ( data.length / ( width * height ) !== 4 ) return false; + + for ( let i = 0; i < indices.length; i += 3 ) { + + const centerUV = { x: 0.0, y: 0.0 }; + + for ( let j = 0; j < 3; j ++ ) { + + const index = indices[ i * 3 + j ]; + const uv = { x: uvs[ index * 2 + 0 ], y: uvs[ index * 2 + 1 ] }; + + if ( getAlphaByUv( image, uv ) < threshold ) return true; + + centerUV.x += uv.x; + centerUV.y += uv.y; + + } + + centerUV.x /= 3; + centerUV.y /= 3; + + if ( getAlphaByUv( image, centerUV ) < threshold ) return true; + + } + + return false; + + } + + /* + * This method expects + * texture.flipY = false + * texture.wrapS = RepeatWrapping + * texture.wrapT = RepeatWrapping + * TODO: more precise + */ + function getAlphaByUv( image, uv ) { + + const width = image.width; + const height = image.height; + + let x = Math.round( uv.x * width ) % width; + let y = Math.round( uv.y * height ) % height; + + if ( x < 0 ) x += width; + if ( y < 0 ) y += height; + + const index = y * width + x; + + return image.data[ index * 4 + 3 ]; + + } + + if ( texture.isCompressedTexture === true ) { + + if ( NON_ALPHA_CHANNEL_FORMATS.includes( texture.format ) ) { + + map.transparent = false; + + } else { + + // any other way to check transparency of CompressedTexture? + map.transparent = true; + + } + + return; + + } + + const imageData = texture.image.data !== undefined + ? texture.image + : createImageData( texture.image ); + + const group = geometry.groups[ groupIndex ]; + + if ( detectImageTransparency( + imageData, + geometry.attributes.uv.array, + geometry.index.array.slice( group.start, group.start + group.count ) ) ) { + + map.transparent = true; + + } + + } ); + + } + +} + +// + +class AnimationBuilder { + + /** + * @param {Object} vmd - parsed VMD data + * @param {SkinnedMesh} mesh - tracks will be fitting to mesh + * @return {AnimationClip} + */ + build( vmd, mesh ) { + + // combine skeletal and morph animations + + const tracks = this.buildSkeletalAnimation( vmd, mesh ).tracks; + const tracks2 = this.buildMorphAnimation( vmd, mesh ).tracks; + + for ( let i = 0, il = tracks2.length; i < il; i ++ ) { + + tracks.push( tracks2[ i ] ); + + } + + return new AnimationClip( '', - 1, tracks ); + + } + + /** + * @param {Object} vmd - parsed VMD data + * @param {SkinnedMesh} mesh - tracks will be fitting to mesh + * @return {AnimationClip} + */ + buildSkeletalAnimation( vmd, mesh ) { + + function pushInterpolation( array, interpolation, index ) { + + array.push( interpolation[ index + 0 ] / 127 ); // x1 + array.push( interpolation[ index + 8 ] / 127 ); // x2 + array.push( interpolation[ index + 4 ] / 127 ); // y1 + array.push( interpolation[ index + 12 ] / 127 ); // y2 + + } + + const tracks = []; + + const motions = {}; + const bones = mesh.skeleton.bones; + const boneNameDictionary = {}; + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + boneNameDictionary[ bones[ i ].name ] = true; + + } + + for ( let i = 0; i < vmd.metadata.motionCount; i ++ ) { + + const motion = vmd.motions[ i ]; + const boneName = motion.boneName; + + if ( boneNameDictionary[ boneName ] === undefined ) continue; + + motions[ boneName ] = motions[ boneName ] || []; + motions[ boneName ].push( motion ); + + } + + for ( const key in motions ) { + + const array = motions[ key ]; + + array.sort( function ( a, b ) { + + return a.frameNum - b.frameNum; + + } ); + + const times = []; + const positions = []; + const rotations = []; + const pInterpolations = []; + const rInterpolations = []; + + const basePosition = mesh.skeleton.getBoneByName( key ).position.toArray(); + + for ( let i = 0, il = array.length; i < il; i ++ ) { + + const time = array[ i ].frameNum / 30; + const position = array[ i ].position; + const rotation = array[ i ].rotation; + const interpolation = array[ i ].interpolation; + + times.push( time ); + + for ( let j = 0; j < 3; j ++ ) positions.push( basePosition[ j ] + position[ j ] ); + for ( let j = 0; j < 4; j ++ ) rotations.push( rotation[ j ] ); + for ( let j = 0; j < 3; j ++ ) pushInterpolation( pInterpolations, interpolation, j ); + + pushInterpolation( rInterpolations, interpolation, 3 ); + + } + + const targetName = '.bones[' + key + ']'; + + tracks.push( this._createTrack( targetName + '.position', VectorKeyframeTrack, times, positions, pInterpolations ) ); + tracks.push( this._createTrack( targetName + '.quaternion', QuaternionKeyframeTrack, times, rotations, rInterpolations ) ); + + } + + return new AnimationClip( '', - 1, tracks ); + + } + + /** + * @param {Object} vmd - parsed VMD data + * @param {SkinnedMesh} mesh - tracks will be fitting to mesh + * @return {AnimationClip} + */ + buildMorphAnimation( vmd, mesh ) { + + const tracks = []; + + const morphs = {}; + const morphTargetDictionary = mesh.morphTargetDictionary; + + for ( let i = 0; i < vmd.metadata.morphCount; i ++ ) { + + const morph = vmd.morphs[ i ]; + const morphName = morph.morphName; + + if ( morphTargetDictionary[ morphName ] === undefined ) continue; + + morphs[ morphName ] = morphs[ morphName ] || []; + morphs[ morphName ].push( morph ); + + } + + for ( const key in morphs ) { + + const array = morphs[ key ]; + + array.sort( function ( a, b ) { + + return a.frameNum - b.frameNum; + + } ); + + const times = []; + const values = []; + + for ( let i = 0, il = array.length; i < il; i ++ ) { + + times.push( array[ i ].frameNum / 30 ); + values.push( array[ i ].weight ); + + } + + tracks.push( new NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetDictionary[ key ] + ']', times, values ) ); + + } + + return new AnimationClip( '', - 1, tracks ); + + } + + /** + * @param {Object} vmd - parsed VMD data + * @return {AnimationClip} + */ + buildCameraAnimation( vmd ) { + + function pushVector3( array, vec ) { + + array.push( vec.x ); + array.push( vec.y ); + array.push( vec.z ); + + } + + function pushQuaternion( array, q ) { + + array.push( q.x ); + array.push( q.y ); + array.push( q.z ); + array.push( q.w ); + + } + + function pushInterpolation( array, interpolation, index ) { + + array.push( interpolation[ index * 4 + 0 ] / 127 ); // x1 + array.push( interpolation[ index * 4 + 1 ] / 127 ); // x2 + array.push( interpolation[ index * 4 + 2 ] / 127 ); // y1 + array.push( interpolation[ index * 4 + 3 ] / 127 ); // y2 + + } + + const cameras = vmd.cameras === undefined ? [] : vmd.cameras.slice(); + + cameras.sort( function ( a, b ) { + + return a.frameNum - b.frameNum; + + } ); + + const times = []; + const centers = []; + const quaternions = []; + const positions = []; + const fovs = []; + + const cInterpolations = []; + const qInterpolations = []; + const pInterpolations = []; + const fInterpolations = []; + + const quaternion = new Quaternion(); + const euler = new Euler(); + const position = new Vector3(); + const center = new Vector3(); + + for ( let i = 0, il = cameras.length; i < il; i ++ ) { + + const motion = cameras[ i ]; + + const time = motion.frameNum / 30; + const pos = motion.position; + const rot = motion.rotation; + const distance = motion.distance; + const fov = motion.fov; + const interpolation = motion.interpolation; + + times.push( time ); + + position.set( 0, 0, - distance ); + center.set( pos[ 0 ], pos[ 1 ], pos[ 2 ] ); + + euler.set( - rot[ 0 ], - rot[ 1 ], - rot[ 2 ] ); + quaternion.setFromEuler( euler ); + + position.add( center ); + position.applyQuaternion( quaternion ); + + pushVector3( centers, center ); + pushQuaternion( quaternions, quaternion ); + pushVector3( positions, position ); + + fovs.push( fov ); + + for ( let j = 0; j < 3; j ++ ) { + + pushInterpolation( cInterpolations, interpolation, j ); + + } + + pushInterpolation( qInterpolations, interpolation, 3 ); + + // use the same parameter for x, y, z axis. + for ( let j = 0; j < 3; j ++ ) { + + pushInterpolation( pInterpolations, interpolation, 4 ); + + } + + pushInterpolation( fInterpolations, interpolation, 5 ); + + } + + const tracks = []; + + // I expect an object whose name 'target' exists under THREE.Camera + tracks.push( this._createTrack( 'target.position', VectorKeyframeTrack, times, centers, cInterpolations ) ); + + tracks.push( this._createTrack( '.quaternion', QuaternionKeyframeTrack, times, quaternions, qInterpolations ) ); + tracks.push( this._createTrack( '.position', VectorKeyframeTrack, times, positions, pInterpolations ) ); + tracks.push( this._createTrack( '.fov', NumberKeyframeTrack, times, fovs, fInterpolations ) ); + + return new AnimationClip( '', - 1, tracks ); + + } + + // private method + + _createTrack( node, typedKeyframeTrack, times, values, interpolations ) { + + /* + * optimizes here not to let KeyframeTrackPrototype optimize + * because KeyframeTrackPrototype optimizes times and values but + * doesn't optimize interpolations. + */ + if ( times.length > 2 ) { + + times = times.slice(); + values = values.slice(); + interpolations = interpolations.slice(); + + const stride = values.length / times.length; + const interpolateStride = interpolations.length / times.length; + + let index = 1; + + for ( let aheadIndex = 2, endIndex = times.length; aheadIndex < endIndex; aheadIndex ++ ) { + + for ( let i = 0; i < stride; i ++ ) { + + if ( values[ index * stride + i ] !== values[ ( index - 1 ) * stride + i ] || + values[ index * stride + i ] !== values[ aheadIndex * stride + i ] ) { + + index ++; + break; + + } + + } + + if ( aheadIndex > index ) { + + times[ index ] = times[ aheadIndex ]; + + for ( let i = 0; i < stride; i ++ ) { + + values[ index * stride + i ] = values[ aheadIndex * stride + i ]; + + } + + for ( let i = 0; i < interpolateStride; i ++ ) { + + interpolations[ index * interpolateStride + i ] = interpolations[ aheadIndex * interpolateStride + i ]; + + } + + } + + } + + times.length = index + 1; + values.length = ( index + 1 ) * stride; + interpolations.length = ( index + 1 ) * interpolateStride; + + } + + const track = new typedKeyframeTrack( node, times, values ); + + track.createInterpolant = function InterpolantFactoryMethodCubicBezier( result ) { + + return new CubicBezierInterpolation( this.times, this.values, this.getValueSize(), result, new Float32Array( interpolations ) ); + + }; + + return track; + + } + +} + +// interpolation + +class CubicBezierInterpolation extends Interpolant { + + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer, params ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + this.interpolationParams = params; + + } + + interpolate_( i1, t0, t, t1 ) { + + const result = this.resultBuffer; + const values = this.sampleValues; + const stride = this.valueSize; + const params = this.interpolationParams; + + const offset1 = i1 * stride; + const offset0 = offset1 - stride; + + // No interpolation if next key frame is in one frame in 30fps. + // This is from MMD animation spec. + // '1.5' is for precision loss. times are Float32 in Three.js Animation system. + const weight1 = ( ( t1 - t0 ) < 1 / 30 * 1.5 ) ? 0.0 : ( t - t0 ) / ( t1 - t0 ); + + if ( stride === 4 ) { // Quaternion + + const x1 = params[ i1 * 4 + 0 ]; + const x2 = params[ i1 * 4 + 1 ]; + const y1 = params[ i1 * 4 + 2 ]; + const y2 = params[ i1 * 4 + 3 ]; + + const ratio = this._calculate( x1, x2, y1, y2, weight1 ); + + Quaternion.slerpFlat( result, 0, values, offset0, values, offset1, ratio ); + + } else if ( stride === 3 ) { // Vector3 + + for ( let i = 0; i !== stride; ++ i ) { + + const x1 = params[ i1 * 12 + i * 4 + 0 ]; + const x2 = params[ i1 * 12 + i * 4 + 1 ]; + const y1 = params[ i1 * 12 + i * 4 + 2 ]; + const y2 = params[ i1 * 12 + i * 4 + 3 ]; + + const ratio = this._calculate( x1, x2, y1, y2, weight1 ); + + result[ i ] = values[ offset0 + i ] * ( 1 - ratio ) + values[ offset1 + i ] * ratio; + + } + + } else { // Number + + const x1 = params[ i1 * 4 + 0 ]; + const x2 = params[ i1 * 4 + 1 ]; + const y1 = params[ i1 * 4 + 2 ]; + const y2 = params[ i1 * 4 + 3 ]; + + const ratio = this._calculate( x1, x2, y1, y2, weight1 ); + + result[ 0 ] = values[ offset0 ] * ( 1 - ratio ) + values[ offset1 ] * ratio; + + } + + return result; + + } + + _calculate( x1, x2, y1, y2, x ) { + + /* + * Cubic Bezier curves + * https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves + * + * B(t) = ( 1 - t ) ^ 3 * P0 + * + 3 * ( 1 - t ) ^ 2 * t * P1 + * + 3 * ( 1 - t ) * t^2 * P2 + * + t ^ 3 * P3 + * ( 0 <= t <= 1 ) + * + * MMD uses Cubic Bezier curves for bone and camera animation interpolation. + * http://d.hatena.ne.jp/edvakf/20111016/1318716097 + * + * x = ( 1 - t ) ^ 3 * x0 + * + 3 * ( 1 - t ) ^ 2 * t * x1 + * + 3 * ( 1 - t ) * t^2 * x2 + * + t ^ 3 * x3 + * y = ( 1 - t ) ^ 3 * y0 + * + 3 * ( 1 - t ) ^ 2 * t * y1 + * + 3 * ( 1 - t ) * t^2 * y2 + * + t ^ 3 * y3 + * ( x0 = 0, y0 = 0 ) + * ( x3 = 1, y3 = 1 ) + * ( 0 <= t, x1, x2, y1, y2 <= 1 ) + * + * Here solves this equation with Bisection method, + * https://en.wikipedia.org/wiki/Bisection_method + * gets t, and then calculate y. + * + * f(t) = 3 * ( 1 - t ) ^ 2 * t * x1 + * + 3 * ( 1 - t ) * t^2 * x2 + * + t ^ 3 - x = 0 + * + * (Another option: Newton's method + * https://en.wikipedia.org/wiki/Newton%27s_method) + */ + + let c = 0.5; + let t = c; + let s = 1.0 - t; + const loop = 15; + const eps = 1e-5; + const math = Math; + + let sst3, stt3, ttt; + + for ( let i = 0; i < loop; i ++ ) { + + sst3 = 3.0 * s * s * t; + stt3 = 3.0 * s * t * t; + ttt = t * t * t; + + const ft = ( sst3 * x1 ) + ( stt3 * x2 ) + ( ttt ) - x; + + if ( math.abs( ft ) < eps ) break; + + c /= 2.0; + + t += ( ft < 0 ) ? c : - c; + s = 1.0 - t; + + } + + return ( sst3 * y1 ) + ( stt3 * y2 ) + ttt; + + } + +} + +class MMDToonMaterial extends ShaderMaterial { + + constructor( parameters ) { + + super(); + + this._matcapCombine = AddOperation; + this.emissiveIntensity = 1.0; + this.normalMapType = TangentSpaceNormalMap; + + this.combine = MultiplyOperation; + + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.flatShading = false; + + this.lights = true; + + this.vertexShader = MMDToonShader.vertexShader; + this.fragmentShader = MMDToonShader.fragmentShader; + + this.defines = Object.assign( {}, MMDToonShader.defines ); + Object.defineProperty( this, 'matcapCombine', { + + get: function () { + + return this._matcapCombine; + + }, + + set: function ( value ) { + + this._matcapCombine = value; + + switch ( value ) { + + case MultiplyOperation: + this.defines.MATCAP_BLENDING_MULTIPLY = true; + delete this.defines.MATCAP_BLENDING_ADD; + break; + + default: + case AddOperation: + this.defines.MATCAP_BLENDING_ADD = true; + delete this.defines.MATCAP_BLENDING_MULTIPLY; + break; + + } + + }, + + } ); + + this.uniforms = UniformsUtils.clone( MMDToonShader.uniforms ); + + // merged from MeshToon/Phong/MatcapMaterial + const exposePropertyNames = [ + 'specular', + 'shininess', + 'opacity', + 'diffuse', + + 'map', + 'matcap', + 'gradientMap', + + 'lightMap', + 'lightMapIntensity', + + 'aoMap', + 'aoMapIntensity', + + 'emissive', + 'emissiveMap', + + 'bumpMap', + 'bumpScale', + + 'normalMap', + 'normalScale', + + 'displacemantBias', + 'displacemantMap', + 'displacemantScale', + + 'specularMap', + + 'alphaMap', + + 'envMap', + 'reflectivity', + 'refractionRatio', + ]; + for ( const propertyName of exposePropertyNames ) { + + Object.defineProperty( this, propertyName, { + + get: function () { + + return this.uniforms[ propertyName ].value; + + }, + + set: function ( value ) { + + this.uniforms[ propertyName ].value = value; + + }, + + } ); + + } + + Object.defineProperty( + this, + 'color', + Object.getOwnPropertyDescriptor( this, 'diffuse' ) + ); + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.matcapCombine = source.matcapCombine; + this.emissiveIntensity = source.emissiveIntensity; + this.normalMapType = source.normalMapType; + + this.combine = source.combine; + + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.flatShading = source.flatShading; + + return this; + + } + +} + +MMDToonMaterial.prototype.isMMDToonMaterial = true; + +export { MMDLoader }; diff --git a/jsm/loaders/MTLLoader.js b/jsm/loaders/MTLLoader.js new file mode 100644 index 0000000..7289eb9 --- /dev/null +++ b/jsm/loaders/MTLLoader.js @@ -0,0 +1,567 @@ +import { + Color, + DefaultLoadingManager, + FileLoader, + FrontSide, + Loader, + LoaderUtils, + MeshPhongMaterial, + RepeatWrapping, + TextureLoader, + Vector2, + sRGBEncoding +} from 'three'; + +/** + * Loads a Wavefront .mtl file specifying materials + */ + +class MTLLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + /** + * Loads and parses a MTL asset from a URL. + * + * @param {String} url - URL to the MTL file. + * @param {Function} [onLoad] - Callback invoked with the loaded object. + * @param {Function} [onProgress] - Callback for download progress. + * @param {Function} [onError] - Callback for download errors. + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to load. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + setMaterialOptions( value ) { + + this.materialOptions = value; + return this; + + } + + /** + * Parses a MTL file. + * + * @param {String} text - Content of MTL file + * @return {MaterialCreator} + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to parse. + */ + parse( text, path ) { + + const lines = text.split( '\n' ); + let info = {}; + const delimiter_pattern = /\s+/; + const materialsInfo = {}; + + for ( let i = 0; i < lines.length; i ++ ) { + + let line = lines[ i ]; + line = line.trim(); + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + // Blank line or comment ignore + continue; + + } + + const pos = line.indexOf( ' ' ); + + let key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + key = key.toLowerCase(); + + let value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; + value = value.trim(); + + if ( key === 'newmtl' ) { + + // New material + + info = { name: value }; + materialsInfo[ value ] = info; + + } else { + + if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) { + + const ss = value.split( delimiter_pattern, 3 ); + info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; + + } else { + + info[ key ] = value; + + } + + } + + } + + const materialCreator = new MaterialCreator( this.resourcePath || path, this.materialOptions ); + materialCreator.setCrossOrigin( this.crossOrigin ); + materialCreator.setManager( this.manager ); + materialCreator.setMaterials( materialsInfo ); + return materialCreator; + + } + +} + +/** + * Create a new MTLLoader.MaterialCreator + * @param baseUrl - Url relative to which textures are loaded + * @param options - Set of options on how to construct the materials + * side: Which side to apply the material + * FrontSide (default), THREE.BackSide, THREE.DoubleSide + * wrap: What type of wrapping to apply for textures + * RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping + * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 + * Default: false, assumed to be already normalized + * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's + * Default: false + * @constructor + */ + +class MaterialCreator { + + constructor( baseUrl = '', options = {} ) { + + this.baseUrl = baseUrl; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + this.crossOrigin = 'anonymous'; + + this.side = ( this.options.side !== undefined ) ? this.options.side : FrontSide; + this.wrap = ( this.options.wrap !== undefined ) ? this.options.wrap : RepeatWrapping; + + } + + setCrossOrigin( value ) { + + this.crossOrigin = value; + return this; + + } + + setManager( value ) { + + this.manager = value; + + } + + setMaterials( materialsInfo ) { + + this.materialsInfo = this.convert( materialsInfo ); + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + } + + convert( materialsInfo ) { + + if ( ! this.options ) return materialsInfo; + + const converted = {}; + + for ( const mn in materialsInfo ) { + + // Convert materials info into normalized form based on options + + const mat = materialsInfo[ mn ]; + + const covmat = {}; + + converted[ mn ] = covmat; + + for ( const prop in mat ) { + + let save = true; + let value = mat[ prop ]; + const lprop = prop.toLowerCase(); + + switch ( lprop ) { + + case 'kd': + case 'ka': + case 'ks': + + // Diffuse color (color under white light) using RGB values + + if ( this.options && this.options.normalizeRGB ) { + + value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; + + } + + if ( this.options && this.options.ignoreZeroRGBs ) { + + if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) { + + // ignore + + save = false; + + } + + } + + break; + + default: + + break; + + } + + if ( save ) { + + covmat[ lprop ] = value; + + } + + } + + } + + return converted; + + } + + preload() { + + for ( const mn in this.materialsInfo ) { + + this.create( mn ); + + } + + } + + getIndex( materialName ) { + + return this.nameLookup[ materialName ]; + + } + + getAsArray() { + + let index = 0; + + for ( const mn in this.materialsInfo ) { + + this.materialsArray[ index ] = this.create( mn ); + this.nameLookup[ mn ] = index; + index ++; + + } + + return this.materialsArray; + + } + + create( materialName ) { + + if ( this.materials[ materialName ] === undefined ) { + + this.createMaterial_( materialName ); + + } + + return this.materials[ materialName ]; + + } + + createMaterial_( materialName ) { + + // Create material + + const scope = this; + const mat = this.materialsInfo[ materialName ]; + const params = { + + name: materialName, + side: this.side + + }; + + function resolveURL( baseUrl, url ) { + + if ( typeof url !== 'string' || url === '' ) + return ''; + + // Absolute URL + if ( /^https?:\/\//i.test( url ) ) return url; + + return baseUrl + url; + + } + + function setMapForType( mapType, value ) { + + if ( params[ mapType ] ) return; // Keep the first encountered texture + + const texParams = scope.getTextureParams( value, params ); + const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + + map.repeat.copy( texParams.scale ); + map.offset.copy( texParams.offset ); + + map.wrapS = scope.wrap; + map.wrapT = scope.wrap; + + if ( mapType === 'map' || mapType === 'emissiveMap' ) { + + map.encoding = sRGBEncoding; + + } + + params[ mapType ] = map; + + } + + for ( const prop in mat ) { + + const value = mat[ prop ]; + let n; + + if ( value === '' ) continue; + + switch ( prop.toLowerCase() ) { + + // Ns is material specular exponent + + case 'kd': + + // Diffuse color (color under white light) using RGB values + + params.color = new Color().fromArray( value ).convertSRGBToLinear(); + + break; + + case 'ks': + + // Specular color (color when light is reflected from shiny surface) using RGB values + params.specular = new Color().fromArray( value ).convertSRGBToLinear(); + + break; + + case 'ke': + + // Emissive using RGB values + params.emissive = new Color().fromArray( value ).convertSRGBToLinear(); + + break; + + case 'map_kd': + + // Diffuse texture map + + setMapForType( 'map', value ); + + break; + + case 'map_ks': + + // Specular map + + setMapForType( 'specularMap', value ); + + break; + + case 'map_ke': + + // Emissive map + + setMapForType( 'emissiveMap', value ); + + break; + + case 'norm': + + setMapForType( 'normalMap', value ); + + break; + + case 'map_bump': + case 'bump': + + // Bump texture map + + setMapForType( 'bumpMap', value ); + + break; + + case 'map_d': + + // Alpha map + + setMapForType( 'alphaMap', value ); + params.transparent = true; + + break; + + case 'ns': + + // The specular exponent (defines the focus of the specular highlight) + // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. + + params.shininess = parseFloat( value ); + + break; + + case 'd': + n = parseFloat( value ); + + if ( n < 1 ) { + + params.opacity = n; + params.transparent = true; + + } + + break; + + case 'tr': + n = parseFloat( value ); + + if ( this.options && this.options.invertTrProperty ) n = 1 - n; + + if ( n > 0 ) { + + params.opacity = 1 - n; + params.transparent = true; + + } + + break; + + default: + break; + + } + + } + + this.materials[ materialName ] = new MeshPhongMaterial( params ); + return this.materials[ materialName ]; + + } + + getTextureParams( value, matParams ) { + + const texParams = { + + scale: new Vector2( 1, 1 ), + offset: new Vector2( 0, 0 ) + + }; + + const items = value.split( /\s+/ ); + let pos; + + pos = items.indexOf( '-bm' ); + + if ( pos >= 0 ) { + + matParams.bumpScale = parseFloat( items[ pos + 1 ] ); + items.splice( pos, 2 ); + + } + + pos = items.indexOf( '-s' ); + + if ( pos >= 0 ) { + + texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + pos = items.indexOf( '-o' ); + + if ( pos >= 0 ) { + + texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + texParams.url = items.join( ' ' ).trim(); + return texParams; + + } + + loadTexture( url, mapping, onLoad, onProgress, onError ) { + + const manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager; + let loader = manager.getHandler( url ); + + if ( loader === null ) { + + loader = new TextureLoader( manager ); + + } + + if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); + + const texture = loader.load( url, onLoad, onProgress, onError ); + + if ( mapping !== undefined ) texture.mapping = mapping; + + return texture; + + } + +} + +export { MTLLoader }; diff --git a/jsm/loaders/NRRDLoader.js b/jsm/loaders/NRRDLoader.js new file mode 100644 index 0000000..c595c85 --- /dev/null +++ b/jsm/loaders/NRRDLoader.js @@ -0,0 +1,672 @@ +import { + FileLoader, + Loader, + Matrix4, + Vector3 +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; +import { Volume } from '../misc/Volume.js'; + +class NRRDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( data ) { + + try { + + onLoad( scope.parse( data ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + // this parser is largely inspired from the XTK NRRD parser : https://github.com/xtk/X + + let _data = data; + + let _dataPointer = 0; + + const _nativeLittleEndian = new Int8Array( new Int16Array( [ 1 ] ).buffer )[ 0 ] > 0; + + const _littleEndian = true; + + const headerObject = {}; + + function scan( type, chunks ) { + + if ( chunks === undefined || chunks === null ) { + + chunks = 1; + + } + + let _chunkSize = 1; + let _array_type = Uint8Array; + + switch ( type ) { + + // 1 byte data types + case 'uchar': + break; + case 'schar': + _array_type = Int8Array; + break; + // 2 byte data types + case 'ushort': + _array_type = Uint16Array; + _chunkSize = 2; + break; + case 'sshort': + _array_type = Int16Array; + _chunkSize = 2; + break; + // 4 byte data types + case 'uint': + _array_type = Uint32Array; + _chunkSize = 4; + break; + case 'sint': + _array_type = Int32Array; + _chunkSize = 4; + break; + case 'float': + _array_type = Float32Array; + _chunkSize = 4; + break; + case 'complex': + _array_type = Float64Array; + _chunkSize = 8; + break; + case 'double': + _array_type = Float64Array; + _chunkSize = 8; + break; + + } + + // increase the data pointer in-place + let _bytes = new _array_type( _data.slice( _dataPointer, + _dataPointer += chunks * _chunkSize ) ); + + // if required, flip the endianness of the bytes + if ( _nativeLittleEndian != _littleEndian ) { + + // we need to flip here since the format doesn't match the native endianness + _bytes = flipEndianness( _bytes, _chunkSize ); + + } + + if ( chunks == 1 ) { + + // if only one chunk was requested, just return one value + return _bytes[ 0 ]; + + } + + // return the byte array + return _bytes; + + } + + //Flips typed array endianness in-place. Based on https://github.com/kig/DataStream.js/blob/master/DataStream.js. + + function flipEndianness( array, chunkSize ) { + + const u8 = new Uint8Array( array.buffer, array.byteOffset, array.byteLength ); + for ( let i = 0; i < array.byteLength; i += chunkSize ) { + + for ( let j = i + chunkSize - 1, k = i; j > k; j --, k ++ ) { + + const tmp = u8[ k ]; + u8[ k ] = u8[ j ]; + u8[ j ] = tmp; + + } + + } + + return array; + + } + + //parse the header + function parseHeader( header ) { + + let data, field, fn, i, l, m, _i, _len; + const lines = header.split( /\r?\n/ ); + for ( _i = 0, _len = lines.length; _i < _len; _i ++ ) { + + l = lines[ _i ]; + if ( l.match( /NRRD\d+/ ) ) { + + headerObject.isNrrd = true; + + } else if ( l.match( /^#/ ) ) { + } else if ( m = l.match( /(.*):(.*)/ ) ) { + + field = m[ 1 ].trim(); + data = m[ 2 ].trim(); + fn = _fieldFunctions[ field ]; + if ( fn ) { + + fn.call( headerObject, data ); + + } else { + + headerObject[ field ] = data; + + } + + } + + } + + if ( ! headerObject.isNrrd ) { + + throw new Error( 'Not an NRRD file' ); + + } + + if ( headerObject.encoding === 'bz2' || headerObject.encoding === 'bzip2' ) { + + throw new Error( 'Bzip is not supported' ); + + } + + if ( ! headerObject.vectors ) { + + //if no space direction is set, let's use the identity + headerObject.vectors = [ ]; + headerObject.vectors.push( [ 1, 0, 0 ] ); + headerObject.vectors.push( [ 0, 1, 0 ] ); + headerObject.vectors.push( [ 0, 0, 1 ] ); + + //apply spacing if defined + if ( headerObject.spacings ) { + + for ( i = 0; i <= 2; i ++ ) { + + if ( ! isNaN( headerObject.spacings[ i ] ) ) { + + for ( let j = 0; j <= 2; j ++ ) { + + headerObject.vectors[ i ][ j ] *= headerObject.spacings[ i ]; + + } + + } + + } + + } + + } + + } + + //parse the data when registred as one of this type : 'text', 'ascii', 'txt' + function parseDataAsText( data, start, end ) { + + let number = ''; + start = start || 0; + end = end || data.length; + let value; + //length of the result is the product of the sizes + const lengthOfTheResult = headerObject.sizes.reduce( function ( previous, current ) { + + return previous * current; + + }, 1 ); + + let base = 10; + if ( headerObject.encoding === 'hex' ) { + + base = 16; + + } + + const result = new headerObject.__array( lengthOfTheResult ); + let resultIndex = 0; + let parsingFunction = parseInt; + if ( headerObject.__array === Float32Array || headerObject.__array === Float64Array ) { + + parsingFunction = parseFloat; + + } + + for ( let i = start; i < end; i ++ ) { + + value = data[ i ]; + //if value is not a space + if ( ( value < 9 || value > 13 ) && value !== 32 ) { + + number += String.fromCharCode( value ); + + } else { + + if ( number !== '' ) { + + result[ resultIndex ] = parsingFunction( number, base ); + resultIndex ++; + + } + + number = ''; + + } + + } + + if ( number !== '' ) { + + result[ resultIndex ] = parsingFunction( number, base ); + resultIndex ++; + + } + + return result; + + } + + const _bytes = scan( 'uchar', data.byteLength ); + const _length = _bytes.length; + let _header = null; + let _data_start = 0; + let i; + for ( i = 1; i < _length; i ++ ) { + + if ( _bytes[ i - 1 ] == 10 && _bytes[ i ] == 10 ) { + + // we found two line breaks in a row + // now we know what the header is + _header = this.parseChars( _bytes, 0, i - 2 ); + // this is were the data starts + _data_start = i + 1; + break; + + } + + } + + // parse the header + parseHeader( _header ); + + _data = _bytes.subarray( _data_start ); // the data without header + if ( headerObject.encoding.substring( 0, 2 ) === 'gz' ) { + + // we need to decompress the datastream + // here we start the unzipping and get a typed Uint8Array back + _data = fflate.gunzipSync( new Uint8Array( _data ) );// eslint-disable-line no-undef + + } else if ( headerObject.encoding === 'ascii' || headerObject.encoding === 'text' || headerObject.encoding === 'txt' || headerObject.encoding === 'hex' ) { + + _data = parseDataAsText( _data ); + + } else if ( headerObject.encoding === 'raw' ) { + + //we need to copy the array to create a new array buffer, else we retrieve the original arraybuffer with the header + const _copy = new Uint8Array( _data.length ); + + for ( let i = 0; i < _data.length; i ++ ) { + + _copy[ i ] = _data[ i ]; + + } + + _data = _copy; + + } + + // .. let's use the underlying array buffer + _data = _data.buffer; + + const volume = new Volume(); + volume.header = headerObject; + // + // parse the (unzipped) data to a datastream of the correct type + // + volume.data = new headerObject.__array( _data ); + // get the min and max intensities + const min_max = volume.computeMinMax(); + const min = min_max[ 0 ]; + const max = min_max[ 1 ]; + // attach the scalar range to the volume + volume.windowLow = min; + volume.windowHigh = max; + + // get the image dimensions + volume.dimensions = [ headerObject.sizes[ 0 ], headerObject.sizes[ 1 ], headerObject.sizes[ 2 ] ]; + volume.xLength = volume.dimensions[ 0 ]; + volume.yLength = volume.dimensions[ 1 ]; + volume.zLength = volume.dimensions[ 2 ]; + + // Identify axis order in the space-directions matrix from the header if possible. + if ( headerObject.vectors ) { + + const xIndex = headerObject.vectors.findIndex( vector => vector[ 0 ] !== 0 ); + const yIndex = headerObject.vectors.findIndex( vector => vector[ 1 ] !== 0 ); + const zIndex = headerObject.vectors.findIndex( vector => vector[ 2 ] !== 0 ); + + const axisOrder = []; + axisOrder[ xIndex ] = 'x'; + axisOrder[ yIndex ] = 'y'; + axisOrder[ zIndex ] = 'z'; + volume.axisOrder = axisOrder; + + } else { + + volume.axisOrder = [ 'x', 'y', 'z' ]; + + } + + // spacing + const spacingX = new Vector3().fromArray( headerObject.vectors[ 0 ] ).length(); + const spacingY = new Vector3().fromArray( headerObject.vectors[ 1 ] ).length(); + const spacingZ = new Vector3().fromArray( headerObject.vectors[ 2 ] ).length(); + volume.spacing = [ spacingX, spacingY, spacingZ ]; + + + // Create IJKtoRAS matrix + volume.matrix = new Matrix4(); + + const transitionMatrix = new Matrix4(); + + if ( headerObject.space === 'left-posterior-superior' ) { + + transitionMatrix.set( + - 1, 0, 0, 0, + 0, - 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + + } else if ( headerObject.space === 'left-anterior-superior' ) { + + transitionMatrix.set( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, - 1, 0, + 0, 0, 0, 1 + ); + + } + + + if ( ! headerObject.vectors ) { + + volume.matrix.set( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 ); + + } else { + + const v = headerObject.vectors; + + const ijk_to_transition = new Matrix4().set( + v[ 0 ][ 0 ], v[ 1 ][ 0 ], v[ 2 ][ 0 ], 0, + v[ 0 ][ 1 ], v[ 1 ][ 1 ], v[ 2 ][ 1 ], 0, + v[ 0 ][ 2 ], v[ 1 ][ 2 ], v[ 2 ][ 2 ], 0, + 0, 0, 0, 1 + ); + + const transition_to_ras = new Matrix4().multiplyMatrices( ijk_to_transition, transitionMatrix ); + + volume.matrix = transition_to_ras; + + } + + volume.inverseMatrix = new Matrix4(); + volume.inverseMatrix.copy( volume.matrix ).invert(); + volume.RASDimensions = new Vector3( volume.xLength, volume.yLength, volume.zLength ).applyMatrix4( volume.matrix ).round().toArray().map( Math.abs ); + + // .. and set the default threshold + // only if the threshold was not already set + if ( volume.lowerThreshold === - Infinity ) { + + volume.lowerThreshold = min; + + } + + if ( volume.upperThreshold === Infinity ) { + + volume.upperThreshold = max; + + } + + return volume; + + } + + parseChars( array, start, end ) { + + // without borders, use the whole array + if ( start === undefined ) { + + start = 0; + + } + + if ( end === undefined ) { + + end = array.length; + + } + + let output = ''; + // create and append the chars + let i = 0; + for ( i = start; i < end; ++ i ) { + + output += String.fromCharCode( array[ i ] ); + + } + + return output; + + } + +} + +const _fieldFunctions = { + + type: function ( data ) { + + switch ( data ) { + + case 'uchar': + case 'unsigned char': + case 'uint8': + case 'uint8_t': + this.__array = Uint8Array; + break; + case 'signed char': + case 'int8': + case 'int8_t': + this.__array = Int8Array; + break; + case 'short': + case 'short int': + case 'signed short': + case 'signed short int': + case 'int16': + case 'int16_t': + this.__array = Int16Array; + break; + case 'ushort': + case 'unsigned short': + case 'unsigned short int': + case 'uint16': + case 'uint16_t': + this.__array = Uint16Array; + break; + case 'int': + case 'signed int': + case 'int32': + case 'int32_t': + this.__array = Int32Array; + break; + case 'uint': + case 'unsigned int': + case 'uint32': + case 'uint32_t': + this.__array = Uint32Array; + break; + case 'float': + this.__array = Float32Array; + break; + case 'double': + this.__array = Float64Array; + break; + default: + throw new Error( 'Unsupported NRRD data type: ' + data ); + + } + + return this.type = data; + + }, + + endian: function ( data ) { + + return this.endian = data; + + }, + + encoding: function ( data ) { + + return this.encoding = data; + + }, + + dimension: function ( data ) { + + return this.dim = parseInt( data, 10 ); + + }, + + sizes: function ( data ) { + + let i; + return this.sizes = ( function () { + + const _ref = data.split( /\s+/ ); + const _results = []; + + for ( let _i = 0, _len = _ref.length; _i < _len; _i ++ ) { + + i = _ref[ _i ]; + _results.push( parseInt( i, 10 ) ); + + } + + return _results; + + } )(); + + }, + + space: function ( data ) { + + return this.space = data; + + }, + + 'space origin': function ( data ) { + + return this.space_origin = data.split( '(' )[ 1 ].split( ')' )[ 0 ].split( ',' ); + + }, + + 'space directions': function ( data ) { + + let f, v; + const parts = data.match( /\(.*?\)/g ); + return this.vectors = ( function () { + + const _results = []; + + for ( let _i = 0, _len = parts.length; _i < _len; _i ++ ) { + + v = parts[ _i ]; + _results.push( ( function () { + + const _ref = v.slice( 1, - 1 ).split( /,/ ); + const _results2 = []; + + for ( let _j = 0, _len2 = _ref.length; _j < _len2; _j ++ ) { + + f = _ref[ _j ]; + _results2.push( parseFloat( f ) ); + + } + + return _results2; + + } )() ); + + } + + return _results; + + } )(); + + }, + + spacings: function ( data ) { + + let f; + const parts = data.split( /\s+/ ); + return this.spacings = ( function () { + + const _results = []; + + for ( let _i = 0, _len = parts.length; _i < _len; _i ++ ) { + + f = parts[ _i ]; + _results.push( parseFloat( f ) ); + + } + + return _results; + + } )(); + + } + +}; + +export { NRRDLoader }; diff --git a/jsm/loaders/OBJLoader.js b/jsm/loaders/OBJLoader.js new file mode 100644 index 0000000..1c7a9a8 --- /dev/null +++ b/jsm/loaders/OBJLoader.js @@ -0,0 +1,913 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Group, + LineBasicMaterial, + LineSegments, + Loader, + Material, + Mesh, + MeshPhongMaterial, + Points, + PointsMaterial, + Vector3, + Color +} from '../../src/Three.js'; + +// o object_name | g group_name +const _object_pattern = /^[og]\s*(.+)?/; +// mtllib file_reference +const _material_library_pattern = /^mtllib /; +// usemtl material_name +const _material_use_pattern = /^usemtl /; +// usemap map_name +const _map_use_pattern = /^usemap /; + +const _vA = new Vector3(); +const _vB = new Vector3(); +const _vC = new Vector3(); + +const _ab = new Vector3(); +const _cb = new Vector3(); + +const _color = new Color(); + +function ParserState() { + + const state = { + objects: [], + object: {}, + + vertices: [], + normals: [], + colors: [], + uvs: [], + + materials: {}, + materialLibraries: [], + + startObject: function ( name, fromDeclaration ) { + + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { + + this.object.name = name; + this.object.fromDeclaration = ( fromDeclaration !== false ); + return; + + } + + const previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + this.object = { + name: name || '', + fromDeclaration: ( fromDeclaration !== false ), + + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [], + hasUVIndices: false + }, + materials: [], + smooth: true, + + startMaterial: function ( name, libraries ) { + + const previous = this._finalize( false ); + + // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + + this.materials.splice( previous.index, 1 ); + + } + + const material = { + index: this.materials.length, + name: name || '', + mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth: ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd: - 1, + groupCount: - 1, + inherited: false, + + clone: function ( index ) { + + const cloned = { + index: ( typeof index === 'number' ? index : this.index ), + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; + + } + }; + + this.materials.push( material ); + + return material; + + }, + + currentMaterial: function () { + + if ( this.materials.length > 0 ) { + + return this.materials[ this.materials.length - 1 ]; + + } + + return undefined; + + }, + + _finalize: function ( end ) { + + const lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { + + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; + + } + + // Ignore objects tail materials if no face declarations followed them before a new o/g started. + if ( end && this.materials.length > 1 ) { + + for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) { + + if ( this.materials[ mi ].groupCount <= 0 ) { + + this.materials.splice( mi, 1 ); + + } + + } + + } + + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end && this.materials.length === 0 ) { + + this.materials.push( { + name: '', + smooth: this.smooth + } ); + + } + + return lastMultiMaterial; + + } + }; + + // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. + + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { + + const declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); + + } + + this.objects.push( this.object ); + + }, + + finalize: function () { + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + }, + + parseVertexIndex: function ( value, len ) { + + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseNormalIndex: function ( value, len ) { + + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseUVIndex: function ( value, len ) { + + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + + }, + + addVertex: function ( a, b, c ) { + + const src = this.vertices; + const dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addVertexPoint: function ( a ) { + + const src = this.vertices; + const dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addVertexLine: function ( a ) { + + const src = this.vertices; + const dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addNormal: function ( a, b, c ) { + + const src = this.normals; + const dst = this.object.geometry.normals; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addFaceNormal: function ( a, b, c ) { + + const src = this.vertices; + const dst = this.object.geometry.normals; + + _vA.fromArray( src, a ); + _vB.fromArray( src, b ); + _vC.fromArray( src, c ); + + _cb.subVectors( _vC, _vB ); + _ab.subVectors( _vA, _vB ); + _cb.cross( _ab ); + + _cb.normalize(); + + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); + + }, + + addColor: function ( a, b, c ) { + + const src = this.colors; + const dst = this.object.geometry.colors; + + if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addUV: function ( a, b, c ) { + + const src = this.uvs; + const dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); + + }, + + addDefaultUV: function () { + + const dst = this.object.geometry.uvs; + + dst.push( 0, 0 ); + dst.push( 0, 0 ); + dst.push( 0, 0 ); + + }, + + addUVLine: function ( a ) { + + const src = this.uvs; + const dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + + }, + + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { + + const vLen = this.vertices.length; + + let ia = this.parseVertexIndex( a, vLen ); + let ib = this.parseVertexIndex( b, vLen ); + let ic = this.parseVertexIndex( c, vLen ); + + this.addVertex( ia, ib, ic ); + this.addColor( ia, ib, ic ); + + // normals + + if ( na !== undefined && na !== '' ) { + + const nLen = this.normals.length; + + ia = this.parseNormalIndex( na, nLen ); + ib = this.parseNormalIndex( nb, nLen ); + ic = this.parseNormalIndex( nc, nLen ); + + this.addNormal( ia, ib, ic ); + + } else { + + this.addFaceNormal( ia, ib, ic ); + + } + + // uvs + + if ( ua !== undefined && ua !== '' ) { + + const uvLen = this.uvs.length; + + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); + + this.addUV( ia, ib, ic ); + + this.object.geometry.hasUVIndices = true; + + } else { + + // add placeholder values (for inconsistent face definitions) + + this.addDefaultUV(); + + } + + }, + + addPointGeometry: function ( vertices ) { + + this.object.geometry.type = 'Points'; + + const vLen = this.vertices.length; + + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { + + const index = this.parseVertexIndex( vertices[ vi ], vLen ); + + this.addVertexPoint( index ); + this.addColor( index ); + + } + + }, + + addLineGeometry: function ( vertices, uvs ) { + + this.object.geometry.type = 'Line'; + + const vLen = this.vertices.length; + const uvLen = this.uvs.length; + + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + + } + + } + + }; + + state.startObject( '', false ); + + return state; + +} + +// + +class OBJLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.materials = null; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + setMaterials( materials ) { + + this.materials = materials; + + return this; + + } + + parse( text ) { + + const state = new ParserState(); + + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + if ( text.indexOf( '\\\n' ) !== - 1 ) { + + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); + + } + + const lines = text.split( '\n' ); + let line = '', lineFirstChar = ''; + let lineLength = 0; + let result = []; + + // Faster to just trim left side of the line. Use if available. + const trimLeft = ( typeof ''.trimLeft === 'function' ); + + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + line = lines[ i ]; + + line = trimLeft ? line.trimLeft() : line.trim(); + + lineLength = line.length; + + if ( lineLength === 0 ) continue; + + lineFirstChar = line.charAt( 0 ); + + // @todo invoke passed in handler if any + if ( lineFirstChar === '#' ) continue; + + if ( lineFirstChar === 'v' ) { + + const data = line.split( /\s+/ ); + + switch ( data[ 0 ] ) { + + case 'v': + state.vertices.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + if ( data.length >= 7 ) { + + _color.setRGB( + parseFloat( data[ 4 ] ), + parseFloat( data[ 5 ] ), + parseFloat( data[ 6 ] ) + ).convertSRGBToLinear(); + + state.colors.push( _color.r, _color.g, _color.b ); + + } else { + + // if no colors are defined, add placeholders so color and vertex indices match + + state.colors.push( undefined, undefined, undefined ); + + } + + break; + case 'vn': + state.normals.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + break; + case 'vt': + state.uvs.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ) + ); + break; + + } + + } else if ( lineFirstChar === 'f' ) { + + const lineData = line.slice( 1 ).trim(); + const vertexData = lineData.split( /\s+/ ); + const faceVertices = []; + + // Parse the face vertex data into an easy to work with format + + for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) { + + const vertex = vertexData[ j ]; + + if ( vertex.length > 0 ) { + + const vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); + + } + + } + + // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + + const v1 = faceVertices[ 0 ]; + + for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + + const v2 = faceVertices[ j ]; + const v3 = faceVertices[ j + 1 ]; + + state.addFace( + v1[ 0 ], v2[ 0 ], v3[ 0 ], + v1[ 1 ], v2[ 1 ], v3[ 1 ], + v1[ 2 ], v2[ 2 ], v3[ 2 ] + ); + + } + + } else if ( lineFirstChar === 'l' ) { + + const lineParts = line.substring( 1 ).trim().split( ' ' ); + let lineVertices = []; + const lineUVs = []; + + if ( line.indexOf( '/' ) === - 1 ) { + + lineVertices = lineParts; + + } else { + + for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) { + + const parts = lineParts[ li ].split( '/' ); + + if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); + + } + + } + + state.addLineGeometry( lineVertices, lineUVs ); + + } else if ( lineFirstChar === 'p' ) { + + const lineData = line.slice( 1 ).trim(); + const pointData = lineData.split( ' ' ); + + state.addPointGeometry( pointData ); + + } else if ( ( result = _object_pattern.exec( line ) ) !== null ) { + + // o object_name + // or + // g group_name + + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // let name = result[ 0 ].slice( 1 ).trim(); + const name = ( ' ' + result[ 0 ].slice( 1 ).trim() ).slice( 1 ); + + state.startObject( name ); + + } else if ( _material_use_pattern.test( line ) ) { + + // material + + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + + } else if ( _material_library_pattern.test( line ) ) { + + // mtl file + + state.materialLibraries.push( line.substring( 7 ).trim() ); + + } else if ( _map_use_pattern.test( line ) ) { + + // the line is parsed but ignored since the loader assumes textures are defined MTL files + // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) + + console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); + + } else if ( lineFirstChar === 's' ) { + + result = line.split( ' ' ); + + // smooth shading + + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + + /* + * http://paulbourke.net/dataformats/obj/ + * + * From chapter "Grouping" Syntax explanation "s group_number": + * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. + * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form + * surfaces, smoothing groups are either turned on or off; there is no difference between values greater + * than 0." + */ + if ( result.length > 1 ) { + + const value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = ( value !== '0' && value !== 'off' ); + + } else { + + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; + + } + + const material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; + + } else { + + // Handle null terminated files without exception + if ( line === '\0' ) continue; + + console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + + } + + } + + state.finalize(); + + const container = new Group(); + container.materialLibraries = [].concat( state.materialLibraries ); + + const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); + + if ( hasPrimitives === true ) { + + for ( let i = 0, l = state.objects.length; i < l; i ++ ) { + + const object = state.objects[ i ]; + const geometry = object.geometry; + const materials = object.materials; + const isLine = ( geometry.type === 'Line' ); + const isPoints = ( geometry.type === 'Points' ); + let hasVertexColors = false; + + // Skip o/g line declarations that did not follow with any faces + if ( geometry.vertices.length === 0 ) continue; + + const buffergeometry = new BufferGeometry(); + + buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) ); + + if ( geometry.normals.length > 0 ) { + + buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) ); + + } + + if ( geometry.colors.length > 0 ) { + + hasVertexColors = true; + buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) ); + + } + + if ( geometry.hasUVIndices === true ) { + + buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) ); + + } + + // Create materials + + const createdMaterials = []; + + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + const sourceMaterial = materials[ mi ]; + const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; + let material = state.materials[ materialHash ]; + + if ( this.materials !== null ) { + + material = this.materials.create( sourceMaterial.name ); + + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) { + + const materialLine = new LineBasicMaterial(); + Material.prototype.copy.call( materialLine, material ); + materialLine.color.copy( material.color ); + material = materialLine; + + } else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) { + + const materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } ); + Material.prototype.copy.call( materialPoints, material ); + materialPoints.color.copy( material.color ); + materialPoints.map = material.map; + material = materialPoints; + + } + + } + + if ( material === undefined ) { + + if ( isLine ) { + + material = new LineBasicMaterial(); + + } else if ( isPoints ) { + + material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); + + } else { + + material = new MeshPhongMaterial(); + + } + + material.name = sourceMaterial.name; + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors; + + state.materials[ materialHash ] = material; + + } + + createdMaterials.push( material ); + + } + + // Create mesh + + let mesh; + + if ( createdMaterials.length > 1 ) { + + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + const sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + + } + + if ( isLine ) { + + mesh = new LineSegments( buffergeometry, createdMaterials ); + + } else if ( isPoints ) { + + mesh = new Points( buffergeometry, createdMaterials ); + + } else { + + mesh = new Mesh( buffergeometry, createdMaterials ); + + } + + } else { + + if ( isLine ) { + + mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] ); + + } else if ( isPoints ) { + + mesh = new Points( buffergeometry, createdMaterials[ 0 ] ); + + } else { + + mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] ); + + } + + } + + mesh.name = object.name; + + container.add( mesh ); + + } + + } else { + + // if there is only the default parser state object with no geometry data, interpret data as point cloud + + if ( state.vertices.length > 0 ) { + + const material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); + + const buffergeometry = new BufferGeometry(); + + buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) ); + + if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { + + buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) ); + material.vertexColors = true; + + } + + const points = new Points( buffergeometry, material ); + container.add( points ); + + } + + } + + return container; + + } + +} + +export { OBJLoader }; diff --git a/jsm/loaders/PCDLoader.js b/jsm/loaders/PCDLoader.js new file mode 100644 index 0000000..2a1784c --- /dev/null +++ b/jsm/loaders/PCDLoader.js @@ -0,0 +1,413 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + LoaderUtils, + Points, + PointsMaterial +} from 'three'; + +class PCDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.littleEndian = true; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( data ) { + + try { + + onLoad( scope.parse( data, url ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data, url ) { + + // from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js + + function decompressLZF( inData, outLength ) { + + const inLength = inData.length; + const outData = new Uint8Array( outLength ); + let inPtr = 0; + let outPtr = 0; + let ctrl; + let len; + let ref; + do { + + ctrl = inData[ inPtr ++ ]; + if ( ctrl < ( 1 << 5 ) ) { + + ctrl ++; + if ( outPtr + ctrl > outLength ) throw new Error( 'Output buffer is not large enough' ); + if ( inPtr + ctrl > inLength ) throw new Error( 'Invalid compressed data' ); + do { + + outData[ outPtr ++ ] = inData[ inPtr ++ ]; + + } while ( -- ctrl ); + + } else { + + len = ctrl >> 5; + ref = outPtr - ( ( ctrl & 0x1f ) << 8 ) - 1; + if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' ); + if ( len === 7 ) { + + len += inData[ inPtr ++ ]; + if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' ); + + } + + ref -= inData[ inPtr ++ ]; + if ( outPtr + len + 2 > outLength ) throw new Error( 'Output buffer is not large enough' ); + if ( ref < 0 ) throw new Error( 'Invalid compressed data' ); + if ( ref >= outPtr ) throw new Error( 'Invalid compressed data' ); + do { + + outData[ outPtr ++ ] = outData[ ref ++ ]; + + } while ( -- len + 2 ); + + } + + } while ( inPtr < inLength ); + + return outData; + + } + + function parseHeader( data ) { + + const PCDheader = {}; + const result1 = data.search( /[\r\n]DATA\s(\S*)\s/i ); + const result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.slice( result1 - 1 ) ); + + PCDheader.data = result2[ 1 ]; + PCDheader.headerLen = result2[ 0 ].length + result1; + PCDheader.str = data.slice( 0, PCDheader.headerLen ); + + // remove comments + + PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' ); + + // parse + + PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str ); + PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str ); + PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str ); + PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str ); + PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str ); + PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str ); + PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str ); + PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str ); + PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str ); + + // evaluate + + if ( PCDheader.version !== null ) + PCDheader.version = parseFloat( PCDheader.version[ 1 ] ); + + PCDheader.fields = ( PCDheader.fields !== null ) ? PCDheader.fields[ 1 ].split( ' ' ) : []; + + if ( PCDheader.type !== null ) + PCDheader.type = PCDheader.type[ 1 ].split( ' ' ); + + if ( PCDheader.width !== null ) + PCDheader.width = parseInt( PCDheader.width[ 1 ] ); + + if ( PCDheader.height !== null ) + PCDheader.height = parseInt( PCDheader.height[ 1 ] ); + + if ( PCDheader.viewpoint !== null ) + PCDheader.viewpoint = PCDheader.viewpoint[ 1 ]; + + if ( PCDheader.points !== null ) + PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 ); + + if ( PCDheader.points === null ) + PCDheader.points = PCDheader.width * PCDheader.height; + + if ( PCDheader.size !== null ) { + + PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) { + + return parseInt( x, 10 ); + + } ); + + } + + if ( PCDheader.count !== null ) { + + PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) { + + return parseInt( x, 10 ); + + } ); + + } else { + + PCDheader.count = []; + + for ( let i = 0, l = PCDheader.fields.length; i < l; i ++ ) { + + PCDheader.count.push( 1 ); + + } + + } + + PCDheader.offset = {}; + + let sizeSum = 0; + + for ( let i = 0, l = PCDheader.fields.length; i < l; i ++ ) { + + if ( PCDheader.data === 'ascii' ) { + + PCDheader.offset[ PCDheader.fields[ i ] ] = i; + + } else { + + PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum; + sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ]; + + } + + } + + // for binary only + + PCDheader.rowSize = sizeSum; + + return PCDheader; + + } + + const textData = LoaderUtils.decodeText( new Uint8Array( data ) ); + + // parse header (always ascii format) + + const PCDheader = parseHeader( textData ); + + // parse data + + const position = []; + const normal = []; + const color = []; + + // ascii + + if ( PCDheader.data === 'ascii' ) { + + const offset = PCDheader.offset; + const pcdData = textData.slice( PCDheader.headerLen ); + const lines = pcdData.split( '\n' ); + + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + if ( lines[ i ] === '' ) continue; + + const line = lines[ i ].split( ' ' ); + + if ( offset.x !== undefined ) { + + position.push( parseFloat( line[ offset.x ] ) ); + position.push( parseFloat( line[ offset.y ] ) ); + position.push( parseFloat( line[ offset.z ] ) ); + + } + + if ( offset.rgb !== undefined ) { + + const rgb_field_index = PCDheader.fields.findIndex( ( field ) => field === 'rgb' ); + const rgb_type = PCDheader.type[ rgb_field_index ]; + + const float = parseFloat( line[ offset.rgb ] ); + let rgb = float; + + if ( rgb_type === 'F' ) { + + // treat float values as int + // https://github.com/daavoo/pyntcloud/pull/204/commits/7b4205e64d5ed09abe708b2e91b615690c24d518 + const farr = new Float32Array( 1 ); + farr[ 0 ] = float; + rgb = new Int32Array( farr.buffer )[ 0 ]; + + } + + const r = ( rgb >> 16 ) & 0x0000ff; + const g = ( rgb >> 8 ) & 0x0000ff; + const b = ( rgb >> 0 ) & 0x0000ff; + color.push( r / 255, g / 255, b / 255 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( parseFloat( line[ offset.normal_x ] ) ); + normal.push( parseFloat( line[ offset.normal_y ] ) ); + normal.push( parseFloat( line[ offset.normal_z ] ) ); + + } + + } + + } + + // binary-compressed + + // normally data in PCD files are organized as array of structures: XYZRGBXYZRGB + // binary compressed PCD files organize their data as structure of arrays: XXYYZZRGBRGB + // that requires a totally different parsing approach compared to non-compressed data + + if ( PCDheader.data === 'binary_compressed' ) { + + const sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) ); + const compressedSize = sizes[ 0 ]; + const decompressedSize = sizes[ 1 ]; + const decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize ); + const dataview = new DataView( decompressed.buffer ); + + const offset = PCDheader.offset; + + for ( let i = 0; i < PCDheader.points; i ++ ) { + + if ( offset.x !== undefined ) { + + position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ 0 ] * i, this.littleEndian ) ); + position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ 1 ] * i, this.littleEndian ) ); + position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ 2 ] * i, this.littleEndian ) ); + + } + + if ( offset.rgb !== undefined ) { + + color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 2 ) / 255.0 ); + color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 1 ) / 255.0 ); + color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 0 ) / 255.0 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ 4 ] * i, this.littleEndian ) ); + normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ 5 ] * i, this.littleEndian ) ); + normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ 6 ] * i, this.littleEndian ) ); + + } + + } + + } + + // binary + + if ( PCDheader.data === 'binary' ) { + + const dataview = new DataView( data, PCDheader.headerLen ); + const offset = PCDheader.offset; + + for ( let i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) { + + if ( offset.x !== undefined ) { + + position.push( dataview.getFloat32( row + offset.x, this.littleEndian ) ); + position.push( dataview.getFloat32( row + offset.y, this.littleEndian ) ); + position.push( dataview.getFloat32( row + offset.z, this.littleEndian ) ); + + } + + if ( offset.rgb !== undefined ) { + + color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 ); + color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 ); + color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) ); + normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) ); + normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) ); + + } + + } + + } + + // build geometry + + const geometry = new BufferGeometry(); + + if ( position.length > 0 ) geometry.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + if ( normal.length > 0 ) geometry.setAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) ); + if ( color.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( color, 3 ) ); + + geometry.computeBoundingSphere(); + + // build material + + const material = new PointsMaterial( { size: 0.005 } ); + + if ( color.length > 0 ) { + + material.vertexColors = true; + + } else { + + material.color.setHex( Math.random() * 0xffffff ); + + } + + // build point cloud + + const mesh = new Points( geometry, material ); + let name = url.split( '' ).reverse().join( '' ); + name = /([^\/]*)/.exec( name ); + name = name[ 1 ].split( '' ).reverse().join( '' ); + mesh.name = name; + + return mesh; + + } + +} + +export { PCDLoader }; diff --git a/jsm/loaders/PDBLoader.js b/jsm/loaders/PDBLoader.js new file mode 100644 index 0000000..87c048c --- /dev/null +++ b/jsm/loaders/PDBLoader.js @@ -0,0 +1,227 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader +} from 'three'; + +class PDBLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + // Based on CanvasMol PDB parser + + parse( text ) { + + function trim( text ) { + + return text.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); + + } + + function capitalize( text ) { + + return text.charAt( 0 ).toUpperCase() + text.slice( 1 ).toLowerCase(); + + } + + function hash( s, e ) { + + return 's' + Math.min( s, e ) + 'e' + Math.max( s, e ); + + } + + function parseBond( start, length, satom, i ) { + + const eatom = parseInt( lines[ i ].slice( start, start + length ) ); + + if ( eatom ) { + + const h = hash( satom, eatom ); + + if ( _bhash[ h ] === undefined ) { + + _bonds.push( [ satom - 1, eatom - 1, 1 ] ); + _bhash[ h ] = _bonds.length - 1; + + } else { + + // doesn't really work as almost all PDBs + // have just normal bonds appearing multiple + // times instead of being double/triple bonds + // bonds[bhash[h]][2] += 1; + + } + + } + + } + + function buildGeometry() { + + const build = { + geometryAtoms: new BufferGeometry(), + geometryBonds: new BufferGeometry(), + json: { + atoms: atoms + } + }; + + const geometryAtoms = build.geometryAtoms; + const geometryBonds = build.geometryBonds; + + const verticesAtoms = []; + const colorsAtoms = []; + const verticesBonds = []; + + // atoms + + for ( let i = 0, l = atoms.length; i < l; i ++ ) { + + const atom = atoms[ i ]; + + const x = atom[ 0 ]; + const y = atom[ 1 ]; + const z = atom[ 2 ]; + + verticesAtoms.push( x, y, z ); + + const r = atom[ 3 ][ 0 ] / 255; + const g = atom[ 3 ][ 1 ] / 255; + const b = atom[ 3 ][ 2 ] / 255; + + colorsAtoms.push( r, g, b ); + + } + + // bonds + + for ( let i = 0, l = _bonds.length; i < l; i ++ ) { + + const bond = _bonds[ i ]; + + const start = bond[ 0 ]; + const end = bond[ 1 ]; + + const startAtom = _atomMap[ start ]; + const endAtom = _atomMap[ end ]; + + let x = startAtom[ 0 ]; + let y = startAtom[ 1 ]; + let z = startAtom[ 2 ]; + + verticesBonds.push( x, y, z ); + + x = endAtom[ 0 ]; + y = endAtom[ 1 ]; + z = endAtom[ 2 ]; + + verticesBonds.push( x, y, z ); + + } + + // build geometry + + geometryAtoms.setAttribute( 'position', new Float32BufferAttribute( verticesAtoms, 3 ) ); + geometryAtoms.setAttribute( 'color', new Float32BufferAttribute( colorsAtoms, 3 ) ); + + geometryBonds.setAttribute( 'position', new Float32BufferAttribute( verticesBonds, 3 ) ); + + return build; + + } + + const CPK = { h: [ 255, 255, 255 ], he: [ 217, 255, 255 ], li: [ 204, 128, 255 ], be: [ 194, 255, 0 ], b: [ 255, 181, 181 ], c: [ 144, 144, 144 ], n: [ 48, 80, 248 ], o: [ 255, 13, 13 ], f: [ 144, 224, 80 ], ne: [ 179, 227, 245 ], na: [ 171, 92, 242 ], mg: [ 138, 255, 0 ], al: [ 191, 166, 166 ], si: [ 240, 200, 160 ], p: [ 255, 128, 0 ], s: [ 255, 255, 48 ], cl: [ 31, 240, 31 ], ar: [ 128, 209, 227 ], k: [ 143, 64, 212 ], ca: [ 61, 255, 0 ], sc: [ 230, 230, 230 ], ti: [ 191, 194, 199 ], v: [ 166, 166, 171 ], cr: [ 138, 153, 199 ], mn: [ 156, 122, 199 ], fe: [ 224, 102, 51 ], co: [ 240, 144, 160 ], ni: [ 80, 208, 80 ], cu: [ 200, 128, 51 ], zn: [ 125, 128, 176 ], ga: [ 194, 143, 143 ], ge: [ 102, 143, 143 ], as: [ 189, 128, 227 ], se: [ 255, 161, 0 ], br: [ 166, 41, 41 ], kr: [ 92, 184, 209 ], rb: [ 112, 46, 176 ], sr: [ 0, 255, 0 ], y: [ 148, 255, 255 ], zr: [ 148, 224, 224 ], nb: [ 115, 194, 201 ], mo: [ 84, 181, 181 ], tc: [ 59, 158, 158 ], ru: [ 36, 143, 143 ], rh: [ 10, 125, 140 ], pd: [ 0, 105, 133 ], ag: [ 192, 192, 192 ], cd: [ 255, 217, 143 ], in: [ 166, 117, 115 ], sn: [ 102, 128, 128 ], sb: [ 158, 99, 181 ], te: [ 212, 122, 0 ], i: [ 148, 0, 148 ], xe: [ 66, 158, 176 ], cs: [ 87, 23, 143 ], ba: [ 0, 201, 0 ], la: [ 112, 212, 255 ], ce: [ 255, 255, 199 ], pr: [ 217, 255, 199 ], nd: [ 199, 255, 199 ], pm: [ 163, 255, 199 ], sm: [ 143, 255, 199 ], eu: [ 97, 255, 199 ], gd: [ 69, 255, 199 ], tb: [ 48, 255, 199 ], dy: [ 31, 255, 199 ], ho: [ 0, 255, 156 ], er: [ 0, 230, 117 ], tm: [ 0, 212, 82 ], yb: [ 0, 191, 56 ], lu: [ 0, 171, 36 ], hf: [ 77, 194, 255 ], ta: [ 77, 166, 255 ], w: [ 33, 148, 214 ], re: [ 38, 125, 171 ], os: [ 38, 102, 150 ], ir: [ 23, 84, 135 ], pt: [ 208, 208, 224 ], au: [ 255, 209, 35 ], hg: [ 184, 184, 208 ], tl: [ 166, 84, 77 ], pb: [ 87, 89, 97 ], bi: [ 158, 79, 181 ], po: [ 171, 92, 0 ], at: [ 117, 79, 69 ], rn: [ 66, 130, 150 ], fr: [ 66, 0, 102 ], ra: [ 0, 125, 0 ], ac: [ 112, 171, 250 ], th: [ 0, 186, 255 ], pa: [ 0, 161, 255 ], u: [ 0, 143, 255 ], np: [ 0, 128, 255 ], pu: [ 0, 107, 255 ], am: [ 84, 92, 242 ], cm: [ 120, 92, 227 ], bk: [ 138, 79, 227 ], cf: [ 161, 54, 212 ], es: [ 179, 31, 212 ], fm: [ 179, 31, 186 ], md: [ 179, 13, 166 ], no: [ 189, 13, 135 ], lr: [ 199, 0, 102 ], rf: [ 204, 0, 89 ], db: [ 209, 0, 79 ], sg: [ 217, 0, 69 ], bh: [ 224, 0, 56 ], hs: [ 230, 0, 46 ], mt: [ 235, 0, 38 ], ds: [ 235, 0, 38 ], rg: [ 235, 0, 38 ], cn: [ 235, 0, 38 ], uut: [ 235, 0, 38 ], uuq: [ 235, 0, 38 ], uup: [ 235, 0, 38 ], uuh: [ 235, 0, 38 ], uus: [ 235, 0, 38 ], uuo: [ 235, 0, 38 ] }; + + const atoms = []; + + const _bonds = []; + const _bhash = {}; + const _atomMap = {}; + + // parse + + const lines = text.split( '\n' ); + + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + if ( lines[ i ].slice( 0, 4 ) === 'ATOM' || lines[ i ].slice( 0, 6 ) === 'HETATM' ) { + + const x = parseFloat( lines[ i ].slice( 30, 37 ) ); + const y = parseFloat( lines[ i ].slice( 38, 45 ) ); + const z = parseFloat( lines[ i ].slice( 46, 53 ) ); + const index = parseInt( lines[ i ].slice( 6, 11 ) ) - 1; + + let e = trim( lines[ i ].slice( 76, 78 ) ).toLowerCase(); + + if ( e === '' ) { + + e = trim( lines[ i ].slice( 12, 14 ) ).toLowerCase(); + + } + + const atomData = [ x, y, z, CPK[ e ], capitalize( e ) ]; + + atoms.push( atomData ); + _atomMap[ index ] = atomData; + + } else if ( lines[ i ].slice( 0, 6 ) === 'CONECT' ) { + + const satom = parseInt( lines[ i ].slice( 6, 11 ) ); + + parseBond( 11, 5, satom, i ); + parseBond( 16, 5, satom, i ); + parseBond( 21, 5, satom, i ); + parseBond( 26, 5, satom, i ); + + } + + } + + // build and return geometry + + return buildGeometry(); + + } + +} + +export { PDBLoader }; diff --git a/jsm/loaders/PLYLoader.js b/jsm/loaders/PLYLoader.js new file mode 100644 index 0000000..d9d1c71 --- /dev/null +++ b/jsm/loaders/PLYLoader.js @@ -0,0 +1,564 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + LoaderUtils, + Color +} from 'three'; + +/** + * Description: A THREE loader for PLY ASCII files (known as the Polygon + * File Format or the Stanford Triangle Format). + * + * Limitations: ASCII decoding assumes file is UTF-8. + * + * Usage: + * const loader = new PLYLoader(); + * loader.load('./models/ply/ascii/dolphins.ply', function (geometry) { + * + * scene.add( new THREE.Mesh( geometry ) ); + * + * } ); + * + * If the PLY file uses non standard property names, they can be mapped while + * loading. For example, the following maps the properties + * “diffuse_(red|green|blue)” in the file to standard color names. + * + * loader.setPropertyNameMapping( { + * diffuse_red: 'red', + * diffuse_green: 'green', + * diffuse_blue: 'blue' + * } ); + * + */ + +const _color = new Color(); + +class PLYLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.propertyNameMapping = {}; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + setPropertyNameMapping( mapping ) { + + this.propertyNameMapping = mapping; + + } + + parse( data ) { + + function parseHeader( data ) { + + const patternHeader = /^ply([\s\S]*)end_header\r?\n/; + let headerText = ''; + let headerLength = 0; + const result = patternHeader.exec( data ); + + if ( result !== null ) { + + headerText = result[ 1 ]; + headerLength = new Blob( [ result[ 0 ] ] ).size; + + } + + const header = { + comments: [], + elements: [], + headerLength: headerLength, + objInfo: '' + }; + + const lines = headerText.split( '\n' ); + let currentElement; + + function make_ply_element_property( propertValues, propertyNameMapping ) { + + const property = { type: propertValues[ 0 ] }; + + if ( property.type === 'list' ) { + + property.name = propertValues[ 3 ]; + property.countType = propertValues[ 1 ]; + property.itemType = propertValues[ 2 ]; + + } else { + + property.name = propertValues[ 1 ]; + + } + + if ( property.name in propertyNameMapping ) { + + property.name = propertyNameMapping[ property.name ]; + + } + + return property; + + } + + for ( let i = 0; i < lines.length; i ++ ) { + + let line = lines[ i ]; + line = line.trim(); + + if ( line === '' ) continue; + + const lineValues = line.split( /\s+/ ); + const lineType = lineValues.shift(); + line = lineValues.join( ' ' ); + + switch ( lineType ) { + + case 'format': + + header.format = lineValues[ 0 ]; + header.version = lineValues[ 1 ]; + + break; + + case 'comment': + + header.comments.push( line ); + + break; + + case 'element': + + if ( currentElement !== undefined ) { + + header.elements.push( currentElement ); + + } + + currentElement = {}; + currentElement.name = lineValues[ 0 ]; + currentElement.count = parseInt( lineValues[ 1 ] ); + currentElement.properties = []; + + break; + + case 'property': + + currentElement.properties.push( make_ply_element_property( lineValues, scope.propertyNameMapping ) ); + + break; + + case 'obj_info': + + header.objInfo = line; + + break; + + + default: + + console.log( 'unhandled', lineType, lineValues ); + + } + + } + + if ( currentElement !== undefined ) { + + header.elements.push( currentElement ); + + } + + return header; + + } + + function parseASCIINumber( n, type ) { + + switch ( type ) { + + case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint': + case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32': + + return parseInt( n ); + + case 'float': case 'double': case 'float32': case 'float64': + + return parseFloat( n ); + + } + + } + + function parseASCIIElement( properties, line ) { + + const values = line.split( /\s+/ ); + + const element = {}; + + for ( let i = 0; i < properties.length; i ++ ) { + + if ( properties[ i ].type === 'list' ) { + + const list = []; + const n = parseASCIINumber( values.shift(), properties[ i ].countType ); + + for ( let j = 0; j < n; j ++ ) { + + list.push( parseASCIINumber( values.shift(), properties[ i ].itemType ) ); + + } + + element[ properties[ i ].name ] = list; + + } else { + + element[ properties[ i ].name ] = parseASCIINumber( values.shift(), properties[ i ].type ); + + } + + } + + return element; + + } + + function parseASCII( data, header ) { + + // PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format) + + const buffer = { + indices: [], + vertices: [], + normals: [], + uvs: [], + faceVertexUvs: [], + colors: [] + }; + + let result; + + const patternBody = /end_header\s([\s\S]*)$/; + let body = ''; + if ( ( result = patternBody.exec( data ) ) !== null ) { + + body = result[ 1 ]; + + } + + const lines = body.split( '\n' ); + let currentElement = 0; + let currentElementCount = 0; + + for ( let i = 0; i < lines.length; i ++ ) { + + let line = lines[ i ]; + line = line.trim(); + if ( line === '' ) { + + continue; + + } + + if ( currentElementCount >= header.elements[ currentElement ].count ) { + + currentElement ++; + currentElementCount = 0; + + } + + const element = parseASCIIElement( header.elements[ currentElement ].properties, line ); + + handleElement( buffer, header.elements[ currentElement ].name, element ); + + currentElementCount ++; + + } + + return postProcess( buffer ); + + } + + function postProcess( buffer ) { + + let geometry = new BufferGeometry(); + + // mandatory buffer data + + if ( buffer.indices.length > 0 ) { + + geometry.setIndex( buffer.indices ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( buffer.vertices, 3 ) ); + + // optional buffer data + + if ( buffer.normals.length > 0 ) { + + geometry.setAttribute( 'normal', new Float32BufferAttribute( buffer.normals, 3 ) ); + + } + + if ( buffer.uvs.length > 0 ) { + + geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.uvs, 2 ) ); + + } + + if ( buffer.colors.length > 0 ) { + + geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.colors, 3 ) ); + + } + + if ( buffer.faceVertexUvs.length > 0 ) { + + geometry = geometry.toNonIndexed(); + geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.faceVertexUvs, 2 ) ); + + } + + geometry.computeBoundingSphere(); + + return geometry; + + } + + function handleElement( buffer, elementName, element ) { + + function findAttrName( names ) { + + for ( let i = 0, l = names.length; i < l; i ++ ) { + + const name = names[ i ]; + + if ( name in element ) return name; + + } + + return null; + + } + + const attrX = findAttrName( [ 'x', 'px', 'posx' ] ) || 'x'; + const attrY = findAttrName( [ 'y', 'py', 'posy' ] ) || 'y'; + const attrZ = findAttrName( [ 'z', 'pz', 'posz' ] ) || 'z'; + const attrNX = findAttrName( [ 'nx', 'normalx' ] ); + const attrNY = findAttrName( [ 'ny', 'normaly' ] ); + const attrNZ = findAttrName( [ 'nz', 'normalz' ] ); + const attrS = findAttrName( [ 's', 'u', 'texture_u', 'tx' ] ); + const attrT = findAttrName( [ 't', 'v', 'texture_v', 'ty' ] ); + const attrR = findAttrName( [ 'red', 'diffuse_red', 'r', 'diffuse_r' ] ); + const attrG = findAttrName( [ 'green', 'diffuse_green', 'g', 'diffuse_g' ] ); + const attrB = findAttrName( [ 'blue', 'diffuse_blue', 'b', 'diffuse_b' ] ); + + if ( elementName === 'vertex' ) { + + buffer.vertices.push( element[ attrX ], element[ attrY ], element[ attrZ ] ); + + if ( attrNX !== null && attrNY !== null && attrNZ !== null ) { + + buffer.normals.push( element[ attrNX ], element[ attrNY ], element[ attrNZ ] ); + + } + + if ( attrS !== null && attrT !== null ) { + + buffer.uvs.push( element[ attrS ], element[ attrT ] ); + + } + + if ( attrR !== null && attrG !== null && attrB !== null ) { + + _color.setRGB( + element[ attrR ] / 255.0, + element[ attrG ] / 255.0, + element[ attrB ] / 255.0 + ).convertSRGBToLinear(); + + buffer.colors.push( _color.r, _color.g, _color.b ); + + } + + } else if ( elementName === 'face' ) { + + const vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338 + const texcoord = element.texcoord; + + if ( vertex_indices.length === 3 ) { + + buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] ); + + if ( texcoord && texcoord.length === 6 ) { + + buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] ); + buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] ); + buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] ); + + } + + } else if ( vertex_indices.length === 4 ) { + + buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] ); + buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] ); + + } + + } + + } + + function binaryRead( dataview, at, type, little_endian ) { + + switch ( type ) { + + // corespondences for non-specific length types here match rply: + case 'int8': case 'char': return [ dataview.getInt8( at ), 1 ]; + case 'uint8': case 'uchar': return [ dataview.getUint8( at ), 1 ]; + case 'int16': case 'short': return [ dataview.getInt16( at, little_endian ), 2 ]; + case 'uint16': case 'ushort': return [ dataview.getUint16( at, little_endian ), 2 ]; + case 'int32': case 'int': return [ dataview.getInt32( at, little_endian ), 4 ]; + case 'uint32': case 'uint': return [ dataview.getUint32( at, little_endian ), 4 ]; + case 'float32': case 'float': return [ dataview.getFloat32( at, little_endian ), 4 ]; + case 'float64': case 'double': return [ dataview.getFloat64( at, little_endian ), 8 ]; + + } + + } + + function binaryReadElement( dataview, at, properties, little_endian ) { + + const element = {}; + let result, read = 0; + + for ( let i = 0; i < properties.length; i ++ ) { + + if ( properties[ i ].type === 'list' ) { + + const list = []; + + result = binaryRead( dataview, at + read, properties[ i ].countType, little_endian ); + const n = result[ 0 ]; + read += result[ 1 ]; + + for ( let j = 0; j < n; j ++ ) { + + result = binaryRead( dataview, at + read, properties[ i ].itemType, little_endian ); + list.push( result[ 0 ] ); + read += result[ 1 ]; + + } + + element[ properties[ i ].name ] = list; + + } else { + + result = binaryRead( dataview, at + read, properties[ i ].type, little_endian ); + element[ properties[ i ].name ] = result[ 0 ]; + read += result[ 1 ]; + + } + + } + + return [ element, read ]; + + } + + function parseBinary( data, header ) { + + const buffer = { + indices: [], + vertices: [], + normals: [], + uvs: [], + faceVertexUvs: [], + colors: [] + }; + + const little_endian = ( header.format === 'binary_little_endian' ); + const body = new DataView( data, header.headerLength ); + let result, loc = 0; + + for ( let currentElement = 0; currentElement < header.elements.length; currentElement ++ ) { + + for ( let currentElementCount = 0; currentElementCount < header.elements[ currentElement ].count; currentElementCount ++ ) { + + result = binaryReadElement( body, loc, header.elements[ currentElement ].properties, little_endian ); + loc += result[ 1 ]; + const element = result[ 0 ]; + + handleElement( buffer, header.elements[ currentElement ].name, element ); + + } + + } + + return postProcess( buffer ); + + } + + // + + let geometry; + const scope = this; + + if ( data instanceof ArrayBuffer ) { + + const text = LoaderUtils.decodeText( new Uint8Array( data ) ); + const header = parseHeader( text ); + + geometry = header.format === 'ascii' ? parseASCII( text, header ) : parseBinary( data, header ); + + } else { + + geometry = parseASCII( data, parseHeader( data ) ); + + } + + return geometry; + + } + +} + +export { PLYLoader }; diff --git a/jsm/loaders/PRWMLoader.js b/jsm/loaders/PRWMLoader.js new file mode 100644 index 0000000..1eb2268 --- /dev/null +++ b/jsm/loaders/PRWMLoader.js @@ -0,0 +1,299 @@ +import { + BufferAttribute, + BufferGeometry, + FileLoader, + Loader +} from 'three'; + +/** + * See https://github.com/kchapelier/PRWM for more informations about this file format + */ + +let bigEndianPlatform = null; + +/** + * Check if the endianness of the platform is big-endian (most significant bit first) + * @returns {boolean} True if big-endian, false if little-endian + */ +function isBigEndianPlatform() { + + if ( bigEndianPlatform === null ) { + + const buffer = new ArrayBuffer( 2 ), + uint8Array = new Uint8Array( buffer ), + uint16Array = new Uint16Array( buffer ); + + uint8Array[ 0 ] = 0xAA; // set first byte + uint8Array[ 1 ] = 0xBB; // set second byte + bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB ); + + } + + return bigEndianPlatform; + +} + +// match the values defined in the spec to the TypedArray types +const InvertedEncodingTypes = [ + null, + Float32Array, + null, + Int8Array, + Int16Array, + null, + Int32Array, + Uint8Array, + Uint16Array, + null, + Uint32Array +]; + +// define the method to use on a DataView, corresponding the TypedArray type +const getMethods = { + Uint16Array: 'getUint16', + Uint32Array: 'getUint32', + Int16Array: 'getInt16', + Int32Array: 'getInt32', + Float32Array: 'getFloat32', + Float64Array: 'getFloat64' +}; + + +function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) { + + const bytesPerElement = viewType.BYTES_PER_ELEMENT; + let result; + + if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) { + + result = new viewType( sourceArrayBuffer, position, length ); + + } else { + + const readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ), + getMethod = getMethods[ viewType.name ], + littleEndian = ! fromBigEndian; + + result = new viewType( length ); + + for ( let i = 0; i < length; i ++ ) { + + result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian ); + + } + + } + + return result; + +} + + +function decodePrwm( buffer ) { + + const array = new Uint8Array( buffer ), + version = array[ 0 ]; + + let flags = array[ 1 ]; + + const indexedGeometry = !! ( flags >> 7 & 0x01 ), + indicesType = flags >> 6 & 0x01, + bigEndian = ( flags >> 5 & 0x01 ) === 1, + attributesNumber = flags & 0x1F; + + let valuesNumber = 0, + indicesNumber = 0; + + if ( bigEndian ) { + + valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ]; + indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ]; + + } else { + + valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 ); + indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 ); + + } + + /** PRELIMINARY CHECKS **/ + + if ( version === 0 ) { + + throw new Error( 'PRWM decoder: Invalid format version: 0' ); + + } else if ( version !== 1 ) { + + throw new Error( 'PRWM decoder: Unsupported format version: ' + version ); + + } + + if ( ! indexedGeometry ) { + + if ( indicesType !== 0 ) { + + throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' ); + + } else if ( indicesNumber !== 0 ) { + + throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' ); + + } + + } + + /** PARSING **/ + + let pos = 8; + + const attributes = {}; + + for ( let i = 0; i < attributesNumber; i ++ ) { + + let attributeName = ''; + + while ( pos < array.length ) { + + const char = array[ pos ]; + pos ++; + + if ( char === 0 ) { + + break; + + } else { + + attributeName += String.fromCharCode( char ); + + } + + } + + flags = array[ pos ]; + + const attributeType = flags >> 7 & 0x01; + const cardinality = ( flags >> 4 & 0x03 ) + 1; + const encodingType = flags & 0x0F; + const arrayType = InvertedEncodingTypes[ encodingType ]; + + pos ++; + + // padding to next multiple of 4 + pos = Math.ceil( pos / 4 ) * 4; + + const values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian ); + + pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber; + + attributes[ attributeName ] = { + type: attributeType, + cardinality: cardinality, + values: values + }; + + } + + pos = Math.ceil( pos / 4 ) * 4; + + let indices = null; + + if ( indexedGeometry ) { + + indices = copyFromBuffer( + buffer, + indicesType === 1 ? Uint32Array : Uint16Array, + pos, + indicesNumber, + bigEndian + ); + + } + + return { + version: version, + attributes: attributes, + indices: indices + }; + +} + +// Define the public interface + +class PRWMLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + + url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' ); + + loader.load( url, function ( arrayBuffer ) { + + try { + + onLoad( scope.parse( arrayBuffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( arrayBuffer ) { + + const data = decodePrwm( arrayBuffer ), + attributesKey = Object.keys( data.attributes ), + bufferGeometry = new BufferGeometry(); + + for ( let i = 0; i < attributesKey.length; i ++ ) { + + const attribute = data.attributes[ attributesKey[ i ] ]; + bufferGeometry.setAttribute( attributesKey[ i ], new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) ); + + } + + if ( data.indices !== null ) { + + bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) ); + + } + + return bufferGeometry; + + } + + static isBigEndianPlatform() { + + return isBigEndianPlatform(); + + } + +} + +export { PRWMLoader }; diff --git a/jsm/loaders/PVRLoader.js b/jsm/loaders/PVRLoader.js new file mode 100644 index 0000000..f39f8e7 --- /dev/null +++ b/jsm/loaders/PVRLoader.js @@ -0,0 +1,251 @@ +import { + CompressedTextureLoader, + RGBA_PVRTC_2BPPV1_Format, + RGBA_PVRTC_4BPPV1_Format, + RGB_PVRTC_2BPPV1_Format, + RGB_PVRTC_4BPPV1_Format +} from 'three'; + +/* + * PVR v2 (legacy) parser + * TODO : Add Support for PVR v3 format + * TODO : implement loadMipmaps option + */ + +class PVRLoader extends CompressedTextureLoader { + + constructor( manager ) { + + super( manager ); + + } + + parse( buffer, loadMipmaps ) { + + const headerLengthInt = 13; + const header = new Uint32Array( buffer, 0, headerLengthInt ); + + const pvrDatas = { + buffer: buffer, + header: header, + loadMipmaps: loadMipmaps + }; + + if ( header[ 0 ] === 0x03525650 ) { + + // PVR v3 + + return _parseV3( pvrDatas ); + + } else if ( header[ 11 ] === 0x21525650 ) { + + // PVR v2 + + return _parseV2( pvrDatas ); + + } else { + + console.error( 'THREE.PVRLoader: Unknown PVR format.' ); + + } + + } + +} + +function _parseV3( pvrDatas ) { + + const header = pvrDatas.header; + let bpp, format; + + + const metaLen = header[ 12 ], + pixelFormat = header[ 2 ], + height = header[ 6 ], + width = header[ 7 ], + // numSurfs = header[ 9 ], + numFaces = header[ 10 ], + numMipmaps = header[ 11 ]; + + switch ( pixelFormat ) { + + case 0 : // PVRTC 2bpp RGB + bpp = 2; + format = RGB_PVRTC_2BPPV1_Format; + break; + + case 1 : // PVRTC 2bpp RGBA + bpp = 2; + format = RGBA_PVRTC_2BPPV1_Format; + break; + + case 2 : // PVRTC 4bpp RGB + bpp = 4; + format = RGB_PVRTC_4BPPV1_Format; + break; + + case 3 : // PVRTC 4bpp RGBA + bpp = 4; + format = RGBA_PVRTC_4BPPV1_Format; + break; + + default : + console.error( 'THREE.PVRLoader: Unsupported PVR format:', pixelFormat ); + + } + + pvrDatas.dataPtr = 52 + metaLen; + pvrDatas.bpp = bpp; + pvrDatas.format = format; + pvrDatas.width = width; + pvrDatas.height = height; + pvrDatas.numSurfaces = numFaces; + pvrDatas.numMipmaps = numMipmaps; + pvrDatas.isCubemap = ( numFaces === 6 ); + + return _extract( pvrDatas ); + +} + +function _parseV2( pvrDatas ) { + + const header = pvrDatas.header; + + const headerLength = header[ 0 ], + height = header[ 1 ], + width = header[ 2 ], + numMipmaps = header[ 3 ], + flags = header[ 4 ], + // dataLength = header[ 5 ], + // bpp = header[ 6 ], + // bitmaskRed = header[ 7 ], + // bitmaskGreen = header[ 8 ], + // bitmaskBlue = header[ 9 ], + bitmaskAlpha = header[ 10 ], + // pvrTag = header[ 11 ], + numSurfs = header[ 12 ]; + + + const TYPE_MASK = 0xff; + const PVRTC_2 = 24, + PVRTC_4 = 25; + + const formatFlags = flags & TYPE_MASK; + + let bpp, format; + const _hasAlpha = bitmaskAlpha > 0; + + if ( formatFlags === PVRTC_4 ) { + + format = _hasAlpha ? RGBA_PVRTC_4BPPV1_Format : RGB_PVRTC_4BPPV1_Format; + bpp = 4; + + } else if ( formatFlags === PVRTC_2 ) { + + format = _hasAlpha ? RGBA_PVRTC_2BPPV1_Format : RGB_PVRTC_2BPPV1_Format; + bpp = 2; + + } else { + + console.error( 'THREE.PVRLoader: Unknown PVR format:', formatFlags ); + + } + + pvrDatas.dataPtr = headerLength; + pvrDatas.bpp = bpp; + pvrDatas.format = format; + pvrDatas.width = width; + pvrDatas.height = height; + pvrDatas.numSurfaces = numSurfs; + pvrDatas.numMipmaps = numMipmaps + 1; + + // guess cubemap type seems tricky in v2 + // it juste a pvr containing 6 surface (no explicit cubemap type) + pvrDatas.isCubemap = ( numSurfs === 6 ); + + return _extract( pvrDatas ); + +} + + +function _extract( pvrDatas ) { + + const pvr = { + mipmaps: [], + width: pvrDatas.width, + height: pvrDatas.height, + format: pvrDatas.format, + mipmapCount: pvrDatas.numMipmaps, + isCubemap: pvrDatas.isCubemap + }; + + const buffer = pvrDatas.buffer; + + let dataOffset = pvrDatas.dataPtr, + dataSize = 0, + blockSize = 0, + blockWidth = 0, + blockHeight = 0, + widthBlocks = 0, + heightBlocks = 0; + + const bpp = pvrDatas.bpp, + numSurfs = pvrDatas.numSurfaces; + + if ( bpp === 2 ) { + + blockWidth = 8; + blockHeight = 4; + + } else { + + blockWidth = 4; + blockHeight = 4; + + } + + blockSize = ( blockWidth * blockHeight ) * bpp / 8; + + pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs; + + let mipLevel = 0; + + while ( mipLevel < pvrDatas.numMipmaps ) { + + const sWidth = pvrDatas.width >> mipLevel, + sHeight = pvrDatas.height >> mipLevel; + + widthBlocks = sWidth / blockWidth; + heightBlocks = sHeight / blockHeight; + + // Clamp to minimum number of blocks + if ( widthBlocks < 2 ) widthBlocks = 2; + if ( heightBlocks < 2 ) heightBlocks = 2; + + dataSize = widthBlocks * heightBlocks * blockSize; + + for ( let surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) { + + const byteArray = new Uint8Array( buffer, dataOffset, dataSize ); + + const mipmap = { + data: byteArray, + width: sWidth, + height: sHeight + }; + + pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap; + + dataOffset += dataSize; + + } + + mipLevel ++; + + } + + return pvr; + +} + +export { PVRLoader }; diff --git a/jsm/loaders/RGBELoader.js b/jsm/loaders/RGBELoader.js new file mode 100644 index 0000000..b6a8559 --- /dev/null +++ b/jsm/loaders/RGBELoader.js @@ -0,0 +1,476 @@ +import { + DataTextureLoader, + DataUtils, + FloatType, + HalfFloatType, + LinearEncoding, + LinearFilter +} from 'three'; + +// https://github.com/mrdoob/three.js/issues/5552 +// http://en.wikipedia.org/wiki/RGBE_image_format + +class RGBELoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + + } + + // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html + + parse( buffer ) { + + const + /* return codes for rgbe routines */ + //RGBE_RETURN_SUCCESS = 0, + RGBE_RETURN_FAILURE = - 1, + + /* default error routine. change this to change error handling */ + rgbe_read_error = 1, + rgbe_write_error = 2, + rgbe_format_error = 3, + rgbe_memory_error = 4, + rgbe_error = function ( rgbe_error_code, msg ) { + + switch ( rgbe_error_code ) { + + case rgbe_read_error: console.error( 'THREE.RGBELoader Read Error: ' + ( msg || '' ) ); + break; + case rgbe_write_error: console.error( 'THREE.RGBELoader Write Error: ' + ( msg || '' ) ); + break; + case rgbe_format_error: console.error( 'THREE.RGBELoader Bad File Format: ' + ( msg || '' ) ); + break; + default: + case rgbe_memory_error: console.error( 'THREE.RGBELoader: Error: ' + ( msg || '' ) ); + + } + + return RGBE_RETURN_FAILURE; + + }, + + /* offsets to red, green, and blue components in a data (float) pixel */ + //RGBE_DATA_RED = 0, + //RGBE_DATA_GREEN = 1, + //RGBE_DATA_BLUE = 2, + + /* number of floats per pixel, use 4 since stored in rgba image format */ + //RGBE_DATA_SIZE = 4, + + /* flags indicating which fields in an rgbe_header_info are valid */ + RGBE_VALID_PROGRAMTYPE = 1, + RGBE_VALID_FORMAT = 2, + RGBE_VALID_DIMENSIONS = 4, + + NEWLINE = '\n', + + fgets = function ( buffer, lineLimit, consume ) { + + const chunkSize = 128; + + lineLimit = ! lineLimit ? 1024 : lineLimit; + let p = buffer.pos, + i = - 1, len = 0, s = '', + chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ); + + while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) { + + s += chunk; len += chunk.length; + p += chunkSize; + chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ); + + } + + if ( - 1 < i ) { + + /*for (i=l-1; i>=0; i--) { + byteCode = m.charCodeAt(i); + if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++; + else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2; + if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate + }*/ + if ( false !== consume ) buffer.pos += len + i + 1; + return s + chunk.slice( 0, i ); + + } + + return false; + + }, + + /* minimal header reading. modify if you want to parse more information */ + RGBE_ReadHeader = function ( buffer ) { + + + // regexes to parse header info fields + const magic_token_re = /^#\?(\S+)/, + gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/, + exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/, + format_re = /^\s*FORMAT=(\S+)\s*$/, + dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/, + + // RGBE format header struct + header = { + + valid: 0, /* indicate which fields are valid */ + + string: '', /* the actual header string */ + + comments: '', /* comments found in header */ + + programtype: 'RGBE', /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */ + + format: '', /* RGBE format, default 32-bit_rle_rgbe */ + + gamma: 1.0, /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */ + + exposure: 1.0, /* a value of 1.0 in an image corresponds to watts/steradian/m^2. defaults to 1.0 */ + + width: 0, height: 0 /* image dimensions, width/height */ + + }; + + let line, match; + + if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) { + + return rgbe_error( rgbe_read_error, 'no header found' ); + + } + + /* if you want to require the magic token then uncomment the next line */ + if ( ! ( match = line.match( magic_token_re ) ) ) { + + return rgbe_error( rgbe_format_error, 'bad initial token' ); + + } + + header.valid |= RGBE_VALID_PROGRAMTYPE; + header.programtype = match[ 1 ]; + header.string += line + '\n'; + + while ( true ) { + + line = fgets( buffer ); + if ( false === line ) break; + header.string += line + '\n'; + + if ( '#' === line.charAt( 0 ) ) { + + header.comments += line + '\n'; + continue; // comment line + + } + + if ( match = line.match( gamma_re ) ) { + + header.gamma = parseFloat( match[ 1 ] ); + + } + + if ( match = line.match( exposure_re ) ) { + + header.exposure = parseFloat( match[ 1 ] ); + + } + + if ( match = line.match( format_re ) ) { + + header.valid |= RGBE_VALID_FORMAT; + header.format = match[ 1 ];//'32-bit_rle_rgbe'; + + } + + if ( match = line.match( dimensions_re ) ) { + + header.valid |= RGBE_VALID_DIMENSIONS; + header.height = parseInt( match[ 1 ], 10 ); + header.width = parseInt( match[ 2 ], 10 ); + + } + + if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break; + + } + + if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) { + + return rgbe_error( rgbe_format_error, 'missing format specifier' ); + + } + + if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) { + + return rgbe_error( rgbe_format_error, 'missing image size specifier' ); + + } + + return header; + + }, + + RGBE_ReadPixels_RLE = function ( buffer, w, h ) { + + const scanline_width = w; + + if ( + // run length encoding is not allowed so read flat + ( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) || + // this file is not run length encoded + ( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) ) + ) { + + // return the flat buffer + return new Uint8Array( buffer ); + + } + + if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) { + + return rgbe_error( rgbe_format_error, 'wrong scanline width' ); + + } + + const data_rgba = new Uint8Array( 4 * w * h ); + + if ( ! data_rgba.length ) { + + return rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' ); + + } + + let offset = 0, pos = 0; + + const ptr_end = 4 * scanline_width; + const rgbeStart = new Uint8Array( 4 ); + const scanline_buffer = new Uint8Array( ptr_end ); + let num_scanlines = h; + + // read in each successive scanline + while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) { + + if ( pos + 4 > buffer.byteLength ) { + + return rgbe_error( rgbe_read_error ); + + } + + rgbeStart[ 0 ] = buffer[ pos ++ ]; + rgbeStart[ 1 ] = buffer[ pos ++ ]; + rgbeStart[ 2 ] = buffer[ pos ++ ]; + rgbeStart[ 3 ] = buffer[ pos ++ ]; + + if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) { + + return rgbe_error( rgbe_format_error, 'bad rgbe scanline format' ); + + } + + // read each of the four channels for the scanline into the buffer + // first red, then green, then blue, then exponent + let ptr = 0, count; + + while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) { + + count = buffer[ pos ++ ]; + const isEncodedRun = count > 128; + if ( isEncodedRun ) count -= 128; + + if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) { + + return rgbe_error( rgbe_format_error, 'bad scanline data' ); + + } + + if ( isEncodedRun ) { + + // a (encoded) run of the same value + const byteValue = buffer[ pos ++ ]; + for ( let i = 0; i < count; i ++ ) { + + scanline_buffer[ ptr ++ ] = byteValue; + + } + //ptr += count; + + } else { + + // a literal-run + scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr ); + ptr += count; pos += count; + + } + + } + + + // now convert data from buffer into rgba + // first red, then green, then blue, then exponent (alpha) + const l = scanline_width; //scanline_buffer.byteLength; + for ( let i = 0; i < l; i ++ ) { + + let off = 0; + data_rgba[ offset ] = scanline_buffer[ i + off ]; + off += scanline_width; //1; + data_rgba[ offset + 1 ] = scanline_buffer[ i + off ]; + off += scanline_width; //1; + data_rgba[ offset + 2 ] = scanline_buffer[ i + off ]; + off += scanline_width; //1; + data_rgba[ offset + 3 ] = scanline_buffer[ i + off ]; + offset += 4; + + } + + num_scanlines --; + + } + + return data_rgba; + + }; + + const RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) { + + const e = sourceArray[ sourceOffset + 3 ]; + const scale = Math.pow( 2.0, e - 128.0 ) / 255.0; + + destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale; + destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale; + destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale; + destArray[ destOffset + 3 ] = 1; + + }; + + const RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) { + + const e = sourceArray[ sourceOffset + 3 ]; + const scale = Math.pow( 2.0, e - 128.0 ) / 255.0; + + // clamping to 65504, the maximum representable value in float16 + destArray[ destOffset + 0 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 0 ] * scale, 65504 ) ); + destArray[ destOffset + 1 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 1 ] * scale, 65504 ) ); + destArray[ destOffset + 2 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 2 ] * scale, 65504 ) ); + destArray[ destOffset + 3 ] = DataUtils.toHalfFloat( 1 ); + + }; + + const byteArray = new Uint8Array( buffer ); + byteArray.pos = 0; + const rgbe_header_info = RGBE_ReadHeader( byteArray ); + + if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) { + + const w = rgbe_header_info.width, + h = rgbe_header_info.height, + image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h ); + + if ( RGBE_RETURN_FAILURE !== image_rgba_data ) { + + let data, format, type; + let numElements; + + switch ( this.type ) { + + case FloatType: + + numElements = image_rgba_data.length / 4; + const floatArray = new Float32Array( numElements * 4 ); + + for ( let j = 0; j < numElements; j ++ ) { + + RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 4 ); + + } + + data = floatArray; + type = FloatType; + break; + + case HalfFloatType: + + numElements = image_rgba_data.length / 4; + const halfArray = new Uint16Array( numElements * 4 ); + + for ( let j = 0; j < numElements; j ++ ) { + + RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 4 ); + + } + + data = halfArray; + type = HalfFloatType; + break; + + default: + + console.error( 'THREE.RGBELoader: unsupported type: ', this.type ); + break; + + } + + return { + width: w, height: h, + data: data, + header: rgbe_header_info.string, + gamma: rgbe_header_info.gamma, + exposure: rgbe_header_info.exposure, + format: format, + type: type + }; + + } + + } + + return null; + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + function onLoadCallback( texture, texData ) { + + switch ( texture.type ) { + + case FloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.flipY = true; + break; + + case HalfFloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.flipY = true; + break; + + } + + if ( onLoad ) onLoad( texture, texData ); + + } + + return super.load( url, onLoadCallback, onProgress, onError ); + + } + +} + +export { RGBELoader }; diff --git a/jsm/loaders/RGBMLoader.js b/jsm/loaders/RGBMLoader.js new file mode 100644 index 0000000..44420c4 --- /dev/null +++ b/jsm/loaders/RGBMLoader.js @@ -0,0 +1,1065 @@ +import { + DataTextureLoader, + RGBAFormat, + LinearFilter, + CubeTexture, + HalfFloatType, + DataUtils +} from 'three'; + +class RGBMLoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + this.maxRange = 7; // more information about this property at https://iwasbeingirony.blogspot.com/2010/06/difference-between-rgbm-and-rgbd.html + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + + setMaxRange( value ) { + + this.maxRange = value; + return this; + + } + + loadCubemap( urls, onLoad, onProgress, onError ) { + + const texture = new CubeTexture(); + + let loaded = 0; + + const scope = this; + + function loadTexture( i ) { + + scope.load( urls[ i ], function ( image ) { + + texture.images[ i ] = image; + + loaded ++; + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + }, undefined, onError ); + + } + + for ( let i = 0; i < urls.length; ++ i ) { + + loadTexture( i ); + + } + + texture.type = this.type; + texture.format = RGBAFormat; + texture.minFilter = LinearFilter; + texture.generateMipmaps = false; + + return texture; + + } + + parse( buffer ) { + + const img = UPNG.decode( buffer ); + const rgba = UPNG.toRGBA8( img )[ 0 ]; + + const data = new Uint8Array( rgba ); + const size = img.width * img.height * 4; + + const output = ( this.type === HalfFloatType ) ? new Uint16Array( size ) : new Float32Array( size ); + + // decode RGBM + + for ( let i = 0; i < data.length; i += 4 ) { + + const r = data[ i + 0 ] / 255; + const g = data[ i + 1 ] / 255; + const b = data[ i + 2 ] / 255; + const a = data[ i + 3 ] / 255; + + if ( this.type === HalfFloatType ) { + + output[ i + 0 ] = DataUtils.toHalfFloat( Math.min( r * a * this.maxRange, 65504 ) ); + output[ i + 1 ] = DataUtils.toHalfFloat( Math.min( g * a * this.maxRange, 65504 ) ); + output[ i + 2 ] = DataUtils.toHalfFloat( Math.min( b * a * this.maxRange, 65504 ) ); + output[ i + 3 ] = DataUtils.toHalfFloat( 1 ); + + } else { + + output[ i + 0 ] = r * a * this.maxRange; + output[ i + 1 ] = g * a * this.maxRange; + output[ i + 2 ] = b * a * this.maxRange; + output[ i + 3 ] = 1; + + } + + } + + return { + width: img.width, + height: img.height, + data: output, + format: RGBAFormat, + type: this.type, + flipY: true + }; + + } + +} + +// from https://github.com/photopea/UPNG.js (MIT License) + +var UPNG = {}; + +UPNG.toRGBA8 = function ( out ) { + + var w = out.width, h = out.height; + if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ]; + + var frms = []; + if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data; + + var len = w * h * 4, img = new Uint8Array( len ), empty = new Uint8Array( len ), prev = new Uint8Array( len ); + for ( var i = 0; i < out.frames.length; i ++ ) { + + var frm = out.frames[ i ]; + var fx = frm.rect.x, fy = frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; + var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out ); + + if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ]; + + if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 ); + + frms.push( img.buffer.slice( 0 ) ); + + if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); + else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ]; + + } + + return frms; + +}; + +UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { + + var area = w * h, bpp = UPNG.decode._getBPP( out ); + var bpl = Math.ceil( w * bpp / 8 ); // bytes per line + + var bf = new Uint8Array( area * 4 ), bf32 = new Uint32Array( bf.buffer ); + var ctype = out.ctype, depth = out.depth; + var rs = UPNG._bin.readUshort; + + if ( ctype == 6 ) { // RGB + alpha + + var qarea = area << 2; + if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) { + + bf[ i ] = data[ i ]; bf[ i + 1 ] = data[ i + 1 ]; bf[ i + 2 ] = data[ i + 2 ]; bf[ i + 3 ] = data[ i + 3 ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) { + + bf[ i ] = data[ i << 1 ]; + + } + + } else if ( ctype == 2 ) { // RGB + + var ts = out.tabs[ 'tRNS' ]; + if ( ts == null ) { + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + + } + + } else { + + var tr = ts[ 0 ], tg = ts[ 1 ], tb = ts[ 2 ]; + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, ti = i * 3; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 2 ] << 16 ) | ( data[ ti + 1 ] << 8 ) | data[ ti ]; + if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, ti = i * 6; bf32[ i ] = ( 255 << 24 ) | ( data[ ti + 4 ] << 16 ) | ( data[ ti + 2 ] << 8 ) | data[ ti ]; + if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0; + + } + + } + + } else if ( ctype == 3 ) { // palette + + var p = out.tabs[ 'PLTE' ], ap = out.tabs[ 'tRNS' ], tl = ap ? ap.length : 0; + //console.log(p, ap); + if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 3 ) ] >> ( 7 - ( ( i & 7 ) << 0 ) ) ) & 1 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 2 ) ] >> ( 6 - ( ( i & 3 ) << 1 ) ) ) & 3 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) { + + var s0 = y * bpl, t0 = y * w; + for ( var i = 0; i < w; i ++ ) { + + var qi = ( t0 + i ) << 2, j = ( ( data[ s0 + ( i >> 1 ) ] >> ( 4 - ( ( i & 1 ) << 2 ) ) ) & 15 ), cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, j = data[ i ], cj = 3 * j; bf[ qi ] = p[ cj ]; bf[ qi + 1 ] = p[ cj + 1 ]; bf[ qi + 2 ] = p[ cj + 2 ]; bf[ qi + 3 ] = ( j < tl ) ? ap[ j ] : 255; + + } + + } else if ( ctype == 4 ) { // gray + alpha + + if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, di = i << 1, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 1 ]; + + } + + if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) { + + var qi = i << 2, di = i << 2, gr = data[ di ]; bf[ qi ] = gr; bf[ qi + 1 ] = gr; bf[ qi + 2 ] = gr; bf[ qi + 3 ] = data[ di + 2 ]; + + } + + } else if ( ctype == 0 ) { // gray + + var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1; + for ( var y = 0; y < h; y ++ ) { + + var off = y * bpl, to = y * w; + if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 255 * ( ( data[ off + ( x >>> 3 ) ] >>> ( 7 - ( x & 7 ) ) ) & 1 ), al = ( gr == tr * 255 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 85 * ( ( data[ off + ( x >>> 2 ) ] >>> ( 6 - ( ( x & 3 ) << 1 ) ) ) & 3 ), al = ( gr == tr * 85 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) { + + var gr = 17 * ( ( data[ off + ( x >>> 1 ) ] >>> ( 4 - ( ( x & 1 ) << 2 ) ) ) & 15 ), al = ( gr == tr * 17 ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) { + + var gr = data[ off + x ], al = ( gr == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { + + var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + + } + + } + + } + + //console.log(Date.now()-time); + return bf; + +}; + + + +UPNG.decode = function ( buff ) { + + var data = new Uint8Array( buff ), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; + var out = { tabs: {}, frames: [] }; + var dd = new Uint8Array( data.length ), doff = 0; // put all IDAT data into it + var fd, foff = 0; // frames + var text, keyw, bfr; + + var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ]; + for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw new Error( 'The input is not a PNG file!' ); + + while ( offset < data.length ) { + + var len = bin.readUint( data, offset ); offset += 4; + var type = bin.readASCII( data, offset, 4 ); offset += 4; + //console.log(type,len); + + if ( type == 'IHDR' ) { + + UPNG.decode._IHDR( data, offset, out ); + + } else if ( type == 'CgBI' ) { + + out.tabs[ type ] = data.slice( offset, offset + 4 ); + + } else if ( type == 'IDAT' ) { + + for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ]; + doff += len; + + } else if ( type == 'acTL' ) { + + out.tabs[ type ] = { num_frames: rUi( data, offset ), num_plays: rUi( data, offset + 4 ) }; + fd = new Uint8Array( data.length ); + + } else if ( type == 'fcTL' ) { + + if ( foff != 0 ) { + + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; + + } + + var rct = { x: rUi( data, offset + 12 ), y: rUi( data, offset + 16 ), width: rUi( data, offset + 4 ), height: rUi( data, offset + 8 ) }; + var del = rUs( data, offset + 22 ); del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del ); + var frm = { rect: rct, delay: Math.round( del * 1000 ), dispose: data[ offset + 24 ], blend: data[ offset + 25 ] }; + //console.log(frm); + out.frames.push( frm ); + + } else if ( type == 'fdAT' ) { + + for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ]; + foff += len - 4; + + } else if ( type == 'pHYs' ) { + + out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ]; + + } else if ( type == 'cHRM' ) { + + out.tabs[ type ] = []; + for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) ); + + } else if ( type == 'tEXt' || type == 'zTXt' ) { + + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = bin.nextZero( data, offset ); + keyw = bin.readASCII( data, offset, nz - offset ); + var tl = offset + len - nz - 1; + if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); + else { + + bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); + + } + + out.tabs[ type ][ keyw ] = text; + + } else if ( type == 'iTXt' ) { + + if ( out.tabs[ type ] == null ) out.tabs[ type ] = {}; + var nz = 0, off = offset; + nz = bin.nextZero( data, off ); + keyw = bin.readASCII( data, off, nz - off ); off = nz + 1; + var cflag = data[ off ]; off += 2; + nz = bin.nextZero( data, off ); + bin.readASCII( data, off, nz - off ); off = nz + 1; + nz = bin.nextZero( data, off ); + bin.readUTF8( data, off, nz - off ); off = nz + 1; + var tl = len - ( off - offset ); + if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); + else { + + bfr = UPNG.decode._inflate( data.slice( off, off + tl ) ); + text = bin.readUTF8( bfr, 0, bfr.length ); + + } + + out.tabs[ type ][ keyw ] = text; + + } else if ( type == 'PLTE' ) { + + out.tabs[ type ] = bin.readBytes( data, offset, len ); + + } else if ( type == 'hIST' ) { + + var pl = out.tabs[ 'PLTE' ].length / 3; + out.tabs[ type ] = []; for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) ); + + } else if ( type == 'tRNS' ) { + + if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); + else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); + else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + //else console.log("tRNS for unsupported color type",out.ctype, len); + + } else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; + else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; + else if ( type == 'bKGD' ) { + + if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; + else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; + else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ]; + + } else if ( type == 'IEND' ) { + + break; + + } + + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } + offset += len; + bin.readUint( data, offset ); offset += 4; + + } + + if ( foff != 0 ) { + + var fr = out.frames[ out.frames.length - 1 ]; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); + + } + + out.data = UPNG.decode._decompress( out, dd, out.width, out.height ); + + delete out.compress; delete out.interlace; delete out.filter; + return out; + +}; + +UPNG.decode._decompress = function ( out, dd, w, h ) { + + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h ); + if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); + else dd = UPNG.decode._inflate( dd, buff ); + + if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); + else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out ); + + return dd; + +}; + +UPNG.decode._inflate = function ( data, buff ) { + + var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff ); return out; + +}; + +UPNG.inflateRaw = function () { + + var H = {}; H.H = {}; H.H.N = function ( N, W ) { + + var R = Uint8Array, i = 0, m = 0, J = 0, h = 0, Q = 0, X = 0, u = 0, w = 0, d = 0, v, C; + if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 ); var V = H.H, n = V.b, A = V.e, l = V.R, M = V.n, I = V.A, e = V.Z, b = V.m, Z = W == null; + if ( Z )W = new R( N.length >>> 2 << 5 ); while ( i == 0 ) { + + i = n( N, d, 1 ); m = n( N, d + 1, 2 ); d += 3; if ( m == 0 ) { + + if ( ( d & 7 ) != 0 )d += 8 - ( d & 7 ); + var D = ( d >>> 3 ) + 4, q = N[ D - 4 ] | N[ D - 3 ] << 8; if ( Z )W = H.H.W( W, w + q ); W.set( new R( N.buffer, N.byteOffset + D, q ), w ); d = D + q << 3; + w += q; continue + ; + + } + + if ( Z )W = H.H.W( W, w + ( 1 << 17 ) ); if ( m == 1 ) { + + v = b.J; C = b.h; X = ( 1 << 9 ) - 1; u = ( 1 << 5 ) - 1; + + } + + if ( m == 2 ) { + + J = A( N, d, 5 ) + 257; + h = A( N, d + 5, 5 ) + 1; Q = A( N, d + 10, 4 ) + 4; d += 14; var j = 1; for ( var c = 0; c < 38; c += 2 ) { + + b.Q[ c ] = 0; b.Q[ c + 1 ] = 0; + + } + + for ( var c = 0; + c < Q; c ++ ) { + + var K = A( N, d + c * 3, 3 ); b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K; if ( K > j )j = K + ; + + } + + d += 3 * Q; M( b.Q, j ); I( b.Q, j, b.u ); v = b.w; C = b.d; + d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v ); var r = V.V( b.v, 0, J, b.C ); X = ( 1 << r ) - 1; var S = V.V( b.v, J, h, b.D ); u = ( 1 << S ) - 1; M( b.C, r ); + I( b.C, r, v ); M( b.D, S ); I( b.D, S, C ) + ; + + } + + while ( ! 0 ) { + + var T = v[ e( N, d ) & X ]; d += T & 15; var p = T >>> 4; if ( p >>> 8 == 0 ) { + + W[ w ++ ] = p; + + } else if ( p == 256 ) { + + break; + + } else { + + var z = w + p - 254; + if ( p > 264 ) { + + var _ = b.q[ p - 257 ]; z = w + ( _ >>> 3 ) + A( N, d, _ & 7 ); d += _ & 7; + + } + + var $ = C[ e( N, d ) & u ]; d += $ & 15; var s = $ >>> 4, Y = b.c[ s ], a = ( Y >>> 4 ) + n( N, d, Y & 15 ); + d += Y & 15; while ( w < z ) { + + W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; W[ w ] = W[ w ++ - a ]; + + } + + w = z + ; + + } + + } + + } + + return W.length == w ? W : W.slice( 0, w ) + ; + + }; + + H.H.W = function ( N, W ) { + + var R = N.length; if ( W <= R ) return N; var V = new Uint8Array( R << 1 ); V.set( N, 0 ); return V; + + }; + + H.H.R = function ( N, W, R, V, n, A ) { + + var l = H.H.e, M = H.H.Z, I = 0; while ( I < R ) { + + var e = N[ M( V, n ) & W ]; n += e & 15; var b = e >>> 4; + if ( b <= 15 ) { + + A[ I ] = b; I ++; + + } else { + + var Z = 0, m = 0; if ( b == 16 ) { + + m = 3 + l( V, n, 2 ); n += 2; Z = A[ I - 1 ]; + + } else if ( b == 17 ) { + + m = 3 + l( V, n, 3 ); + n += 3 + ; + + } else if ( b == 18 ) { + + m = 11 + l( V, n, 7 ); n += 7; + + } + + var J = I + m; while ( I < J ) { + + A[ I ] = Z; I ++; + + } + + } + + } + + return n + ; + + }; + + H.H.V = function ( N, W, R, V ) { + + var n = 0, A = 0, l = V.length >>> 1; + while ( A < R ) { + + var M = N[ A + W ]; V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = M; if ( M > n )n = M; A ++; + + } + + while ( A < l ) { + + V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; + + } + + return n + ; + + }; + + H.H.n = function ( N, W ) { + + var R = H.H.m, V = N.length, n, A, l, M, I, e = R.j; for ( var M = 0; M <= W; M ++ )e[ M ] = 0; for ( M = 1; M < V; M += 2 )e[ N[ M ] ] ++; + var b = R.K; n = 0; e[ 0 ] = 0; for ( A = 1; A <= W; A ++ ) { + + n = n + e[ A - 1 ] << 1; b[ A ] = n; + + } + + for ( l = 0; l < V; l += 2 ) { + + I = N[ l + 1 ]; if ( I != 0 ) { + + N[ l ] = b[ I ]; + b[ I ] ++ + ; + + } + + } + + }; + + H.H.A = function ( N, W, R ) { + + var V = N.length, n = H.H.m, A = n.r; for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) { + + var M = l >> 1, I = N[ l + 1 ], e = M << 4 | I, b = W - I, Z = N[ l ] << b, m = Z + ( 1 << b ); + while ( Z != m ) { + + var J = A[ Z ] >>> 15 - W; R[ J ] = e; Z ++; + + } + + } + + }; + + H.H.l = function ( N, W ) { + + var R = H.H.m.r, V = 15 - W; for ( var n = 0; n < N.length; + n += 2 ) { + + var A = N[ n ] << W - N[ n + 1 ]; N[ n ] = R[ A ] >>> V; + + } + + }; + + H.H.M = function ( N, W, R ) { + + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; + + }; + + H.H.I = function ( N, W, R ) { + + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; N[ V + 2 ] |= R >>> 16; + + }; + + H.H.e = function ( N, W, R ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + + }; + + H.H.b = function ( N, W, R ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + + }; + + H.H.Z = function ( N, W ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); + + }; + + H.H.i = function ( N, W ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 ); + + }; + + H.H.m = function () { + + var N = Uint16Array, W = Uint32Array; + return { K: new N( 16 ), j: new N( 16 ), X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ], T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ], q: new N( 32 ), p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ], z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ], c: new W( 32 ), J: new N( 512 ), _: [], h: new N( 32 ), $: [], w: new N( 32768 ), C: [], v: [], d: new N( 32768 ), D: [], u: new N( 512 ), Q: [], r: new N( 1 << 15 ), s: new W( 286 ), Y: new W( 30 ), a: new W( 19 ), t: new W( 15e3 ), k: new N( 1 << 16 ), g: new N( 1 << 15 ) } + ; + + }(); + ( function () { + + var N = H.H.m, W = 1 << 15; for ( var R = 0; R < W; R ++ ) { + + var V = R; V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1; + V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2; V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4; V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8; + N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17 + ; + + } + + function n( A, l, M ) { + + while ( l -- != 0 )A.push( 0, M ) + ; + + } + + for ( var R = 0; R < 32; R ++ ) { + + N.q[ R ] = N.S[ R ] << 3 | N.T[ R ]; + N.c[ R ] = N.p[ R ] << 4 | N.z[ R ] + ; + + } + + n( N._, 144, 8 ); n( N._, 255 - 143, 9 ); n( N._, 279 - 255, 7 ); n( N._, 287 - 279, 8 ); H.H.n( N._, 9 ); + H.H.A( N._, 9, N.J ); H.H.l( N._, 9 ); n( N.$, 32, 5 ); H.H.n( N.$, 5 ); H.H.A( N.$, 5, N.h ); H.H.l( N.$, 5 ); n( N.Q, 19, 0 ); n( N.C, 286, 0 ); + n( N.D, 30, 0 ); n( N.v, 320, 0 ) + ; + + }() ); + + return H.H.N + ; + +}(); + + +UPNG.decode._readInterlace = function ( data, out ) { + + var w = out.width, h = out.height; + var bpp = UPNG.decode._getBPP( out ), cbpp = bpp >> 3, bpl = Math.ceil( w * bpp / 8 ); + var img = new Uint8Array( h * bpl ); + var di = 0; + + var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ]; + var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ]; + var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ]; + var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ]; + + var pass = 0; + while ( pass < 7 ) { + + var ri = row_increment[ pass ], ci = col_increment[ pass ]; + var sw = 0, sh = 0; + var cr = starting_row[ pass ]; while ( cr < h ) { + + cr += ri; sh ++; + + } + + var cc = starting_col[ pass ]; while ( cc < w ) { + + cc += ci; sw ++; + + } + + var bpll = Math.ceil( sw * bpp / 8 ); + UPNG.decode._filterZero( data, out, di, sw, sh ); + + var y = 0, row = starting_row[ pass ]; + var val; + + while ( row < h ) { + + var col = starting_col[ pass ]; + var cdi = ( di + y * bpll ) << 3; + + while ( col < w ) { + + if ( bpp == 1 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 7 - ( cdi & 7 ) ) ) & 1; + img[ row * bpl + ( col >> 3 ) ] |= ( val << ( 7 - ( ( col & 7 ) << 0 ) ) ); + + } + + if ( bpp == 2 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 6 - ( cdi & 7 ) ) ) & 3; + img[ row * bpl + ( col >> 2 ) ] |= ( val << ( 6 - ( ( col & 3 ) << 1 ) ) ); + + } + + if ( bpp == 4 ) { + + val = data[ cdi >> 3 ]; val = ( val >> ( 4 - ( cdi & 7 ) ) ) & 15; + img[ row * bpl + ( col >> 1 ) ] |= ( val << ( 4 - ( ( col & 1 ) << 2 ) ) ); + + } + + if ( bpp >= 8 ) { + + var ii = row * bpl + col * cbpp; + for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ]; + + } + + cdi += bpp; col += ci; + + } + + y ++; row += ri; + + } + + if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); + pass = pass + 1; + + } + + return img; + +}; + +UPNG.decode._getBPP = function ( out ) { + + var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ]; + return noc * out.depth; + +}; + +UPNG.decode._filterZero = function ( data, out, off, w, h ) { + + var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; + bpp = Math.ceil( bpp / 8 ); + + var i, di, type = data[ off ], x = 0; + + if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; + if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; + + for ( var y = 0; y < h; y ++ ) { + + i = off + y * bpl; di = i + y + 1; + type = data[ di - 1 ]; x = 0; + + if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; + else if ( type == 1 ) { + + for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ]; + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpp ] ); + + } else if ( type == 2 ) { + + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + data[ i + x - bpl ] ); + + } else if ( type == 3 ) { + + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + ( data[ i + x - bpl ] >>> 1 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + ( ( data[ i + x - bpl ] + data[ i + x - bpp ] ) >>> 1 ) ); + + } else { + + for ( ; x < bpp; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 ) ); + for ( ; x < bpl; x ++ ) data[ i + x ] = ( data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] ) ); + + } + + } + + return data; + +}; + +UPNG.decode._paeth = function ( a, b, c ) { + + var p = a + b - c, pa = ( p - a ), pb = ( p - b ), pc = ( p - c ); + if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; + else if ( pb * pb <= pc * pc ) return b; + return c; + +}; + +UPNG.decode._IHDR = function ( data, offset, out ) { + + var bin = UPNG._bin; + out.width = bin.readUint( data, offset ); offset += 4; + out.height = bin.readUint( data, offset ); offset += 4; + out.depth = data[ offset ]; offset ++; + out.ctype = data[ offset ]; offset ++; + out.compress = data[ offset ]; offset ++; + out.filter = data[ offset ]; offset ++; + out.interlace = data[ offset ]; offset ++; + +}; + +UPNG._bin = { + nextZero: function ( data, p ) { + + while ( data[ p ] != 0 ) p ++; return p; + + }, + readUshort: function ( buff, p ) { + + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + + }, + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + + }, + readUint: function ( buff, p ) { + + return ( buff[ p ] * ( 256 * 256 * 256 ) ) + ( ( buff[ p + 1 ] << 16 ) | ( buff[ p + 2 ] << 8 ) | buff[ p + 3 ] ); + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = n & 255; + + }, + readASCII: function ( buff, p, l ) { + + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + + }, + writeASCII: function ( data, p, s ) { + + for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i ); + + }, + readBytes: function ( buff, p, l ) { + + var arr = []; for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] ); return arr; + + }, + pad: function ( n ) { + + return n.length < 2 ? '0' + n : n; + + }, + readUTF8: function ( buff, p, l ) { + + var s = '', ns; + for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) ); + try { + + ns = decodeURIComponent( s ); + + } catch ( e ) { + + return UPNG._bin.readASCII( buff, p, l ); + + } + + return ns; + + } +}; +UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) { + + var w = Math.min( sw, tw ), h = Math.min( sh, th ); + var si = 0, ti = 0; + for ( var y = 0; y < h; y ++ ) + for ( var x = 0; x < w; x ++ ) { + + if ( xoff >= 0 && yoff >= 0 ) { + + si = ( y * sw + x ) << 2; ti = ( ( yoff + y ) * tw + xoff + x ) << 2; + + } else { + + si = ( ( - yoff + y ) * sw - xoff + x ) << 2; ti = ( y * tw + x ) << 2; + + } + + if ( mode == 0 ) { + + tb[ ti ] = sb[ si ]; tb[ ti + 1 ] = sb[ si + 1 ]; tb[ ti + 2 ] = sb[ si + 2 ]; tb[ ti + 3 ] = sb[ si + 3 ]; + + } else if ( mode == 1 ) { + + var fa = sb[ si + 3 ] * ( 1 / 255 ), fr = sb[ si ] * fa, fg = sb[ si + 1 ] * fa, fb = sb[ si + 2 ] * fa; + var ba = tb[ ti + 3 ] * ( 1 / 255 ), br = tb[ ti ] * ba, bg = tb[ ti + 1 ] * ba, bb = tb[ ti + 2 ] * ba; + + var ifa = 1 - fa, oa = fa + ba * ifa, ioa = ( oa == 0 ? 0 : 1 / oa ); + tb[ ti + 3 ] = 255 * oa; + tb[ ti + 0 ] = ( fr + br * ifa ) * ioa; + tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa; + tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa; + + } else if ( mode == 2 ) { // copy only differences, otherwise zero + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) { + + tb[ ti ] = 0; tb[ ti + 1 ] = 0; tb[ ti + 2 ] = 0; tb[ ti + 3 ] = 0; + + } else { + + tb[ ti ] = fr; tb[ ti + 1 ] = fg; tb[ ti + 2 ] = fb; tb[ ti + 3 ] = fa; + + } + + } else if ( mode == 3 ) { // check if can be blended + + var fa = sb[ si + 3 ], fr = sb[ si ], fg = sb[ si + 1 ], fb = sb[ si + 2 ]; + var ba = tb[ ti + 3 ], br = tb[ ti ], bg = tb[ ti + 1 ], bb = tb[ ti + 2 ]; + if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; + //if(fa!=255 && ba!=0) return false; + if ( fa < 220 && ba > 20 ) return false; + + } + + } + + return true; + +}; + +export { RGBMLoader }; diff --git a/jsm/loaders/STLLoader.js b/jsm/loaders/STLLoader.js new file mode 100644 index 0000000..b3981df --- /dev/null +++ b/jsm/loaders/STLLoader.js @@ -0,0 +1,399 @@ +import { + BufferAttribute, + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + LoaderUtils, + Vector3 +} from 'three'; + +/** + * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. + * + * Supports both binary and ASCII encoded files, with automatic detection of type. + * + * The loader returns a non-indexed buffer geometry. + * + * Limitations: + * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). + * There is perhaps some question as to how valid it is to always assume little-endian-ness. + * ASCII decoding assumes file is UTF-8. + * + * Usage: + * const loader = new STLLoader(); + * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { + * scene.add( new THREE.Mesh( geometry ) ); + * }); + * + * For binary STLs geometry might contain colors for vertices. To use it: + * // use the same code to load STL as above + * if (geometry.hasColors) { + * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true }); + * } else { .... } + * const mesh = new THREE.Mesh( geometry, material ); + * + * For ASCII STLs containing multiple solids, each solid is assigned to a different group. + * Groups can be used to assign a different color by defining an array of materials with the same length of + * geometry.groups and passing it to the Mesh constructor: + * + * const mesh = new THREE.Mesh( geometry, material ); + * + * For example: + * + * const materials = []; + * const nGeometryGroups = geometry.groups.length; + * + * const colorMap = ...; // Some logic to index colors. + * + * for (let i = 0; i < nGeometryGroups; i++) { + * + * const material = new THREE.MeshPhongMaterial({ + * color: colorMap[i], + * wireframe: false + * }); + * + * } + * + * materials.push(material); + * const mesh = new THREE.Mesh(geometry, materials); + */ + + +class STLLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + function isBinary( data ) { + + const reader = new DataView( data ); + const face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 ); + const n_faces = reader.getUint32( 80, true ); + const expect = 80 + ( 32 / 8 ) + ( n_faces * face_size ); + + if ( expect === reader.byteLength ) { + + return true; + + } + + // An ASCII STL data must begin with 'solid ' as the first six bytes. + // However, ASCII STLs lacking the SPACE after the 'd' are known to be + // plentiful. So, check the first 5 bytes for 'solid'. + + // Several encodings, such as UTF-8, precede the text with up to 5 bytes: + // https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding + // Search for "solid" to start anywhere after those prefixes. + + // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd' + + const solid = [ 115, 111, 108, 105, 100 ]; + + for ( let off = 0; off < 5; off ++ ) { + + // If "solid" text is matched to the current offset, declare it to be an ASCII STL. + + if ( matchDataViewAt( solid, reader, off ) ) return false; + + } + + // Couldn't find "solid" text at the beginning; it is binary STL. + + return true; + + } + + function matchDataViewAt( query, reader, offset ) { + + // Check if each byte in query matches the corresponding byte from the current offset + + for ( let i = 0, il = query.length; i < il; i ++ ) { + + if ( query[ i ] !== reader.getUint8( offset + i ) ) return false; + + } + + return true; + + } + + function parseBinary( data ) { + + const reader = new DataView( data ); + const faces = reader.getUint32( 80, true ); + + let r, g, b, hasColors = false, colors; + let defaultR, defaultG, defaultB, alpha; + + // process STL header + // check for default color in header ("COLOR=rgba" sequence). + + for ( let index = 0; index < 80 - 10; index ++ ) { + + if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) && + ( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) && + ( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) { + + hasColors = true; + colors = new Float32Array( faces * 3 * 3 ); + + defaultR = reader.getUint8( index + 6 ) / 255; + defaultG = reader.getUint8( index + 7 ) / 255; + defaultB = reader.getUint8( index + 8 ) / 255; + alpha = reader.getUint8( index + 9 ) / 255; + + } + + } + + const dataOffset = 84; + const faceLength = 12 * 4 + 2; + + const geometry = new BufferGeometry(); + + const vertices = new Float32Array( faces * 3 * 3 ); + const normals = new Float32Array( faces * 3 * 3 ); + + for ( let face = 0; face < faces; face ++ ) { + + const start = dataOffset + face * faceLength; + const normalX = reader.getFloat32( start, true ); + const normalY = reader.getFloat32( start + 4, true ); + const normalZ = reader.getFloat32( start + 8, true ); + + if ( hasColors ) { + + const packedColor = reader.getUint16( start + 48, true ); + + if ( ( packedColor & 0x8000 ) === 0 ) { + + // facet has its own unique color + + r = ( packedColor & 0x1F ) / 31; + g = ( ( packedColor >> 5 ) & 0x1F ) / 31; + b = ( ( packedColor >> 10 ) & 0x1F ) / 31; + + } else { + + r = defaultR; + g = defaultG; + b = defaultB; + + } + + } + + for ( let i = 1; i <= 3; i ++ ) { + + const vertexstart = start + i * 12; + const componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 ); + + vertices[ componentIdx ] = reader.getFloat32( vertexstart, true ); + vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true ); + vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true ); + + normals[ componentIdx ] = normalX; + normals[ componentIdx + 1 ] = normalY; + normals[ componentIdx + 2 ] = normalZ; + + if ( hasColors ) { + + colors[ componentIdx ] = r; + colors[ componentIdx + 1 ] = g; + colors[ componentIdx + 2 ] = b; + + } + + } + + } + + geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); + + if ( hasColors ) { + + geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); + geometry.hasColors = true; + geometry.alpha = alpha; + + } + + return geometry; + + } + + function parseASCII( data ) { + + const geometry = new BufferGeometry(); + const patternSolid = /solid([\s\S]*?)endsolid/g; + const patternFace = /facet([\s\S]*?)endfacet/g; + let faceCounter = 0; + + const patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; + const patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' ); + const patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' ); + + const vertices = []; + const normals = []; + + const normal = new Vector3(); + + let result; + + let groupCount = 0; + let startVertex = 0; + let endVertex = 0; + + while ( ( result = patternSolid.exec( data ) ) !== null ) { + + startVertex = endVertex; + + const solid = result[ 0 ]; + + while ( ( result = patternFace.exec( solid ) ) !== null ) { + + let vertexCountPerFace = 0; + let normalCountPerFace = 0; + + const text = result[ 0 ]; + + while ( ( result = patternNormal.exec( text ) ) !== null ) { + + normal.x = parseFloat( result[ 1 ] ); + normal.y = parseFloat( result[ 2 ] ); + normal.z = parseFloat( result[ 3 ] ); + normalCountPerFace ++; + + } + + while ( ( result = patternVertex.exec( text ) ) !== null ) { + + vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) ); + normals.push( normal.x, normal.y, normal.z ); + vertexCountPerFace ++; + endVertex ++; + + } + + // every face have to own ONE valid normal + + if ( normalCountPerFace !== 1 ) { + + console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter ); + + } + + // each face have to own THREE valid vertices + + if ( vertexCountPerFace !== 3 ) { + + console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter ); + + } + + faceCounter ++; + + } + + const start = startVertex; + const count = endVertex - startVertex; + + geometry.addGroup( start, count, groupCount ); + groupCount ++; + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + + return geometry; + + } + + function ensureString( buffer ) { + + if ( typeof buffer !== 'string' ) { + + return LoaderUtils.decodeText( new Uint8Array( buffer ) ); + + } + + return buffer; + + } + + function ensureBinary( buffer ) { + + if ( typeof buffer === 'string' ) { + + const array_buffer = new Uint8Array( buffer.length ); + for ( let i = 0; i < buffer.length; i ++ ) { + + array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian + + } + + return array_buffer.buffer || array_buffer; + + } else { + + return buffer; + + } + + } + + // start + + const binData = ensureBinary( data ); + + return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) ); + + } + +} + +export { STLLoader }; diff --git a/jsm/loaders/SVGLoader.js b/jsm/loaders/SVGLoader.js new file mode 100644 index 0000000..f754b27 --- /dev/null +++ b/jsm/loaders/SVGLoader.js @@ -0,0 +1,2949 @@ +import { + Box2, + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + Matrix3, + Path, + Shape, + ShapePath, + ShapeUtils, + Vector2, + Vector3 +} from 'three'; + +class SVGLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + // Default dots per inch + this.defaultDPI = 90; + + // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' + this.defaultUnit = 'px'; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( text ) { + + const scope = this; + + function parseNode( node, style ) { + + if ( node.nodeType !== 1 ) return; + + const transform = getNodeTransform( node ); + + let isDefsNode = false; + + let path = null; + + switch ( node.nodeName ) { + + case 'svg': + break; + + case 'style': + parseCSSStylesheet( node ); + break; + + case 'g': + style = parseStyle( node, style ); + break; + + case 'path': + style = parseStyle( node, style ); + if ( node.hasAttribute( 'd' ) ) path = parsePathNode( node ); + break; + + case 'rect': + style = parseStyle( node, style ); + path = parseRectNode( node ); + break; + + case 'polygon': + style = parseStyle( node, style ); + path = parsePolygonNode( node ); + break; + + case 'polyline': + style = parseStyle( node, style ); + path = parsePolylineNode( node ); + break; + + case 'circle': + style = parseStyle( node, style ); + path = parseCircleNode( node ); + break; + + case 'ellipse': + style = parseStyle( node, style ); + path = parseEllipseNode( node ); + break; + + case 'line': + style = parseStyle( node, style ); + path = parseLineNode( node ); + break; + + case 'defs': + isDefsNode = true; + break; + + case 'use': + style = parseStyle( node, style ); + + const href = node.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) || ''; + const usedNodeId = href.substring( 1 ); + const usedNode = node.viewportElement.getElementById( usedNodeId ); + if ( usedNode ) { + + parseNode( usedNode, style ); + + } else { + + console.warn( 'SVGLoader: \'use node\' references non-existent node id: ' + usedNodeId ); + + } + + break; + + default: + // console.log( node ); + + } + + if ( path ) { + + if ( style.fill !== undefined && style.fill !== 'none' ) { + + path.color.setStyle( style.fill ); + + } + + transformPath( path, currentTransform ); + + paths.push( path ); + + path.userData = { node: node, style: style }; + + } + + const childNodes = node.childNodes; + + for ( let i = 0; i < childNodes.length; i ++ ) { + + const node = childNodes[ i ]; + + if ( isDefsNode && node.nodeName !== 'style' && node.nodeName !== 'defs' ) { + + // Ignore everything in defs except CSS style definitions + // and nested defs, because it is OK by the standard to have + //

@ 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