From 71ddbefccef281732e3a4061fd032927b9e95b22 Mon Sep 17 00:00:00 2001 From: NixAJ Date: Sun, 23 Jun 2024 03:00:42 +0200 Subject: [PATCH] Updated Premake Integration - Removed Premake5.exe - Added light wrapper utilities for typical premake operations - Added Quill as dependency (New Logger Library) - Updated all existing premake files to use new light wrapper utilities Updated Submodule Engine Updated Jolt Removed Meshoptimizer --- Dependencies/Dependencies.lua | 17 +- .../NodeCodec/NodeCodecQuadTreeHalfFloat.h | 4 +- .../TriangleCodecIndexed8BitPackSOA4Flags.h | 32 +- Dependencies/Jolt/Jolt/Core/Color.h | 3 + Dependencies/Jolt/Jolt/Core/Core.h | 27 +- Dependencies/Jolt/Jolt/Core/FPControlWord.h | 10 +- Dependencies/Jolt/Jolt/Core/FPException.h | 16 +- .../Jolt/Jolt/Core/FPFlushDenormals.h | 12 +- Dependencies/Jolt/Jolt/Core/Factory.h | 2 +- Dependencies/Jolt/Jolt/Core/JobSystem.h | 2 +- .../Jolt/Jolt/Core/JobSystemThreadPool.cpp | 88 +- .../Jolt/Jolt/Core/JobSystemThreadPool.h | 2 +- Dependencies/Jolt/Jolt/Core/LockFreeHashMap.h | 2 +- Dependencies/Jolt/Jolt/Core/Memory.cpp | 1 + Dependencies/Jolt/Jolt/Core/Profiler.h | 2 +- Dependencies/Jolt/Jolt/Core/Reference.h | 4 +- Dependencies/Jolt/Jolt/Core/Semaphore.h | 2 +- Dependencies/Jolt/Jolt/Core/StreamIn.h | 16 + Dependencies/Jolt/Jolt/Core/StreamOut.h | 13 +- Dependencies/Jolt/Jolt/Core/TempAllocator.h | 10 +- Dependencies/Jolt/Jolt/Core/TickCounter.h | 4 + Dependencies/Jolt/Jolt/Geometry/AABox4.h | 25 +- .../Jolt/Jolt/Geometry/ClosestPoint.h | 25 +- .../Jolt/Jolt/Geometry/ConvexHullBuilder.cpp | 2 +- .../Jolt/Jolt/Geometry/ConvexHullBuilder.h | 4 +- .../Jolt/Geometry/ConvexHullBuilder2D.cpp | 2 +- .../Jolt/Jolt/Geometry/ConvexHullBuilder2D.h | 4 +- .../Jolt/Jolt/Geometry/EPAConvexHullBuilder.h | 2 +- .../Jolt/Jolt/Geometry/EPAPenetrationDepth.h | 4 +- Dependencies/Jolt/Jolt/Geometry/Ellipse.h | 4 +- .../Jolt/Jolt/Geometry/GJKClosestPoint.h | 14 +- .../Jolt/Jolt/Geometry/IndexedTriangle.h | 4 +- Dependencies/Jolt/Jolt/Geometry/OrientedBox.h | 4 +- Dependencies/Jolt/Jolt/Jolt.cmake | 56 +- Dependencies/Jolt/Jolt/Jolt.natvis | 4 +- Dependencies/Jolt/Jolt/Math/DMat44.h | 2 +- Dependencies/Jolt/Jolt/Math/DVec3.h | 3 + Dependencies/Jolt/Jolt/Math/DVec3.inl | 4 +- .../Jolt/Jolt/Math/EigenValueSymmetric.h | 2 +- Dependencies/Jolt/Jolt/Math/Float3.h | 2 +- Dependencies/Jolt/Jolt/Math/Mat44.h | 5 +- Dependencies/Jolt/Jolt/Math/Mat44.inl | 11 +- Dependencies/Jolt/Jolt/Math/Math.h | 4 + Dependencies/Jolt/Jolt/Math/MathTypes.h | 14 +- Dependencies/Jolt/Jolt/Math/Real.h | 2 +- Dependencies/Jolt/Jolt/Math/UVec4.inl | 2 +- Dependencies/Jolt/Jolt/Math/Vec4.h | 4 +- .../Jolt/Jolt/Physics/Body/AllowedDOFs.h | 12 +- Dependencies/Jolt/Jolt/Physics/Body/Body.cpp | 46 +- Dependencies/Jolt/Jolt/Physics/Body/Body.h | 79 +- Dependencies/Jolt/Jolt/Physics/Body/Body.inl | 33 +- .../Physics/Body/BodyCreationSettings.cpp | 18 +- .../Jolt/Physics/Body/BodyCreationSettings.h | 6 +- Dependencies/Jolt/Jolt/Physics/Body/BodyID.h | 2 +- .../Jolt/Jolt/Physics/Body/BodyInterface.cpp | 25 + .../Jolt/Jolt/Physics/Body/BodyInterface.h | 8 +- .../Jolt/Jolt/Physics/Body/BodyManager.cpp | 28 +- .../Jolt/Jolt/Physics/Body/BodyManager.h | 4 + .../Jolt/Physics/Body/MotionProperties.cpp | 76 +- .../Jolt/Jolt/Physics/Body/MotionProperties.h | 61 +- .../Jolt/Physics/Body/MotionProperties.inl | 52 +- .../Physics/Character/CharacterVirtual.cpp | 33 +- .../Jolt/Physics/Character/CharacterVirtual.h | 22 +- .../Physics/Collision/BroadPhase/BroadPhase.h | 3 + .../BroadPhase/BroadPhaseBruteForce.cpp | 10 + .../BroadPhase/BroadPhaseBruteForce.h | 1 + .../Collision/BroadPhase/BroadPhaseLayer.h | 5 + .../BroadPhase/BroadPhaseLayerInterfaceMask.h | 92 + .../BroadPhaseLayerInterfaceTable.h | 64 + .../BroadPhase/BroadPhaseQuadTree.cpp | 11 + .../Collision/BroadPhase/BroadPhaseQuadTree.h | 1 + .../ObjectVsBroadPhaseLayerFilterMask.h | 35 + .../ObjectVsBroadPhaseLayerFilterTable.h | 66 + .../Physics/Collision/BroadPhase/QuadTree.cpp | 18 +- .../Physics/Collision/BroadPhase/QuadTree.h | 3 + .../Collision/CastConvexVsTriangles.cpp | 2 +- .../Jolt/Jolt/Physics/Collision/CastResult.h | 3 + .../Collision/CastSphereVsTriangles.cpp | 6 +- .../Collision/CollideConvexVsTriangles.cpp | 9 +- .../CollideSoftBodyVerticesVsTriangles.h | 98 + .../Collision/CollideSphereVsTriangles.cpp | 14 +- .../Physics/Collision/CollisionCollector.h | 4 +- .../Physics/Collision/CollisionDispatch.h | 2 +- .../Jolt/Physics/Collision/ContactListener.h | 10 +- .../Collision/EstimateCollisionResponse.cpp | 2 +- .../Jolt/Physics/Collision/GroupFilterTable.h | 30 +- .../Collision/InternalEdgeRemovingCollector.h | 233 + .../Collision/ManifoldBetweenTwoFaces.cpp | 4 +- .../Collision/ManifoldBetweenTwoFaces.h | 4 +- .../Collision/ObjectLayerPairFilterMask.h | 52 + .../Collision/ObjectLayerPairFilterTable.h | 78 + .../Jolt/Physics/Collision/Shape/BoxShape.cpp | 1 + .../Jolt/Physics/Collision/Shape/BoxShape.h | 2 +- .../Physics/Collision/Shape/CapsuleShape.cpp | 21 +- .../Physics/Collision/Shape/CapsuleShape.h | 2 +- .../Physics/Collision/Shape/CompoundShape.cpp | 42 +- .../Physics/Collision/Shape/CompoundShape.h | 2 +- .../Collision/Shape/CompoundShapeVisitors.h | 2 +- .../Collision/Shape/ConvexHullShape.cpp | 5 +- .../Physics/Collision/Shape/ConvexHullShape.h | 2 +- .../Physics/Collision/Shape/ConvexShape.cpp | 2 +- .../Physics/Collision/Shape/ConvexShape.h | 5 +- .../Physics/Collision/Shape/CylinderShape.cpp | 3 +- .../Physics/Collision/Shape/CylinderShape.h | 2 +- .../Collision/Shape/GetTrianglesContext.h | 2 +- .../Collision/Shape/HeightFieldShape.cpp | 239 +- .../Collision/Shape/HeightFieldShape.h | 39 +- .../Physics/Collision/Shape/MeshShape.cpp | 53 +- .../Jolt/Physics/Collision/Shape/MeshShape.h | 2 +- .../Collision/Shape/OffsetCenterOfMassShape.h | 2 +- .../Shape/RotatedTranslatedShape.cpp | 36 + .../Collision/Shape/RotatedTranslatedShape.h | 4 +- .../Physics/Collision/Shape/ScaledShape.h | 2 +- .../Jolt/Physics/Collision/Shape/Shape.cpp | 41 +- .../Jolt/Jolt/Physics/Collision/Shape/Shape.h | 23 +- .../Physics/Collision/Shape/SphereShape.cpp | 3 +- .../Physics/Collision/Shape/SphereShape.h | 2 +- .../Collision/Shape/TaperedCapsuleShape.cpp | 51 +- .../Collision/Shape/TaperedCapsuleShape.h | 2 +- .../Physics/Collision/Shape/TriangleShape.cpp | 56 +- .../Physics/Collision/Shape/TriangleShape.h | 2 +- .../Physics/Collision/SortReverseAndStore.h | 9 +- .../Jolt/Physics/Collision/TransformedShape.h | 4 +- .../Constraints/CalculateSolverSteps.h | 66 + .../Physics/Constraints/ConeConstraint.cpp | 6 + .../Jolt/Physics/Constraints/ConeConstraint.h | 3 +- .../Jolt/Physics/Constraints/Constraint.h | 40 +- .../Physics/Constraints/ConstraintManager.cpp | 35 +- .../Physics/Constraints/ConstraintManager.h | 9 +- .../ConstraintPart/AxisConstraintPart.h | 10 +- .../ConstraintPart/DualAxisConstraintPart.h | 12 +- .../RotationEulerConstraintPart.h | 6 +- .../RotationQuatConstraintPart.h | 2 +- .../Constraints/ConstraintPart/SpringPart.h | 2 +- .../ConstraintPart/SwingTwistConstraintPart.h | 300 +- .../Constraints/ContactConstraintManager.cpp | 69 +- .../Constraints/ContactConstraintManager.h | 3 +- .../Constraints/DistanceConstraint.cpp | 17 +- .../Physics/Constraints/DistanceConstraint.h | 3 +- .../Physics/Constraints/FixedConstraint.cpp | 6 + .../Physics/Constraints/FixedConstraint.h | 5 +- .../Physics/Constraints/GearConstraint.cpp | 5 + .../Jolt/Physics/Constraints/GearConstraint.h | 3 +- .../Physics/Constraints/HingeConstraint.cpp | 17 +- .../Physics/Constraints/HingeConstraint.h | 4 +- .../Physics/Constraints/PathConstraint.cpp | 19 +- .../Jolt/Physics/Constraints/PathConstraint.h | 12 +- .../Physics/Constraints/PathConstraintPath.h | 7 +- .../Constraints/PathConstraintPathHermite.cpp | 2 +- .../Constraints/PathConstraintPathHermite.h | 2 +- .../Physics/Constraints/PointConstraint.cpp | 5 + .../Physics/Constraints/PointConstraint.h | 3 +- .../Physics/Constraints/PulleyConstraint.cpp | 5 + .../Physics/Constraints/PulleyConstraint.h | 3 +- .../Constraints/RackAndPinionConstraint.cpp | 5 + .../Constraints/RackAndPinionConstraint.h | 3 +- .../Physics/Constraints/SixDOFConstraint.cpp | 138 +- .../Physics/Constraints/SixDOFConstraint.h | 41 +- .../Physics/Constraints/SliderConstraint.cpp | 8 + .../Physics/Constraints/SliderConstraint.h | 3 +- .../Jolt/Physics/Constraints/SpringSettings.h | 6 +- .../Constraints/SwingTwistConstraint.cpp | 28 +- .../Constraints/SwingTwistConstraint.h | 14 +- .../Physics/Constraints/TwoBodyConstraint.h | 2 +- .../Jolt/Jolt/Physics/IslandBuilder.cpp | 4 + .../Jolt/Jolt/Physics/IslandBuilder.h | 6 + .../Jolt/Jolt/Physics/LargeIslandSplitter.cpp | 32 +- .../Jolt/Jolt/Physics/LargeIslandSplitter.h | 5 +- .../Jolt/Jolt/Physics/PhysicsSettings.h | 8 +- .../Jolt/Jolt/Physics/PhysicsSystem.cpp | 336 +- .../Jolt/Jolt/Physics/PhysicsSystem.h | 14 + .../Jolt/Jolt/Physics/PhysicsUpdateContext.h | 14 +- .../Jolt/Jolt/Physics/Ragdoll/Ragdoll.cpp | 12 +- .../Jolt/Jolt/Physics/Ragdoll/Ragdoll.h | 3 + .../SoftBody/SoftBodyContactListener.h | 55 + .../SoftBody/SoftBodyCreationSettings.h | 2 +- .../Jolt/Physics/SoftBody/SoftBodyManifold.h | 59 + .../SoftBody/SoftBodyMotionProperties.cpp | 698 +- .../SoftBody/SoftBodyMotionProperties.h | 89 +- .../Jolt/Physics/SoftBody/SoftBodyShape.cpp | 8 + .../Jolt/Physics/SoftBody/SoftBodyShape.h | 3 + .../SoftBody/SoftBodySharedSettings.cpp | 476 +- .../Physics/SoftBody/SoftBodySharedSettings.h | 204 +- .../Physics/SoftBody/SoftBodyUpdateContext.h | 2 + .../Jolt/Physics/SoftBody/SoftBodyVertex.h | 1 + .../Physics/Vehicle/MotorcycleController.cpp | 7 +- .../Physics/Vehicle/MotorcycleController.h | 30 +- .../Vehicle/TrackedVehicleController.cpp | 23 +- .../Vehicle/TrackedVehicleController.h | 4 +- .../Vehicle/VehicleCollisionTester.cpp | 88 + .../Physics/Vehicle/VehicleCollisionTester.h | 17 + .../Physics/Vehicle/VehicleConstraint.cpp | 81 +- .../Jolt/Physics/Vehicle/VehicleConstraint.h | 46 +- .../Jolt/Physics/Vehicle/VehicleController.h | 4 +- .../Jolt/Jolt/Physics/Vehicle/VehicleEngine.h | 2 +- .../Physics/Vehicle/VehicleTransmission.h | 2 +- .../Jolt/Jolt/Physics/Vehicle/Wheel.h | 4 +- .../Vehicle/WheeledVehicleController.cpp | 44 +- .../Vehicle/WheeledVehicleController.h | 21 +- Dependencies/Jolt/Jolt/RegisterTypes.cpp | 16 +- Dependencies/Jolt/Jolt/RegisterTypes.h | 4 +- .../Jolt/Jolt/Renderer/DebugRenderer.cpp | 185 +- .../Jolt/Jolt/Renderer/DebugRenderer.h | 100 +- .../Jolt/Renderer/DebugRendererSimple.cpp | 80 + .../Jolt/Jolt/Renderer/DebugRendererSimple.h | 88 + .../Jolt/Jolt/Skeleton/SkeletalAnimation.h | 2 +- .../Jolt/Jolt/Skeleton/SkeletonPose.cpp | 2 +- .../version-4.0.2 => Jolt/version-5.0.0} | 0 Dependencies/jolt/jolt.lua | 178 +- Dependencies/meshoptimizer/.clang-format | 13 - Dependencies/meshoptimizer/.editorconfig | 8 - .../.github/ISSUE_TEMPLATE/bug_report.md | 8 - .../.github/ISSUE_TEMPLATE/config.yml | 5 - .../.github/ISSUE_TEMPLATE/feature_request.md | 8 - .../meshoptimizer/.github/workflows/build.yml | 149 - .../.github/workflows/release.yml | 36 - Dependencies/meshoptimizer/.gitignore | 3 - Dependencies/meshoptimizer/CMakeLists.txt | 151 - Dependencies/meshoptimizer/CONTRIBUTING.md | 54 - Dependencies/meshoptimizer/LICENSE.md | 21 - Dependencies/meshoptimizer/Makefile | 189 - Dependencies/meshoptimizer/README.md | 372 - Dependencies/meshoptimizer/codecov.yml | 11 - Dependencies/meshoptimizer/config.cmake.in | 4 - Dependencies/meshoptimizer/demo/ansi.c | 2 - Dependencies/meshoptimizer/demo/demo.html | 66 - Dependencies/meshoptimizer/demo/index.html | 128 - Dependencies/meshoptimizer/demo/main.cpp | 1249 -- Dependencies/meshoptimizer/demo/pirate.glb | Bin 24696 -> 0 bytes Dependencies/meshoptimizer/demo/pirate.obj | 11358 ---------------- Dependencies/meshoptimizer/demo/tests.cpp | 1147 -- Dependencies/meshoptimizer/extern/cgltf.h | 6638 --------- Dependencies/meshoptimizer/extern/fast_obj.h | 1434 -- Dependencies/meshoptimizer/extern/sdefl.h | 696 - Dependencies/meshoptimizer/gltf/README.md | 64 - Dependencies/meshoptimizer/gltf/animation.cpp | 336 - Dependencies/meshoptimizer/gltf/basisenc.cpp | 173 - Dependencies/meshoptimizer/gltf/basislib.cpp | 66 - Dependencies/meshoptimizer/gltf/cli.js | 26 - Dependencies/meshoptimizer/gltf/fileio.cpp | 126 - Dependencies/meshoptimizer/gltf/gltfpack.cpp | 1497 -- Dependencies/meshoptimizer/gltf/gltfpack.h | 381 - Dependencies/meshoptimizer/gltf/image.cpp | 268 - Dependencies/meshoptimizer/gltf/json.cpp | 74 - Dependencies/meshoptimizer/gltf/library.js | 374 - Dependencies/meshoptimizer/gltf/material.cpp | 530 - Dependencies/meshoptimizer/gltf/mesh.cpp | 996 -- Dependencies/meshoptimizer/gltf/node.cpp | 206 - Dependencies/meshoptimizer/gltf/package.json | 24 - Dependencies/meshoptimizer/gltf/parsegltf.cpp | 525 - Dependencies/meshoptimizer/gltf/parseobj.cpp | 219 - Dependencies/meshoptimizer/gltf/stream.cpp | 820 -- Dependencies/meshoptimizer/gltf/wasistubs.cpp | 45 - Dependencies/meshoptimizer/gltf/wasistubs.txt | 2 - Dependencies/meshoptimizer/gltf/write.cpp | 1604 --- Dependencies/meshoptimizer/js/README.md | 110 - Dependencies/meshoptimizer/js/benchmark.js | 104 - Dependencies/meshoptimizer/js/index.js | 4 - .../meshoptimizer/js/index.module.d.ts | 2 - Dependencies/meshoptimizer/js/index.module.js | 2 - .../meshoptimizer/js/meshopt_decoder.js | 122 - .../js/meshopt_decoder.module.d.ts | 12 - .../js/meshopt_decoder.module.js | 112 - .../meshoptimizer/js/meshopt_decoder.test.js | 267 - .../js/meshopt_decoder_reference.js | 348 - .../meshoptimizer/js/meshopt_encoder.js | 182 - .../js/meshopt_encoder.module.d.ts | 18 - .../js/meshopt_encoder.module.js | 172 - .../meshoptimizer/js/meshopt_encoder.test.js | 197 - Dependencies/meshoptimizer/js/package.json | 27 - Dependencies/meshoptimizer/src/allocator.cpp | 8 - .../meshoptimizer/src/clusterizer.cpp | 856 -- Dependencies/meshoptimizer/src/indexcodec.cpp | 674 - .../meshoptimizer/src/indexgenerator.cpp | 551 - .../meshoptimizer/src/meshoptimizer.h | 1069 -- .../meshoptimizer/src/overdrawanalyzer.cpp | 230 - .../meshoptimizer/src/overdrawoptimizer.cpp | 333 - Dependencies/meshoptimizer/src/simplifier.cpp | 1677 --- .../meshoptimizer/src/spatialorder.cpp | 194 - Dependencies/meshoptimizer/src/stripifier.cpp | 295 - .../meshoptimizer/src/vcacheanalyzer.cpp | 73 - .../meshoptimizer/src/vcacheoptimizer.cpp | 473 - .../meshoptimizer/src/vertexcodec.cpp | 1195 -- .../meshoptimizer/src/vertexfilter.cpp | 962 -- .../meshoptimizer/src/vfetchanalyzer.cpp | 58 - .../meshoptimizer/src/vfetchoptimizer.cpp | 74 - .../meshoptimizer/tools/codecbench.cpp | 224 - .../meshoptimizer/tools/codecfuzz.cpp | 42 - .../meshoptimizer/tools/meshloader.cpp | 9 - .../meshoptimizer/tools/vcachetester.cpp | 509 - .../meshoptimizer/tools/vcachetuner.cpp | 498 - Dependencies/meshoptimizer/tools/wasmpack.py | 36 - .../meshoptimizer/tools/wasmstubs.cpp | 112 - Premake/ProjectUtil.lua | 487 +- Source/Game-Tests/Game-Tests.lua | 17 + .../Game-Tests}/Example.cpp | 0 Source/Game/Game.lua | 104 +- .../Game/Game/Animation/AnimationSystem.cpp | 4 +- Source/Game/Game/Application/Application.cpp | 26 +- .../Application/ConsoleCommandHandler.cpp | 2 +- .../Game/Game/ECS/Singletons/ActiveCamera.h | 10 +- .../Game/Game/ECS/Singletons/CameraSaveDB.h | 53 +- .../Game/ECS/Singletons/CharacterSingleton.h | 3 + .../Game/ECS/Singletons/ClientDBCollection.h | 212 +- Source/Game/Game/ECS/Singletons/JoltState.h | 298 +- Source/Game/Game/ECS/Singletons/MapDB.h | 14 +- .../Game/Game/ECS/Singletons/NetworkState.h | 16 +- .../ECS/Singletons/OrbitalCameraSettings.h | 22 +- Source/Game/Game/ECS/Singletons/RenderState.h | 10 +- .../Game/ECS/Singletons/TextureSingleton.h | 14 +- .../Systems/CalculateShadowCameraMatrices.cpp | 4 +- .../Game/ECS/Systems/CharacterController.cpp | 50 +- .../Game/ECS/Systems/NetworkConnection.cpp | 12 +- .../Game/Game/ECS/Systems/OrbitalCamera.cpp | 79 +- .../Game/Game/ECS/Systems/UpdatePhysics.cpp | 2 +- Source/Game/Game/ECS/Util/MapUtil.cpp | 71 +- Source/Game/Game/ECS/Util/Transforms.cpp | 6 +- Source/Game/Game/Editor/AssetBrowser.cpp | 3 +- Source/Game/Game/Editor/CVarEditor.cpp | 2 +- Source/Game/Game/Editor/CameraInfo.cpp | 45 +- Source/Game/Game/Editor/EditorHandler.cpp | 11 +- Source/Game/Game/Editor/MapSelector.cpp | 58 +- Source/Game/Game/Editor/SkyboxSelector.cpp | 3 +- .../Game/Gameplay/GameConsole/GameConsole.h | 36 +- .../GameConsole/GameConsoleCommandHandler.cpp | 2 +- .../GameConsole/GameConsoleCommandHandler.h | 2 +- .../GameConsole/GameConsoleCommands.cpp | 2 +- Source/Game/Game/Gameplay/MapLoader.cpp | 14 +- .../Game/Loaders/ClientDB/ClientDBLoader.h | 152 +- .../Game/Game/Loaders/Texture/TextureLoader.h | 6 +- Source/Game/Game/Rendering/CulledRenderer.cpp | 10 +- .../Game/Game/Rendering/CullingResources.cpp | 2 +- .../Game/Rendering/Effect/EffectRenderer.cpp | 4 +- .../Game/Rendering/Liquid/LiquidRenderer.cpp | 27 +- .../Game/Game/Rendering/Model/ModelLoader.cpp | 29 +- .../Game/Rendering/Model/ModelRenderer.cpp | 14 +- .../Game/Rendering/Terrain/TerrainLoader.cpp | 26 +- .../Rendering/Terrain/TerrainManipulator.cpp | 2 +- .../Rendering/Terrain/TerrainRenderer.cpp | 4 +- Source/Game/Game/Scripting/LuaStateCtx.cpp | 12 +- .../Game/Util/ImGui/FakeScrollingArea.cpp | 4 +- Source/Game/Game/Util/JoltStream.cpp | 2 +- Source/Game/main.cpp | 11 +- Source/Generate/Generate.lua | 7 +- Source/Modules.lua | 35 + Source/Projects.lua | 30 - .../ShaderCookerStandalone.lua | 26 +- Source/ShaderCookerStandalone/main.cpp | 20 +- Source/Shaders/Shaders.lua | 46 +- Source/UnitTests/UnitTests.lua | 6 - Submodules/Engine | 2 +- premake5.exe | Bin 1568256 -> 0 bytes premake5.lua | 108 +- 353 files changed, 6425 insertions(+), 46285 deletions(-) create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h create mode 100644 Dependencies/Jolt/Jolt/Physics/Constraints/CalculateSolverSteps.h create mode 100644 Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyContactListener.h create mode 100644 Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyManifold.h create mode 100644 Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.cpp create mode 100644 Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.h rename Dependencies/{jolt/version-4.0.2 => Jolt/version-5.0.0} (100%) delete mode 100644 Dependencies/meshoptimizer/.clang-format delete mode 100644 Dependencies/meshoptimizer/.editorconfig delete mode 100644 Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/config.yml delete mode 100644 Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 Dependencies/meshoptimizer/.github/workflows/build.yml delete mode 100644 Dependencies/meshoptimizer/.github/workflows/release.yml delete mode 100644 Dependencies/meshoptimizer/.gitignore delete mode 100644 Dependencies/meshoptimizer/CMakeLists.txt delete mode 100644 Dependencies/meshoptimizer/CONTRIBUTING.md delete mode 100644 Dependencies/meshoptimizer/LICENSE.md delete mode 100644 Dependencies/meshoptimizer/Makefile delete mode 100644 Dependencies/meshoptimizer/README.md delete mode 100644 Dependencies/meshoptimizer/codecov.yml delete mode 100644 Dependencies/meshoptimizer/config.cmake.in delete mode 100644 Dependencies/meshoptimizer/demo/ansi.c delete mode 100644 Dependencies/meshoptimizer/demo/demo.html delete mode 100644 Dependencies/meshoptimizer/demo/index.html delete mode 100644 Dependencies/meshoptimizer/demo/main.cpp delete mode 100644 Dependencies/meshoptimizer/demo/pirate.glb delete mode 100644 Dependencies/meshoptimizer/demo/pirate.obj delete mode 100644 Dependencies/meshoptimizer/demo/tests.cpp delete mode 100644 Dependencies/meshoptimizer/extern/cgltf.h delete mode 100644 Dependencies/meshoptimizer/extern/fast_obj.h delete mode 100644 Dependencies/meshoptimizer/extern/sdefl.h delete mode 100644 Dependencies/meshoptimizer/gltf/README.md delete mode 100644 Dependencies/meshoptimizer/gltf/animation.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/basisenc.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/basislib.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/cli.js delete mode 100644 Dependencies/meshoptimizer/gltf/fileio.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/gltfpack.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/gltfpack.h delete mode 100644 Dependencies/meshoptimizer/gltf/image.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/json.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/library.js delete mode 100644 Dependencies/meshoptimizer/gltf/material.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/mesh.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/node.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/package.json delete mode 100644 Dependencies/meshoptimizer/gltf/parsegltf.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/parseobj.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/stream.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/wasistubs.cpp delete mode 100644 Dependencies/meshoptimizer/gltf/wasistubs.txt delete mode 100644 Dependencies/meshoptimizer/gltf/write.cpp delete mode 100644 Dependencies/meshoptimizer/js/README.md delete mode 100644 Dependencies/meshoptimizer/js/benchmark.js delete mode 100644 Dependencies/meshoptimizer/js/index.js delete mode 100644 Dependencies/meshoptimizer/js/index.module.d.ts delete mode 100644 Dependencies/meshoptimizer/js/index.module.js delete mode 100644 Dependencies/meshoptimizer/js/meshopt_decoder.js delete mode 100644 Dependencies/meshoptimizer/js/meshopt_decoder.module.d.ts delete mode 100644 Dependencies/meshoptimizer/js/meshopt_decoder.module.js delete mode 100644 Dependencies/meshoptimizer/js/meshopt_decoder.test.js delete mode 100644 Dependencies/meshoptimizer/js/meshopt_decoder_reference.js delete mode 100644 Dependencies/meshoptimizer/js/meshopt_encoder.js delete mode 100644 Dependencies/meshoptimizer/js/meshopt_encoder.module.d.ts delete mode 100644 Dependencies/meshoptimizer/js/meshopt_encoder.module.js delete mode 100644 Dependencies/meshoptimizer/js/meshopt_encoder.test.js delete mode 100644 Dependencies/meshoptimizer/js/package.json delete mode 100644 Dependencies/meshoptimizer/src/allocator.cpp delete mode 100644 Dependencies/meshoptimizer/src/clusterizer.cpp delete mode 100644 Dependencies/meshoptimizer/src/indexcodec.cpp delete mode 100644 Dependencies/meshoptimizer/src/indexgenerator.cpp delete mode 100644 Dependencies/meshoptimizer/src/meshoptimizer.h delete mode 100644 Dependencies/meshoptimizer/src/overdrawanalyzer.cpp delete mode 100644 Dependencies/meshoptimizer/src/overdrawoptimizer.cpp delete mode 100644 Dependencies/meshoptimizer/src/simplifier.cpp delete mode 100644 Dependencies/meshoptimizer/src/spatialorder.cpp delete mode 100644 Dependencies/meshoptimizer/src/stripifier.cpp delete mode 100644 Dependencies/meshoptimizer/src/vcacheanalyzer.cpp delete mode 100644 Dependencies/meshoptimizer/src/vcacheoptimizer.cpp delete mode 100644 Dependencies/meshoptimizer/src/vertexcodec.cpp delete mode 100644 Dependencies/meshoptimizer/src/vertexfilter.cpp delete mode 100644 Dependencies/meshoptimizer/src/vfetchanalyzer.cpp delete mode 100644 Dependencies/meshoptimizer/src/vfetchoptimizer.cpp delete mode 100644 Dependencies/meshoptimizer/tools/codecbench.cpp delete mode 100644 Dependencies/meshoptimizer/tools/codecfuzz.cpp delete mode 100644 Dependencies/meshoptimizer/tools/meshloader.cpp delete mode 100644 Dependencies/meshoptimizer/tools/vcachetester.cpp delete mode 100644 Dependencies/meshoptimizer/tools/vcachetuner.cpp delete mode 100644 Dependencies/meshoptimizer/tools/wasmpack.py delete mode 100644 Dependencies/meshoptimizer/tools/wasmstubs.cpp create mode 100644 Source/Game-Tests/Game-Tests.lua rename Source/{UnitTests/UnitTests => Game-Tests/Game-Tests}/Example.cpp (100%) create mode 100644 Source/Modules.lua delete mode 100644 Source/Projects.lua delete mode 100644 Source/UnitTests/UnitTests.lua delete mode 100644 premake5.exe diff --git a/Dependencies/Dependencies.lua b/Dependencies/Dependencies.lua index 4c33582b..14426253 100644 --- a/Dependencies/Dependencies.lua +++ b/Dependencies/Dependencies.lua @@ -1,10 +1,7 @@ -- Dependencies -Game.dependencyDir = path.getabsolute("Dependencies/", Game.rootDir) - -print("-- Creating Dependencies --") - -Game.dependencyGroup = (Game.name .. "/Dependencies") -group (Game.dependencyGroup) +Solution.Util.Print("-- Creating Dependencies --") +Solution.Util.ClearFilter() +Solution.Util.SetGroup(Solution.DependencyGroup) local dependencies = { @@ -12,11 +9,9 @@ local dependencies = } for k,v in pairs(dependencies) do - filter { } include(v) + Solution.Util.ClearFilter() end -filter { } -group (Game.name) - -print("-- Finished with Dependencies --\n") \ No newline at end of file +Solution.Util.SetGroup("") +Solution.Util.Print("-- Finished with Dependencies --\n") \ No newline at end of file diff --git a/Dependencies/Jolt/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h b/Dependencies/Jolt/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h index 70fabc81..e5376ea8 100644 --- a/Dependencies/Jolt/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h +++ b/Dependencies/Jolt/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h @@ -145,7 +145,7 @@ class NodeCodecQuadTreeHalfFloat uint offset = node->mNodeProperties[i] != 0? inChildrenTrianglesStart[i] : inChildrenNodeStart[i]; if (offset & OFFSET_NON_SIGNIFICANT_MASK) { - outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-signifiant bits set"; + outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set"; return false; } offset >>= OFFSET_NON_SIGNIFICANT_BITS; @@ -168,7 +168,7 @@ class NodeCodecQuadTreeHalfFloat uint offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart; if (offset & OFFSET_NON_SIGNIFICANT_MASK) { - outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-signifiant bits set"; + outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set"; return false; } offset >>= OFFSET_NON_SIGNIFICANT_BITS; diff --git a/Dependencies/Jolt/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h b/Dependencies/Jolt/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h index 94b80b44..7d297218 100644 --- a/Dependencies/Jolt/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h +++ b/Dependencies/Jolt/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h @@ -88,6 +88,36 @@ class TriangleCodecIndexed8BitPackSOA4Flags static_assert(sizeof(TriangleBlockHeader) == 4, "Compiler added padding"); + /// This class is used to validate that the triangle data will not be degenerate after compression + class ValidationContext + { + public: + /// Constructor + ValidationContext(const IndexedTriangleList &inTriangles, const VertexList &inVertices) : + mVertices(inVertices) + { + // Only used the referenced triangles, just like EncodingContext::Finalize does + for (const IndexedTriangle &i : inTriangles) + for (uint32 idx : i.mIdx) + mBounds.Encapsulate(Vec3(inVertices[idx])); + } + + /// Test if a triangle will be degenerate after quantization + bool IsDegenerate(const IndexedTriangle &inTriangle) const + { + // Quantize the triangle in the same way as EncodingContext::Finalize does + UVec4 quantized_vertex[3]; + Vec3 compress_scale = Vec3::sReplicate(COMPONENT_MASK) / Vec3::sMax(mBounds.GetSize(), Vec3::sReplicate(1.0e-20f)); + for (int i = 0; i < 3; ++i) + quantized_vertex[i] = ((Vec3(mVertices[inTriangle.mIdx[i]]) - mBounds.mMin) * compress_scale + Vec3::sReplicate(0.5f)).ToInt(); + return quantized_vertex[0] == quantized_vertex[1] || quantized_vertex[1] == quantized_vertex[2] || quantized_vertex[0] == quantized_vertex[2]; + } + + private: + const VertexList & mVertices; + AABox mBounds; + }; + /// This class is used to encode and compress triangle data into a byte buffer class EncodingContext { @@ -406,7 +436,7 @@ class TriangleCodecIndexed8BitPackSOA4Flags return first_block[inTriangleIndex >> 2].mFlags[inTriangleIndex & 0b11]; } - /// Unpacks triangles and flags, convencience function + /// Unpacks triangles and flags, convenience function JPH_INLINE void Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles, uint8 *outTriangleFlags) const { Unpack(inTriangleStart, inNumTriangles, outTriangles); diff --git a/Dependencies/Jolt/Jolt/Core/Color.h b/Dependencies/Jolt/Jolt/Core/Color.h index f7e7b586..b979fb4f 100644 --- a/Dependencies/Jolt/Jolt/Core/Color.h +++ b/Dependencies/Jolt/Jolt/Core/Color.h @@ -34,6 +34,9 @@ class [[nodiscard]] JPH_EXPORT_GCC_BUG_WORKAROUND Color inline uint8 operator () (uint inIdx) const { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; } inline uint8 & operator () (uint inIdx) { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; } + /// Multiply two colors + inline Color operator * (const Color &inRHS) const { return Color(uint8((uint32(r) * inRHS.r) >> 8), uint8((uint32(g) * inRHS.g) >> 8), uint8((uint32(b) * inRHS.b) >> 8), uint8((uint32(a) * inRHS.a) >> 8)); } + /// Convert to Vec4 with range [0, 1] inline Vec4 ToVec4() const { return Vec4(r, g, b, a) / 255.0f; } diff --git a/Dependencies/Jolt/Jolt/Core/Core.h b/Dependencies/Jolt/Jolt/Core/Core.h index b5a251a9..bd26ef11 100644 --- a/Dependencies/Jolt/Jolt/Core/Core.h +++ b/Dependencies/Jolt/Jolt/Core/Core.h @@ -5,9 +5,9 @@ #pragma once // Jolt library version -#define JPH_VERSION_MAJOR 4 +#define JPH_VERSION_MAJOR 5 #define JPH_VERSION_MINOR 0 -#define JPH_VERSION_PATCH 2 +#define JPH_VERSION_PATCH 0 // Determine which features the library was compiled with #ifdef JPH_DOUBLE_PRECISION @@ -78,6 +78,8 @@ #define JPH_PLATFORM_ANDROID #elif defined(__linux__) #define JPH_PLATFORM_LINUX +#elif defined(__FreeBSD__) + #define JPH_PLATFORM_FREEBSD #elif defined(__APPLE__) #include #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE @@ -179,7 +181,18 @@ #define JPH_CPU_ADDRESS_BITS 32 #define JPH_VECTOR_ALIGNMENT 16 #define JPH_DVECTOR_ALIGNMENT 32 - #define JPH_DISABLE_CUSTOM_ALLOCATOR + #ifdef __wasm_simd128__ + #define JPH_USE_SSE + #define JPH_USE_SSE4_1 + #define JPH_USE_SSE4_2 + #endif +#elif defined(__e2k__) + // Elbrus e2k architecture + #define JPH_CPU_E2K + #define JPH_CPU_ADDRESS_BITS 64 + #define JPH_USE_SSE + #define JPH_VECTOR_ALIGNMENT 16 + #define JPH_DVECTOR_ALIGNMENT 32 #else #error Unsupported CPU architecture #endif @@ -294,6 +307,7 @@ JPH_GCC_SUPPRESS_WARNING("-Wcomment") \ JPH_GCC_SUPPRESS_WARNING("-Winvalid-offsetof") \ JPH_GCC_SUPPRESS_WARNING("-Wclass-memaccess") \ + JPH_GCC_SUPPRESS_WARNING("-Wpedantic") \ \ JPH_MSVC_SUPPRESS_WARNING(4619) /* #pragma warning: there is no warning number 'XXXX' */ \ JPH_MSVC_SUPPRESS_WARNING(4514) /* 'X' : unreferenced inline function has been removed */ \ @@ -329,11 +343,13 @@ // Creating one should only be a couple of minutes of work if you have the documentation for the platform // (you only need to define JPH_BREAKPOINT, JPH_PLATFORM_BLUE_GET_TICKS, JPH_PLATFORM_BLUE_MUTEX*, JPH_PLATFORM_BLUE_RWLOCK* and include the right header). #include -#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS) +#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS) || defined(JPH_PLATFORM_FREEBSD) #if defined(JPH_CPU_X86) #define JPH_BREAKPOINT __asm volatile ("int $0x3") #elif defined(JPH_CPU_ARM) #define JPH_BREAKPOINT __builtin_trap() + #elif defined(JPH_CPU_E2K) + #define JPH_BREAKPOINT __builtin_trap() #endif #elif defined(JPH_PLATFORM_WASM) #define JPH_BREAKPOINT do { } while (false) // Not supported @@ -341,9 +357,6 @@ #error Unknown platform #endif -// Crashes the application -#define JPH_CRASH do { int *ptr = nullptr; *ptr = 0; } while (false) - // Begin the JPH namespace #define JPH_NAMESPACE_BEGIN \ JPH_SUPPRESS_WARNING_PUSH \ diff --git a/Dependencies/Jolt/Jolt/Core/FPControlWord.h b/Dependencies/Jolt/Jolt/Core/FPControlWord.h index bde14b31..a046bffe 100644 --- a/Dependencies/Jolt/Jolt/Core/FPControlWord.h +++ b/Dependencies/Jolt/Jolt/Core/FPControlWord.h @@ -8,7 +8,11 @@ JPH_NAMESPACE_BEGIN -#ifdef JPH_USE_SSE +#if defined(JPH_CPU_WASM) + +// Not supported + +#elif defined(JPH_USE_SSE) /// Helper class that needs to be put on the stack to update the state of the floating point control word. /// This state is kept per thread. @@ -122,10 +126,6 @@ class FPControlWord : public NonCopyable uint32 mPrevState; }; -#elif defined(JPH_CPU_WASM) - -// Not supported - #else #error Unsupported CPU architecture diff --git a/Dependencies/Jolt/Jolt/Core/FPException.h b/Dependencies/Jolt/Jolt/Core/FPException.h index 0c5980cc..3083f05c 100644 --- a/Dependencies/Jolt/Jolt/Core/FPException.h +++ b/Dependencies/Jolt/Jolt/Core/FPException.h @@ -10,7 +10,14 @@ JPH_NAMESPACE_BEGIN #ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED -#if defined(JPH_USE_SSE) +#if defined(JPH_CPU_WASM) + +// Not supported +class FPExceptionsEnable { }; +class FPExceptionDisableInvalid { }; +class FPExceptionDisableDivByZero { }; + +#elif defined(JPH_USE_SSE) /// Enable floating point divide by zero exception and exceptions on invalid numbers class FPExceptionsEnable : public FPControlWord<0, _MM_MASK_DIV_ZERO | _MM_MASK_INVALID> { }; @@ -49,13 +56,6 @@ class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { }; /// Disable division by zero floating point exceptions class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { }; -#elif defined(JPH_CPU_WASM) - -// Not supported -class FPExceptionsEnable { }; -class FPExceptionDisableInvalid { }; -class FPExceptionDisableDivByZero { }; - #else #error Unsupported CPU architecture diff --git a/Dependencies/Jolt/Jolt/Core/FPFlushDenormals.h b/Dependencies/Jolt/Jolt/Core/FPFlushDenormals.h index 12e6c365..672a19db 100644 --- a/Dependencies/Jolt/Jolt/Core/FPFlushDenormals.h +++ b/Dependencies/Jolt/Jolt/Core/FPFlushDenormals.h @@ -8,7 +8,12 @@ JPH_NAMESPACE_BEGIN -#if defined(JPH_USE_SSE) +#if defined(JPH_CPU_WASM) + +// Not supported +class FPFlushDenormals { }; + +#elif defined(JPH_USE_SSE) /// Helper class that needs to be put on the stack to enable flushing denormals to zero /// This can make floating point operations much faster when working with very small numbers @@ -27,11 +32,6 @@ static constexpr uint64 FP_FZ = 1 << 24; /// This can make floating point operations much faster when working with very small numbers class FPFlushDenormals : public FPControlWord { }; -#elif defined(JPH_CPU_WASM) - -// Not supported -class FPFlushDenormals { }; - #else #error Unsupported CPU architecture diff --git a/Dependencies/Jolt/Jolt/Core/Factory.h b/Dependencies/Jolt/Jolt/Core/Factory.h index 4d6fd7a2..557f3817 100644 --- a/Dependencies/Jolt/Jolt/Core/Factory.h +++ b/Dependencies/Jolt/Jolt/Core/Factory.h @@ -9,7 +9,7 @@ JPH_NAMESPACE_BEGIN -/// Factory, to create RTTI objects +/// This class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data. class JPH_EXPORT Factory { public: diff --git a/Dependencies/Jolt/Jolt/Core/JobSystem.h b/Dependencies/Jolt/Jolt/Core/JobSystem.h index d8719068..f12f5380 100644 --- a/Dependencies/Jolt/Jolt/Core/JobSystem.h +++ b/Dependencies/Jolt/Jolt/Core/JobSystem.h @@ -198,7 +198,7 @@ class JPH_EXPORT JobSystem : public NonCopyable // Releasing a reference must use release semantics... if (mReferenceCount.fetch_sub(1, memory_order_release) == 1) { - // ... so that we can use aquire to ensure that we see any updates from other threads that released a ref before freeing the job + // ... so that we can use acquire to ensure that we see any updates from other threads that released a ref before freeing the job atomic_thread_fence(memory_order_acquire); mJobSystem->FreeJob(this); } diff --git a/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.cpp b/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.cpp index df2cb8dd..64beb5b7 100644 --- a/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.cpp +++ b/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.cpp @@ -20,6 +20,9 @@ JPH_SUPPRESS_WARNING_POP #endif +#ifdef JPH_PLATFORM_LINUX + #include +#endif JPH_NAMESPACE_BEGIN @@ -230,39 +233,74 @@ void JobSystemThreadPool::QueueJobs(Job **inJobs, uint inNumJobs) mSemaphore.Release(min(inNumJobs, (uint)mThreads.size())); } -#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW) // MinGW doesn't support __try/__except - -// Sets the current thread name in MSVC debugger -static void SetThreadName(const char *inName) -{ - #pragma pack(push, 8) +#if defined(JPH_PLATFORM_WINDOWS) - struct THREADNAME_INFO +#if !defined(JPH_COMPILER_MINGW) // MinGW doesn't support __try/__except) + // Sets the current thread name in MSVC debugger + static void RaiseThreadNameException(const char *inName) { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - }; + #pragma pack(push, 8) + + struct THREADNAME_INFO + { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + }; + + #pragma pack(pop) - #pragma pack(pop) + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = inName; + info.dwThreadID = (DWORD)-1; + info.dwFlags = 0; - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = inName; - info.dwThreadID = (DWORD)-1; - info.dwFlags = 0; + __try + { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } + } +#endif // !JPH_COMPILER_MINGW - __try + static void SetThreadName(const char* inName) { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + JPH_SUPPRESS_WARNING_PUSH + + // Suppress casting warning, it's fine here as GetProcAddress doesn't really return a FARPROC + JPH_CLANG_SUPPRESS_WARNING("-Wcast-function-type") // error : cast from 'FARPROC' (aka 'long long (*)()') to 'SetThreadDescriptionFunc' (aka 'long (*)(void *, const wchar_t *)') converts to incompatible function type + JPH_CLANG_SUPPRESS_WARNING("-Wcast-function-type-strict") // error : cast from 'FARPROC' (aka 'long long (*)()') to 'SetThreadDescriptionFunc' (aka 'long (*)(void *, const wchar_t *)') converts to incompatible function type + JPH_MSVC_SUPPRESS_WARNING(4191) // reinterpret_cast' : unsafe conversion from 'FARPROC' to 'SetThreadDescriptionFunc'. Calling this function through the result pointer may cause your program to fail + + using SetThreadDescriptionFunc = HRESULT(WINAPI*)(HANDLE hThread, PCWSTR lpThreadDescription); + static SetThreadDescriptionFunc SetThreadDescription = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"Kernel32.dll"), "SetThreadDescription")); + + JPH_SUPPRESS_WARNING_POP + + if (SetThreadDescription) + { + wchar_t name_buffer[64] = { 0 }; + if (MultiByteToWideChar(CP_UTF8, 0, inName, -1, name_buffer, sizeof(name_buffer) / sizeof(wchar_t) - 1) == 0) + return; + + SetThreadDescription(GetCurrentThread(), name_buffer); + } +#if !defined(JPH_COMPILER_MINGW) + else if (IsDebuggerPresent()) + RaiseThreadNameException(inName); +#endif // !JPH_COMPILER_MINGW } - __except(EXCEPTION_EXECUTE_HANDLER) +#elif defined(JPH_PLATFORM_LINUX) + static void SetThreadName(const char *inName) { + JPH_ASSERT(strlen(inName) < 16); // String will be truncated if it is longer + prctl(PR_SET_NAME, inName, 0, 0, 0); } -} - -#endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW +#endif // JPH_PLATFORM_LINUX void JobSystemThreadPool::ThreadMain(int inThreadIndex) { @@ -270,7 +308,7 @@ void JobSystemThreadPool::ThreadMain(int inThreadIndex) char name[64]; snprintf(name, sizeof(name), "Worker %d", int(inThreadIndex + 1)); -#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW) +#if defined(JPH_PLATFORM_WINDOWS) || defined(JPH_PLATFORM_LINUX) SetThreadName(name); #endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW diff --git a/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.h b/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.h index 686f74fa..05e5ae9b 100644 --- a/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.h +++ b/Dependencies/Jolt/Jolt/Core/JobSystemThreadPool.h @@ -36,7 +36,7 @@ class JPH_EXPORT JobSystemThreadPool final : public JobSystemWithBarrier /// Initialize the thread pool /// @param inMaxJobs Max number of jobs that can be allocated at any time /// @param inMaxBarriers Max number of barriers that can be allocated at any time - /// @param inNumThreads Number of threads to start (the number of concurrent jobs is 1 more because the main thread will also run jobs while waiting for a barrier to complete). Use -1 to autodetect the amount of CPU's. + /// @param inNumThreads Number of threads to start (the number of concurrent jobs is 1 more because the main thread will also run jobs while waiting for a barrier to complete). Use -1 to auto detect the amount of CPU's. void Init(uint inMaxJobs, uint inMaxBarriers, int inNumThreads = -1); // See JobSystem diff --git a/Dependencies/Jolt/Jolt/Core/LockFreeHashMap.h b/Dependencies/Jolt/Jolt/Core/LockFreeHashMap.h index 3fa4539c..9debf9f9 100644 --- a/Dependencies/Jolt/Jolt/Core/LockFreeHashMap.h +++ b/Dependencies/Jolt/Jolt/Core/LockFreeHashMap.h @@ -38,7 +38,7 @@ class LFHMAllocator : public NonCopyable inline T * FromOffset(uint32 inOffset) const; private: - uint8 * mObjectStore = nullptr; ///< This contains a contigous list of objects (possibly of varying size) + uint8 * mObjectStore = nullptr; ///< This contains a contiguous list of objects (possibly of varying size) uint32 mObjectStoreSizeBytes = 0; ///< The size of mObjectStore in bytes atomic mWriteOffset { 0 }; ///< Next offset to write to in mObjectStore }; diff --git a/Dependencies/Jolt/Jolt/Core/Memory.cpp b/Dependencies/Jolt/Jolt/Core/Memory.cpp index 8eb1d686..b259313e 100644 --- a/Dependencies/Jolt/Jolt/Core/Memory.cpp +++ b/Dependencies/Jolt/Jolt/Core/Memory.cpp @@ -38,6 +38,7 @@ JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(AlignedAllocate)(size_t inSize, size_t inAlig void *block = nullptr; JPH_SUPPRESS_WARNING_PUSH JPH_GCC_SUPPRESS_WARNING("-Wunused-result") + JPH_CLANG_SUPPRESS_WARNING("-Wunused-result") posix_memalign(&block, inAlignment, inSize); JPH_SUPPRESS_WARNING_POP return block; diff --git a/Dependencies/Jolt/Jolt/Core/Profiler.h b/Dependencies/Jolt/Jolt/Core/Profiler.h index 4254c562..bbe64182 100644 --- a/Dependencies/Jolt/Jolt/Core/Profiler.h +++ b/Dependencies/Jolt/Jolt/Core/Profiler.h @@ -150,7 +150,7 @@ class JPH_EXPORT Profiler : public NonCopyable /// We measure the amount of ticks per second, this function resets the reference time point void UpdateReferenceTime(); - /// Get the amount of ticks per second, note that this number will never be fully accurate as the amound of ticks per second may vary with CPU load, so this number is only to be used to give an indication of time for profiling purposes + /// Get the amount of ticks per second, note that this number will never be fully accurate as the amount of ticks per second may vary with CPU load, so this number is only to be used to give an indication of time for profiling purposes uint64 GetProcessorTicksPerSecond() const; /// Dump profiling statistics diff --git a/Dependencies/Jolt/Jolt/Core/Reference.h b/Dependencies/Jolt/Jolt/Core/Reference.h index 481cf795..d6fdf1da 100644 --- a/Dependencies/Jolt/Jolt/Core/Reference.h +++ b/Dependencies/Jolt/Jolt/Core/Reference.h @@ -27,7 +27,7 @@ template class RefConst; /// some responsibility to the programmer. The most notable point is that you cannot /// have one object reference another and have the other reference the first one /// back, because this way the reference count of both objects will never become -/// lower than 1, resulting in a memory leak. By carefully designing your classses +/// lower than 1, resulting in a memory leak. By carefully designing your classes /// (and particularly identifying who owns who in the class hierarchy) you can avoid /// these problems. template @@ -62,7 +62,7 @@ class RefTarget // Releasing a reference must use release semantics... if (mRefCount.fetch_sub(1, memory_order_release) == 1) { - // ... so that we can use aquire to ensure that we see any updates from other threads that released a ref before deleting the object + // ... so that we can use acquire to ensure that we see any updates from other threads that released a ref before deleting the object atomic_thread_fence(memory_order_acquire); delete static_cast(this); } diff --git a/Dependencies/Jolt/Jolt/Core/Semaphore.h b/Dependencies/Jolt/Jolt/Core/Semaphore.h index 85367f23..498b1b8d 100644 --- a/Dependencies/Jolt/Jolt/Core/Semaphore.h +++ b/Dependencies/Jolt/Jolt/Core/Semaphore.h @@ -26,7 +26,7 @@ class JPH_EXPORT Semaphore Semaphore(); ~Semaphore(); - /// Release the semaphore, signalling the thread waiting on the barrier that there may be work + /// Release the semaphore, signaling the thread waiting on the barrier that there may be work void Release(uint inNumber = 1); /// Acquire the semaphore inNumber times diff --git a/Dependencies/Jolt/Jolt/Core/StreamIn.h b/Dependencies/Jolt/Jolt/Core/StreamIn.h index 927d936b..0820d0fb 100644 --- a/Dependencies/Jolt/Jolt/Core/StreamIn.h +++ b/Dependencies/Jolt/Jolt/Core/StreamIn.h @@ -62,6 +62,22 @@ class JPH_EXPORT StreamIn : public NonCopyable outString.clear(); } + /// Read a vector of primitives from the binary stream using a custom function to read the elements + template + void Read(std::vector &outT, const F &inReadElement) + { + typename Array::size_type len = outT.size(); // Initialize to previous array size, this is used for validation in the StateRecorder class + Read(len); + if (!IsEOF() && !IsFailed()) + { + outT.resize(len); + for (typename Array::size_type i = 0; i < len; ++i) + inReadElement(*this, outT[i]); + } + else + outT.clear(); + } + /// Read a Vec3 (don't read W) void Read(Vec3 &outVec) { diff --git a/Dependencies/Jolt/Jolt/Core/StreamOut.h b/Dependencies/Jolt/Jolt/Core/StreamOut.h index 219e1cf7..ac817ff0 100644 --- a/Dependencies/Jolt/Jolt/Core/StreamOut.h +++ b/Dependencies/Jolt/Jolt/Core/StreamOut.h @@ -28,7 +28,7 @@ class JPH_EXPORT StreamOut : public NonCopyable WriteBytes(&inT, sizeof(inT)); } - /// Write a vector of primitives from the binary stream + /// Write a vector of primitives to the binary stream template void Write(const std::vector &inT) { @@ -49,6 +49,17 @@ class JPH_EXPORT StreamOut : public NonCopyable WriteBytes(inString.data(), len * sizeof(Type)); } + /// Write a vector of primitives to the binary stream using a custom write function + template + void Write(const std::vector &inT, const F &inWriteElement) + { + typename Array::size_type len = inT.size(); + Write(len); + if (!IsFailed()) + for (typename Array::size_type i = 0; i < len; ++i) + inWriteElement(inT[i], *this); + } + /// Write a Vec3 (don't write W) void Write(const Vec3 &inVec) { diff --git a/Dependencies/Jolt/Jolt/Core/TempAllocator.h b/Dependencies/Jolt/Jolt/Core/TempAllocator.h index 6b3ef8cf..5d228752 100644 --- a/Dependencies/Jolt/Jolt/Core/TempAllocator.h +++ b/Dependencies/Jolt/Jolt/Core/TempAllocator.h @@ -58,7 +58,10 @@ class JPH_EXPORT TempAllocatorImpl final : public TempAllocator { uint new_top = mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT); if (new_top > mSize) - JPH_CRASH; // Out of memory + { + Trace("TempAllocator: Out of memory"); + std::abort(); + } void *address = mBase + mTop; mTop = new_top; return address; @@ -76,7 +79,10 @@ class JPH_EXPORT TempAllocatorImpl final : public TempAllocator { mTop -= AlignUp(inSize, JPH_RVECTOR_ALIGNMENT); if (mBase + mTop != inAddress) - JPH_CRASH; // Freeing in the wrong order + { + Trace("TempAllocator: Freeing in the wrong order"); + std::abort(); + } } } diff --git a/Dependencies/Jolt/Jolt/Core/TickCounter.h b/Dependencies/Jolt/Jolt/Core/TickCounter.h index 5d42eb40..2b5410e3 100644 --- a/Dependencies/Jolt/Jolt/Core/TickCounter.h +++ b/Dependencies/Jolt/Jolt/Core/TickCounter.h @@ -9,6 +9,8 @@ #include #elif defined(JPH_CPU_X86) && defined(JPH_COMPILER_GCC) #include +#elif defined(JPH_CPU_E2K) + #include #endif JPH_NAMESPACE_BEGIN @@ -27,6 +29,8 @@ JPH_INLINE uint64 GetProcessorTickCount() return JPH_PLATFORM_BLUE_GET_TICKS(); #elif defined(JPH_CPU_X86) return __rdtsc(); +#elif defined(JPH_CPU_E2K) + return __rdtsc(); #elif defined(JPH_CPU_ARM) && defined(JPH_USE_NEON) uint64 val; asm volatile("mrs %0, cntvct_el0" : "=r" (val)); diff --git a/Dependencies/Jolt/Jolt/Geometry/AABox4.h b/Dependencies/Jolt/Jolt/Geometry/AABox4.h index b461fab4..4465d4da 100644 --- a/Dependencies/Jolt/Jolt/Geometry/AABox4.h +++ b/Dependencies/Jolt/Jolt/Geometry/AABox4.h @@ -189,16 +189,29 @@ JPH_INLINE UVec4 AABox4VsBox(const OrientedBox &inBox, Vec4Arg inBoxMinX, Vec4Ar return AABox4VsBox(inBox.mOrientation, inBox.mHalfExtents, inBoxMinX, inBoxMinY, inBoxMinZ, inBoxMaxX, inBoxMaxY, inBoxMaxZ, inEpsilon); } -/// Test 4 AABoxes vs a sphere -JPH_INLINE UVec4 AABox4VsSphere(Vec4Arg inCenterX, Vec4Arg inCenterY, Vec4Arg inCenterZ, Vec4Arg inRadiusSq, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ) +/// Get the squared distance between 4 AABoxes and a point +JPH_INLINE Vec4 AABox4DistanceSqToPoint(Vec4Arg inPointX, Vec4Arg inPointY, Vec4Arg inPointZ, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ) { // Get closest point on box - Vec4 closest_x = Vec4::sMin(Vec4::sMax(inCenterX, inBoxMinX), inBoxMaxX); - Vec4 closest_y = Vec4::sMin(Vec4::sMax(inCenterY, inBoxMinY), inBoxMaxY); - Vec4 closest_z = Vec4::sMin(Vec4::sMax(inCenterZ, inBoxMinZ), inBoxMaxZ); + Vec4 closest_x = Vec4::sMin(Vec4::sMax(inPointX, inBoxMinX), inBoxMaxX); + Vec4 closest_y = Vec4::sMin(Vec4::sMax(inPointY, inBoxMinY), inBoxMaxY); + Vec4 closest_z = Vec4::sMin(Vec4::sMax(inPointZ, inBoxMinZ), inBoxMaxZ); + + // Return the squared distance between the box and point + return Square(closest_x - inPointX) + Square(closest_y - inPointY) + Square(closest_z - inPointZ); +} + +/// Get the squared distance between 4 AABoxes and a point +JPH_INLINE Vec4 AABox4DistanceSqToPoint(Vec3 inPoint, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ) +{ + return AABox4DistanceSqToPoint(inPoint.SplatX(), inPoint.SplatY(), inPoint.SplatZ(), inBoxMinX, inBoxMinY, inBoxMinZ, inBoxMaxX, inBoxMaxY, inBoxMaxZ); +} +/// Test 4 AABoxes vs a sphere +JPH_INLINE UVec4 AABox4VsSphere(Vec4Arg inCenterX, Vec4Arg inCenterY, Vec4Arg inCenterZ, Vec4Arg inRadiusSq, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ) +{ // Test the distance from the center of the sphere to the box is smaller than the radius - Vec4 distance_sq = Square(closest_x - inCenterX) + Square(closest_y - inCenterY) + Square(closest_z - inCenterZ); + Vec4 distance_sq = AABox4DistanceSqToPoint(inCenterX, inCenterY, inCenterZ, inBoxMinX, inBoxMinY, inBoxMinZ, inBoxMaxX, inBoxMaxY, inBoxMaxZ); return Vec4::sLessOrEqual(distance_sq, inRadiusSq); } diff --git a/Dependencies/Jolt/Jolt/Geometry/ClosestPoint.h b/Dependencies/Jolt/Jolt/Geometry/ClosestPoint.h index 8c646bc3..a437763f 100644 --- a/Dependencies/Jolt/Jolt/Geometry/ClosestPoint.h +++ b/Dependencies/Jolt/Jolt/Geometry/ClosestPoint.h @@ -14,7 +14,8 @@ namespace ClosestPoint { /// Compute barycentric coordinates of closest point to origin for infinite line defined by (inA, inB) /// Point can then be computed as inA * outU + inB * outV - inline void GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, float &outU, float &outV) + /// Returns false if the points inA, inB do not form a line (are at the same point) + inline bool GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, float &outU, float &outV) { Vec3 ab = inB - inA; float denominator = ab.LengthSq(); @@ -33,17 +34,20 @@ namespace ClosestPoint outU = 0.0f; outV = 1.0f; } + return false; } else { outV = -inA.Dot(ab) / denominator; outU = 1.0f - outV; } + return true; } /// Compute barycentric coordinates of closest point to origin for plane defined by (inA, inB, inC) /// Point can then be computed as inA * outU + inB * outV + inC * outW - inline void GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, float &outU, float &outV, float &outW) + /// Returns false if the points inA, inB, inC do not form a plane (are on the same line or at the same point) + inline bool GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, float &outU, float &outV, float &outW) { // Taken from: Real-Time Collision Detection - Christer Ericson (Section: Barycentric Coordinates) // With p = 0 @@ -55,16 +59,18 @@ namespace ClosestPoint Vec3 v2 = inC - inB; // Make sure that the shortest edge is included in the calculation to keep the products a * b - c * d as small as possible to preserve accuracy - float d00 = v0.Dot(v0); - float d11 = v1.Dot(v1); - float d22 = v2.Dot(v2); + float d00 = v0.LengthSq(); + float d11 = v1.LengthSq(); + float d22 = v2.LengthSq(); if (d00 <= d22) { // Use v0 and v1 to calculate barycentric coordinates float d01 = v0.Dot(v1); + // Denominator must be positive: + // |v0|^2 * |v1|^2 - (v0 . v1)^2 = |v0|^2 * |v1|^2 * (1 - cos(angle)^2) >= 0 float denominator = d00 * d11 - d01 * d01; - if (abs(denominator) < 1.0e-12f) + if (denominator < 1.0e-12f) { // Degenerate triangle, return coordinates along longest edge if (d00 > d11) @@ -77,6 +83,7 @@ namespace ClosestPoint GetBaryCentricCoordinates(inA, inC, outU, outW); outV = 0.0f; } + return false; } else { @@ -93,7 +100,7 @@ namespace ClosestPoint float d12 = v1.Dot(v2); float denominator = d11 * d22 - d12 * d12; - if (abs(denominator) < 1.0e-12f) + if (denominator < 1.0e-12f) { // Degenerate triangle, return coordinates along longest edge if (d11 > d22) @@ -106,6 +113,7 @@ namespace ClosestPoint GetBaryCentricCoordinates(inB, inC, outV, outW); outU = 0.0f; } + return false; } else { @@ -116,6 +124,7 @@ namespace ClosestPoint outW = 1.0f - outU - outV; } } + return true; } /// Get the closest point to the origin of line (inA, inB) @@ -174,7 +183,7 @@ namespace ClosestPoint float n_len_sq = n.LengthSq(); // Check degenerate - if (n_len_sq < 1.0e-11f) // Square(FLT_EPSILON) was too small and caused numerical problems, see test case TestCollideParallelTriangleVsCapsule + if (n_len_sq < 1.0e-10f) // Square(FLT_EPSILON) was too small and caused numerical problems, see test case TestCollideParallelTriangleVsCapsule { // Degenerate, fallback to vertices and edges diff --git a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.cpp b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.cpp index 298eada9..16ccf89c 100644 --- a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.cpp +++ b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.cpp @@ -218,7 +218,7 @@ bool ConvexHullBuilder::AssignPointToFace(int inPositionIdx, const Faces &inFace // This point is in front of the face, add it to the conflict list if (best_dist_sq > best_face->mFurthestPointDistanceSq) { - // This point is futher away than any others, update the distance and add point as last point + // This point is further away than any others, update the distance and add point as last point best_face->mFurthestPointDistanceSq = best_dist_sq; best_face->mConflictList.push_back(inPositionIdx); } diff --git a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.h b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.h index 6c73b0b8..7a6aea8c 100644 --- a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.h +++ b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder.h @@ -75,7 +75,7 @@ class JPH_EXPORT ConvexHullBuilder : public NonCopyable Vec3 mCentroid; ///< Center of the face ConflictList mConflictList; ///< Positions associated with this edge (that are closest to this edge). The last position in the list is the point that is furthest away from the face. Edge * mFirstEdge = nullptr; ///< First edge of this face - float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furtest point from the conflict list to the face + float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furthest point from the conflict list to the face bool mRemoved = false; ///< Flag that indicates that face has been removed (face will be freed later) #ifdef JPH_CONVEX_BUILDER_DEBUG int mIteration; ///< Iteration that this face was created @@ -144,7 +144,7 @@ class JPH_EXPORT ConvexHullBuilder : public NonCopyable public: Edge * mNeighbourEdge; ///< Edge that this edge is connected to int mStartIdx; ///< Vertex index in mPositions that indicates the start vertex of this edge - int mEndIdx; ///< Vertex index in mPosition that indicats the end vertex of this edge + int mEndIdx; ///< Vertex index in mPosition that indicates the end vertex of this edge }; // Private typedefs diff --git a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.cpp b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.cpp index 7973b553..968e6b46 100644 --- a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.cpp +++ b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.cpp @@ -135,7 +135,7 @@ void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array best_edge->mFurthestPointDistanceSq) { - // This point is futher away than any others, update the distance and add point as last point + // This point is further away than any others, update the distance and add point as last point best_edge->mFurthestPointDistanceSq = best_dist_sq; best_edge->mConflictList.push_back(inPositionIdx); } diff --git a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.h b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.h index da5ea600..ff06a340 100644 --- a/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.h +++ b/Dependencies/Jolt/Jolt/Geometry/ConvexHullBuilder2D.h @@ -86,10 +86,10 @@ class JPH_EXPORT ConvexHullBuilder2D : public NonCopyable Vec3 mNormal; ///< Normal of the edge (not normalized) Vec3 mCenter; ///< Center of the edge ConflictList mConflictList; ///< Positions associated with this edge (that are closest to this edge). Last entry is the one furthest away from the edge, remainder is unsorted. - Edge * mPrevEdge = nullptr; ///< Previous edge in cicular list + Edge * mPrevEdge = nullptr; ///< Previous edge in circular list Edge * mNextEdge = nullptr; ///< Next edge in circular list int mStartIdx; ///< Position index of start of this edge - float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furtest point from the conflict list to the edge + float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furthest point from the conflict list to the edge }; const Positions & mPositions; ///< List of positions (some of them are part of the hull) diff --git a/Dependencies/Jolt/Jolt/Geometry/EPAConvexHullBuilder.h b/Dependencies/Jolt/Jolt/Geometry/EPAConvexHullBuilder.h index 885df041..9f1c5a8e 100644 --- a/Dependencies/Jolt/Jolt/Geometry/EPAConvexHullBuilder.h +++ b/Dependencies/Jolt/Jolt/Geometry/EPAConvexHullBuilder.h @@ -567,7 +567,7 @@ class EPAConvexHullBuilder : public NonCopyable { if (inT->mRemoved) { - // Valdiate that removed triangles are not connected to anything + // Validate that removed triangles are not connected to anything for (const Edge &my_edge : inT->mEdge) JPH_ASSERT(my_edge.mNeighbourTriangle == nullptr); } diff --git a/Dependencies/Jolt/Jolt/Geometry/EPAPenetrationDepth.h b/Dependencies/Jolt/Jolt/Geometry/EPAPenetrationDepth.h index eff1522d..ba7b18f7 100644 --- a/Dependencies/Jolt/Jolt/Geometry/EPAPenetrationDepth.h +++ b/Dependencies/Jolt/Jolt/Geometry/EPAPenetrationDepth.h @@ -494,7 +494,7 @@ class EPAPenetrationDepth return false; } - /// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> instersects inB + /// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB /// /// @param inStart Start position and orientation of the convex object /// @param inDirection Direction of the sweep (ioLambda * inDirection determines length) @@ -504,7 +504,7 @@ class EPAPenetrationDepth /// @param inB The convex object B, must support the GetSupport(Vec3) function. /// @param inConvexRadiusA The convex radius of A, this will be added on all sides to pad A. /// @param inConvexRadiusB The convex radius of B, this will be added on all sides to pad B. - /// @param inReturnDeepestPoint If the shapes are initially interesecting this determines if the EPA algorithm will run to find the deepest point + /// @param inReturnDeepestPoint If the shapes are initially intersecting this determines if the EPA algorithm will run to find the deepest point /// @param ioLambda The max fraction along the sweep, on output updated with the actual collision fraction. /// @param outPointA is the contact point on A /// @param outPointB is the contact point on B diff --git a/Dependencies/Jolt/Jolt/Geometry/Ellipse.h b/Dependencies/Jolt/Jolt/Geometry/Ellipse.h index 19d00942..bfa508fa 100644 --- a/Dependencies/Jolt/Jolt/Geometry/Ellipse.h +++ b/Dependencies/Jolt/Jolt/Geometry/Ellipse.h @@ -18,7 +18,7 @@ class Ellipse /// Construct ellipse with radius A along the X-axis and B along the Y-axis Ellipse(float inA, float inB) : mA(inA), mB(inB) { JPH_ASSERT(inA > 0.0f); JPH_ASSERT(inB > 0.0f); } - /// Check if inPoint is inside the ellipsse + /// Check if inPoint is inside the ellipse bool IsInside(const Float2 &inPoint) const { return Square(inPoint.x / mA) + Square(inPoint.y / mB) <= 1.0f; @@ -26,7 +26,7 @@ class Ellipse /// Get the closest point on the ellipse to inPoint /// Assumes inPoint is outside the ellipse - /// @see Rotation Joint Limits in Quaterion Space by Gino van den Bergen, section 10.1 in Game Engine Gems 3. + /// @see Rotation Joint Limits in Quaternion Space by Gino van den Bergen, section 10.1 in Game Engine Gems 3. Float2 GetClosestPoint(const Float2 &inPoint) const { float a_sq = Square(mA); diff --git a/Dependencies/Jolt/Jolt/Geometry/GJKClosestPoint.h b/Dependencies/Jolt/Jolt/Geometry/GJKClosestPoint.h index 802dff75..2be8fec9 100644 --- a/Dependencies/Jolt/Jolt/Geometry/GJKClosestPoint.h +++ b/Dependencies/Jolt/Jolt/Geometry/GJKClosestPoint.h @@ -244,7 +244,7 @@ class GJKClosestPoint : public NonCopyable { // Separating axis found #ifdef JPH_GJK_DEBUG - Trace("Seperating axis"); + Trace("Separating axis"); #endif return false; } @@ -293,7 +293,7 @@ class GJKClosestPoint : public NonCopyable return true; } - // The next seperation axis to test is the negative of the closest point of the Minkowski sum to the origin + // The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin // Note: This must be done before terminating as converged since the separating axis is -v ioV = -ioV; @@ -456,7 +456,7 @@ class GJKClosestPoint : public NonCopyable break; } - // The next seperation axis to test is the negative of the closest point of the Minkowski sum to the origin + // The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin // Note: This must be done before terminating as converged since the separating axis is -v ioV = -ioV; @@ -508,7 +508,7 @@ class GJKClosestPoint : public NonCopyable outNumPoints = mNumPoints; } - /// Test if a ray inRayOrigin + lambda * inRayDirection for lambda e [0, ioLambda> instersects inA + /// Test if a ray inRayOrigin + lambda * inRayDirection for lambda e [0, ioLambda> intersects inA /// /// Code based upon: Ray Casting against General Convex Objects with Application to Continuous Collision Detection - Gino van den Bergen /// @@ -651,7 +651,7 @@ class GJKClosestPoint : public NonCopyable return true; } - /// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> instersects inB + /// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB /// /// @param inStart Start position and orientation of the convex object /// @param inDirection Direction of the sweep (ioLambda * inDirection determines length) @@ -675,7 +675,7 @@ class GJKClosestPoint : public NonCopyable return CastRay(Vec3::sZero(), inDirection, inTolerance, difference, ioLambda); } - /// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> instersects inB + /// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB /// /// @param inStart Start position and orientation of the convex object /// @param inDirection Direction of the sweep (ioLambda * inDirection determines length) @@ -697,7 +697,7 @@ class GJKClosestPoint : public NonCopyable { float tolerance_sq = Square(inTolerance); - // Calculate how close A and B (without their convex radius) need to be to eachother in order for us to consider this a collision + // Calculate how close A and B (without their convex radius) need to be to each other in order for us to consider this a collision float sum_convex_radius = inConvexRadiusA + inConvexRadiusB; // Transform the shape to be cast to the starting position diff --git a/Dependencies/Jolt/Jolt/Geometry/IndexedTriangle.h b/Dependencies/Jolt/Jolt/Geometry/IndexedTriangle.h index 24c619de..0275fcca 100644 --- a/Dependencies/Jolt/Jolt/Geometry/IndexedTriangle.h +++ b/Dependencies/Jolt/Jolt/Geometry/IndexedTriangle.h @@ -16,7 +16,7 @@ class IndexedTriangleNoMaterial /// Constructor IndexedTriangleNoMaterial() = default; - IndexedTriangleNoMaterial(uint32 inI1, uint32 inI2, uint32 inI3) { mIdx[0] = inI1; mIdx[1] = inI2; mIdx[2] = inI3; } + constexpr IndexedTriangleNoMaterial(uint32 inI1, uint32 inI2, uint32 inI3) : mIdx { inI1, inI2, inI3 } { } /// Check if two triangles are identical bool operator == (const IndexedTriangleNoMaterial &inRHS) const @@ -75,7 +75,7 @@ class IndexedTriangle : public IndexedTriangleNoMaterial using IndexedTriangleNoMaterial::IndexedTriangleNoMaterial; /// Constructor - IndexedTriangle(uint32 inI1, uint32 inI2, uint32 inI3, uint32 inMaterialIndex) : IndexedTriangleNoMaterial(inI1, inI2, inI3), mMaterialIndex(inMaterialIndex) { } + constexpr IndexedTriangle(uint32 inI1, uint32 inI2, uint32 inI3, uint32 inMaterialIndex) : IndexedTriangleNoMaterial(inI1, inI2, inI3), mMaterialIndex(inMaterialIndex) { } /// Check if two triangles are identical bool operator == (const IndexedTriangle &inRHS) const diff --git a/Dependencies/Jolt/Jolt/Geometry/OrientedBox.h b/Dependencies/Jolt/Jolt/Geometry/OrientedBox.h index 1e3174fb..c5c2a0e1 100644 --- a/Dependencies/Jolt/Jolt/Geometry/OrientedBox.h +++ b/Dependencies/Jolt/Jolt/Geometry/OrientedBox.h @@ -26,10 +26,10 @@ class [[nodiscard]] JPH_EXPORT_GCC_BUG_WORKAROUND OrientedBox /// Construct from axis aligned box and transform. Only works for rotation/translation matrix (no scaling / shearing). OrientedBox(Mat44Arg inOrientation, const AABox &inBox) : OrientedBox(inOrientation.PreTranslated(inBox.GetCenter()), inBox.GetExtent()) { } - /// Test if oriented boxe overlaps with axis aligned box eachother + /// Test if oriented box overlaps with axis aligned box each other bool Overlaps(const AABox &inBox, float inEpsilon = 1.0e-6f) const; - /// Test if two oriented boxes overlap eachother + /// Test if two oriented boxes overlap each other bool Overlaps(const OrientedBox &inBox, float inEpsilon = 1.0e-6f) const; Mat44 mOrientation; ///< Transform that positions and rotates the local space axis aligned box into world space diff --git a/Dependencies/Jolt/Jolt/Jolt.cmake b/Dependencies/Jolt/Jolt/Jolt.cmake index c0f70990..2b282842 100644 --- a/Dependencies/Jolt/Jolt/Jolt.cmake +++ b/Dependencies/Jolt/Jolt/Jolt.cmake @@ -203,9 +203,13 @@ set(JOLT_PHYSICS_SRC_FILES ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseLayer.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/BroadPhaseQuery.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/QuadTree.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/BroadPhase/QuadTree.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/CastConvexVsTriangles.cpp @@ -218,6 +222,7 @@ set(JOLT_PHYSICS_SRC_FILES ${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideConvexVsTriangles.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/CollidePointResult.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideShape.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideSphereVsTriangles.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideSphereVsTriangles.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/CollisionCollector.h @@ -233,6 +238,7 @@ set(JOLT_PHYSICS_SRC_FILES ${JOLT_PHYSICS_ROOT}/Physics/Collision/GroupFilter.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/GroupFilterTable.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/GroupFilterTable.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/InternalEdgeRemovingCollector.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/ManifoldBetweenTwoFaces.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/ManifoldBetweenTwoFaces.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/NarrowPhaseQuery.cpp @@ -240,6 +246,8 @@ set(JOLT_PHYSICS_SRC_FILES ${JOLT_PHYSICS_ROOT}/Physics/Collision/NarrowPhaseStats.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/NarrowPhaseStats.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/ObjectLayer.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/ObjectLayerPairFilterMask.h + ${JOLT_PHYSICS_ROOT}/Physics/Collision/ObjectLayerPairFilterTable.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/PhysicsMaterial.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/PhysicsMaterial.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/PhysicsMaterialSimple.cpp @@ -293,6 +301,7 @@ set(JOLT_PHYSICS_SRC_FILES ${JOLT_PHYSICS_ROOT}/Physics/Collision/SortReverseAndStore.h ${JOLT_PHYSICS_ROOT}/Physics/Collision/TransformedShape.cpp ${JOLT_PHYSICS_ROOT}/Physics/Collision/TransformedShape.h + ${JOLT_PHYSICS_ROOT}/Physics/Constraints/CalculateSolverSteps.h ${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConeConstraint.cpp ${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConeConstraint.h ${JOLT_PHYSICS_ROOT}/Physics/Constraints/Constraint.cpp @@ -365,8 +374,10 @@ set(JOLT_PHYSICS_SRC_FILES ${JOLT_PHYSICS_ROOT}/Physics/PhysicsUpdateContext.h ${JOLT_PHYSICS_ROOT}/Physics/Ragdoll/Ragdoll.cpp ${JOLT_PHYSICS_ROOT}/Physics/Ragdoll/Ragdoll.h + ${JOLT_PHYSICS_ROOT}/Physics/SoftBody/SoftBodyContactListener.h ${JOLT_PHYSICS_ROOT}/Physics/SoftBody/SoftBodyCreationSettings.cpp ${JOLT_PHYSICS_ROOT}/Physics/SoftBody/SoftBodyCreationSettings.h + ${JOLT_PHYSICS_ROOT}/Physics/SoftBody/SoftBodyManifold.h ${JOLT_PHYSICS_ROOT}/Physics/SoftBody/SoftBodyMotionProperties.h ${JOLT_PHYSICS_ROOT}/Physics/SoftBody/SoftBodyMotionProperties.cpp ${JOLT_PHYSICS_ROOT}/Physics/SoftBody/SoftBodyShape.cpp @@ -409,6 +420,8 @@ set(JOLT_PHYSICS_SRC_FILES ${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererPlayback.h ${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererRecorder.cpp ${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererRecorder.h + ${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererSimple.cpp + ${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererSimple.h ${JOLT_PHYSICS_ROOT}/Skeleton/SkeletalAnimation.cpp ${JOLT_PHYSICS_ROOT}/Skeleton/SkeletalAnimation.h ${JOLT_PHYSICS_ROOT}/Skeleton/Skeleton.cpp @@ -476,17 +489,22 @@ endif() target_include_directories(Jolt PUBLIC ${PHYSICS_REPO_ROOT}) target_precompile_headers(Jolt PRIVATE ${JOLT_PHYSICS_ROOT}/Jolt.h) -target_compile_definitions(Jolt PUBLIC "$<$:_DEBUG;JPH_PROFILE_ENABLED;JPH_DEBUG_RENDERER>") -target_compile_definitions(Jolt PUBLIC "$<$:NDEBUG;JPH_PROFILE_ENABLED;JPH_DEBUG_RENDERER>") -target_compile_definitions(Jolt PUBLIC "$<$:NDEBUG>") -target_compile_definitions(Jolt PUBLIC "$<$:NDEBUG;JPH_PROFILE_ENABLED;JPH_DISABLE_TEMP_ALLOCATOR;JPH_DISABLE_CUSTOM_ALLOCATOR;JPH_DEBUG_RENDERER>") -target_compile_definitions(Jolt PUBLIC "$<$:NDEBUG;JPH_PROFILE_ENABLED;JPH_DEBUG_RENDERER>") -target_compile_definitions(Jolt PUBLIC "$<$:NDEBUG>") + +# Set the debug/non-debug build flags +target_compile_definitions(Jolt PUBLIC "$<$:_DEBUG>") +target_compile_definitions(Jolt PUBLIC "$<$:NDEBUG>") + +# ASAN should use the default allocators +target_compile_definitions(Jolt PUBLIC "$<$:JPH_DISABLE_TEMP_ALLOCATOR;JPH_DISABLE_CUSTOM_ALLOCATOR>") # Setting floating point exceptions if (FLOATING_POINT_EXCEPTIONS_ENABLED AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - target_compile_definitions(Jolt PUBLIC "$<$:JPH_FLOATING_POINT_EXCEPTIONS_ENABLED>") - target_compile_definitions(Jolt PUBLIC "$<$:JPH_FLOATING_POINT_EXCEPTIONS_ENABLED>") + target_compile_definitions(Jolt PUBLIC "$<$:JPH_FLOATING_POINT_EXCEPTIONS_ENABLED>") +endif() + +# Setting the disable custom allocator flag +if (DISABLE_CUSTOM_ALLOCATOR) + target_compile_definitions(Jolt PUBLIC JPH_DISABLE_CUSTOM_ALLOCATOR) endif() # Setting double precision flag @@ -514,6 +532,20 @@ if (TRACK_NARROWPHASE_STATS) target_compile_definitions(Jolt PUBLIC JPH_TRACK_NARROWPHASE_STATS) endif() +# Enable the debug renderer +if (DEBUG_RENDERER_IN_DISTRIBUTION) + target_compile_definitions(Jolt PUBLIC "JPH_DEBUG_RENDERER") +elseif (DEBUG_RENDERER_IN_DEBUG_AND_RELEASE) + target_compile_definitions(Jolt PUBLIC "$<$:JPH_DEBUG_RENDERER>") +endif() + +# Enable the profiler +if (PROFILER_IN_DISTRIBUTION) + target_compile_definitions(Jolt PUBLIC "JPH_PROFILE_ENABLED") +elseif (PROFILER_IN_DEBUG_AND_RELEASE) + target_compile_definitions(Jolt PUBLIC "$<$:JPH_PROFILE_ENABLED>") +endif() + # Emit the instruction set definitions to ensure that child projects use the same settings even if they override the used instruction sets (a mismatch causes link errors) function(EMIT_X86_INSTRUCTION_SET_DEFINITIONS) if (USE_AVX512) @@ -589,6 +621,14 @@ else() if (USE_FMADD AND NOT CROSS_PLATFORM_DETERMINISTIC) target_compile_options(Jolt PUBLIC -mfma) endif() + + # On 32-bit builds we need to default to using SSE instructions, the x87 FPU instructions have higher intermediate precision + # which will cause problems in the collision detection code (the effect is similar to leaving FMA on, search for + # JPH_PRECISE_MATH_ON for the locations where this is a problem). + if (NOT MSVC) + target_compile_options(Jolt PUBLIC -mfpmath=sse) + endif() + EMIT_X86_INSTRUCTION_SET_DEFINITIONS() endif() endif() diff --git a/Dependencies/Jolt/Jolt/Jolt.natvis b/Dependencies/Jolt/Jolt/Jolt.natvis index 00a39519..4252263d 100644 --- a/Dependencies/Jolt/Jolt/Jolt.natvis +++ b/Dependencies/Jolt/Jolt/Jolt.natvis @@ -1,4 +1,4 @@ - + r={(int)r}, g={(int)g}, b={(int)b}, a={(int)a} @@ -83,4 +83,4 @@ - \ No newline at end of file + diff --git a/Dependencies/Jolt/Jolt/Math/DMat44.h b/Dependencies/Jolt/Jolt/Math/DMat44.h index f1fbaeaa..65c96871 100644 --- a/Dependencies/Jolt/Jolt/Math/DMat44.h +++ b/Dependencies/Jolt/Jolt/Math/DMat44.h @@ -55,7 +55,7 @@ class [[nodiscard]] alignas(JPH_DVECTOR_ALIGNMENT) DMat44 /// Convert to Mat44 rounding to nearest JPH_INLINE Mat44 ToMat44() const { return Mat44(mCol[0], mCol[1], mCol[2], Vec3(mCol3)); } - /// Comparsion + /// Comparison JPH_INLINE bool operator == (DMat44Arg inM2) const; JPH_INLINE bool operator != (DMat44Arg inM2) const { return !(*this == inM2); } diff --git a/Dependencies/Jolt/Jolt/Math/DVec3.h b/Dependencies/Jolt/Jolt/Math/DVec3.h index f288dbf0..58e0a06f 100644 --- a/Dependencies/Jolt/Jolt/Math/DVec3.h +++ b/Dependencies/Jolt/Jolt/Math/DVec3.h @@ -154,6 +154,9 @@ class [[nodiscard]] alignas(JPH_DVECTOR_ALIGNMENT) DVec3 JPH_INLINE void SetY(double inY) { mF64[1] = inY; } JPH_INLINE void SetZ(double inZ) { mF64[2] = mF64[3] = inZ; } // Assure Z and W are the same + /// Set all components + JPH_INLINE void Set(double inX, double inY, double inZ) { *this = DVec3(inX, inY, inZ); } + /// Get double component by index JPH_INLINE double operator [] (uint inCoordinate) const { JPH_ASSERT(inCoordinate < 3); return mF64[inCoordinate]; } diff --git a/Dependencies/Jolt/Jolt/Math/DVec3.inl b/Dependencies/Jolt/Jolt/Math/DVec3.inl index fb575f34..08a17f84 100644 --- a/Dependencies/Jolt/Jolt/Math/DVec3.inl +++ b/Dependencies/Jolt/Jolt/Math/DVec3.inl @@ -42,7 +42,7 @@ DVec3::DVec3(double inX, double inY, double inZ) mValue = _mm256_set_pd(inZ, inZ, inY, inX); // Assure Z and W are the same #elif defined(JPH_USE_SSE) mValue.mLow = _mm_set_pd(inY, inX); - mValue.mHigh = _mm_set_pd1(inZ); + mValue.mHigh = _mm_set1_pd(inZ); #elif defined(JPH_USE_NEON) mValue.val[0] = vcombine_f64(vcreate_f64(*reinterpret_cast(&inX)), vcreate_f64(*reinterpret_cast(&inY))); mValue.val[1] = vdupq_n_f64(inZ); @@ -66,7 +66,7 @@ DVec3::DVec3(const Double3 &inV) mValue = _mm256_blend_pd(xy, z, 0b1100); // Assure Z and W are the same #elif defined(JPH_USE_SSE) mValue.mLow = _mm_loadu_pd(&inV.x); - mValue.mHigh = _mm_set_pd1(inV.z); + mValue.mHigh = _mm_set1_pd(inV.z); #elif defined(JPH_USE_NEON) mValue.val[0] = vld1q_f64(&inV.x); mValue.val[1] = vdupq_n_f64(inV.z); diff --git a/Dependencies/Jolt/Jolt/Math/EigenValueSymmetric.h b/Dependencies/Jolt/Jolt/Math/EigenValueSymmetric.h index b795a2a1..43436ee9 100644 --- a/Dependencies/Jolt/Jolt/Math/EigenValueSymmetric.h +++ b/Dependencies/Jolt/Jolt/Math/EigenValueSymmetric.h @@ -92,7 +92,7 @@ bool EigenValueSymmetric(const Matrix &inMatrix, Matrix &outEigVec, Vector &outE return true; } - // On the first three sweeps use a fraction of the sum of the off diagonal elements as treshold + // On the first three sweeps use a fraction of the sum of the off diagonal elements as threshold float tresh = sweep < 4? 0.2f * sm / Square(n) : 0.0f; for (uint ip = 0; ip < n - 1; ++ip) diff --git a/Dependencies/Jolt/Jolt/Math/Float3.h b/Dependencies/Jolt/Jolt/Math/Float3.h index b62660df..e288201b 100644 --- a/Dependencies/Jolt/Jolt/Math/Float3.h +++ b/Dependencies/Jolt/Jolt/Math/Float3.h @@ -17,7 +17,7 @@ class [[nodiscard]] Float3 Float3() = default; ///< Intentionally not initialized for performance reasons Float3(const Float3 &inRHS) = default; Float3 & operator = (const Float3 &inRHS) = default; - Float3(float inX, float inY, float inZ) : x(inX), y(inY), z(inZ) { } + constexpr Float3(float inX, float inY, float inZ) : x(inX), y(inY), z(inZ) { } float operator [] (int inCoordinate) const { diff --git a/Dependencies/Jolt/Jolt/Math/Mat44.h b/Dependencies/Jolt/Jolt/Math/Mat44.h index bec53bd8..1a1e254c 100644 --- a/Dependencies/Jolt/Jolt/Math/Mat44.h +++ b/Dependencies/Jolt/Jolt/Math/Mat44.h @@ -87,11 +87,14 @@ class [[nodiscard]] alignas(JPH_VECTOR_ALIGNMENT) Mat44 /// @param inUp Up vector static JPH_INLINE Mat44 sLookAt(Vec3Arg inPos, Vec3Arg inTarget, Vec3Arg inUp); + /// Returns a right-handed perspective projection matrix + static JPH_INLINE Mat44 sPerspective(float inFovY, float inAspect, float inNear, float inFar); + /// Get float component by element index JPH_INLINE float operator () (uint inRow, uint inColumn) const { JPH_ASSERT(inRow < 4); JPH_ASSERT(inColumn < 4); return mCol[inColumn].mF32[inRow]; } JPH_INLINE float & operator () (uint inRow, uint inColumn) { JPH_ASSERT(inRow < 4); JPH_ASSERT(inColumn < 4); return mCol[inColumn].mF32[inRow]; } - /// Comparsion + /// Comparison JPH_INLINE bool operator == (Mat44Arg inM2) const; JPH_INLINE bool operator != (Mat44Arg inM2) const { return !(*this == inM2); } diff --git a/Dependencies/Jolt/Jolt/Math/Mat44.inl b/Dependencies/Jolt/Jolt/Math/Mat44.inl index d61ffecc..76577b71 100644 --- a/Dependencies/Jolt/Jolt/Math/Mat44.inl +++ b/Dependencies/Jolt/Jolt/Math/Mat44.inl @@ -213,6 +213,15 @@ Mat44 Mat44::sLookAt(Vec3Arg inPos, Vec3Arg inTarget, Vec3Arg inUp) return Mat44(Vec4(right, 0), Vec4(up, 0), Vec4(-direction, 0), Vec4(inPos, 1)).InversedRotationTranslation(); } +Mat44 Mat44::sPerspective(float inFovY, float inAspect, float inNear, float inFar) +{ + float height = 1.0f / Tan(0.5f * inFovY); + float width = height / inAspect; + float range = inFar / (inNear - inFar); + + return Mat44(Vec4(width, 0.0f, 0.0f, 0.0f), Vec4(0.0f, height, 0.0f, 0.0f), Vec4(0.0f, 0.0f, range, -1.0f), Vec4(0.0f, 0.0f, range * inNear, 0.0f)); +} + bool Mat44::operator == (Mat44Arg inM2) const { return UVec4::sAnd( @@ -773,8 +782,6 @@ bool Mat44::SetInversed3x3(Mat44Arg inM) Quat Mat44::GetQuaternion() const { - JPH_ASSERT(mCol[3] == Vec4(0, 0, 0, 1)); - float tr = mCol[0].mF32[0] + mCol[1].mF32[1] + mCol[2].mF32[2]; if (tr >= 0.0f) diff --git a/Dependencies/Jolt/Jolt/Math/Math.h b/Dependencies/Jolt/Jolt/Math/Math.h index c37c04b1..fe787f21 100644 --- a/Dependencies/Jolt/Jolt/Math/Math.h +++ b/Dependencies/Jolt/Jolt/Math/Math.h @@ -118,6 +118,8 @@ inline uint CountTrailingZeros(uint32 inValue) #else return __builtin_clz(__builtin_bitreverse32(inValue)); #endif +#elif defined(JPH_CPU_E2K) + return inValue ? __builtin_ctz(inValue) : 32; #else #error Undefined #endif @@ -146,6 +148,8 @@ inline uint CountLeadingZeros(uint32 inValue) #else return __builtin_clz(inValue); #endif +#elif defined(JPH_CPU_E2K) + return inValue ? __builtin_clz(inValue) : 32; #else #error Undefined #endif diff --git a/Dependencies/Jolt/Jolt/Math/MathTypes.h b/Dependencies/Jolt/Jolt/Math/MathTypes.h index 0acd459d..8d019ae4 100644 --- a/Dependencies/Jolt/Jolt/Math/MathTypes.h +++ b/Dependencies/Jolt/Jolt/Math/MathTypes.h @@ -17,17 +17,17 @@ class Mat44; class DMat44; // Types to use for passing arguments to functions -using Vec3Arg = Vec3; +using Vec3Arg = const Vec3; #ifdef JPH_USE_AVX - using DVec3Arg = DVec3; + using DVec3Arg = const DVec3; #else using DVec3Arg = const DVec3 &; #endif -using Vec4Arg = Vec4; -using UVec4Arg = UVec4; -using Vec8Arg = Vec8; -using UVec8Arg = UVec8; -using QuatArg = Quat; +using Vec4Arg = const Vec4; +using UVec4Arg = const UVec4; +using Vec8Arg = const Vec8; +using UVec8Arg = const UVec8; +using QuatArg = const Quat; using Mat44Arg = const Mat44 &; using DMat44Arg = const DMat44 &; diff --git a/Dependencies/Jolt/Jolt/Math/Real.h b/Dependencies/Jolt/Jolt/Math/Real.h index a9d32751..7773abf7 100644 --- a/Dependencies/Jolt/Jolt/Math/Real.h +++ b/Dependencies/Jolt/Jolt/Math/Real.h @@ -38,7 +38,7 @@ using RMat44Arg = Mat44Arg; // Put the 'real' operator in a namespace so that users can opt in to use it: // using namespace JPH::literals; namespace literals { - constexpr Real operator "" _r (long double inValue) { return Real(inValue); } + constexpr Real operator ""_r (long double inValue) { return Real(inValue); } }; JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Math/UVec4.inl b/Dependencies/Jolt/Jolt/Math/UVec4.inl index 8f343163..e01d66b1 100644 --- a/Dependencies/Jolt/Jolt/Math/UVec4.inl +++ b/Dependencies/Jolt/Jolt/Math/UVec4.inl @@ -227,7 +227,7 @@ UVec4 UVec4::sSort4True(UVec4Arg inValue, UVec4Arg inIndex) // If inValue.y is false then shift Z and further to Y and further v = UVec4::sSelect(v.Swizzle(), v, inValue.SplatY()); - // If inValue.x is false then shift X and furhter to Y and furhter + // If inValue.x is false then shift X and further to Y and further v = UVec4::sSelect(v.Swizzle(), v, inValue.SplatX()); return v; diff --git a/Dependencies/Jolt/Jolt/Math/Vec4.h b/Dependencies/Jolt/Jolt/Math/Vec4.h index 7cf4389e..b369f8e3 100644 --- a/Dependencies/Jolt/Jolt/Math/Vec4.h +++ b/Dependencies/Jolt/Jolt/Math/Vec4.h @@ -242,10 +242,10 @@ class [[nodiscard]] alignas(JPH_VECTOR_ALIGNMENT) Vec4 /// Get vector that contains the sign of each element (returns 1.0f if positive, -1.0f if negative) JPH_INLINE Vec4 GetSign() const; - /// Calcluate the sine and cosine for each element of this vector (input in radians) + /// Calculate the sine and cosine for each element of this vector (input in radians) inline void SinCos(Vec4 &outSin, Vec4 &outCos) const; - /// Calcluate the tangent for each element of this vector (input in radians) + /// Calculate the tangent for each element of this vector (input in radians) inline Vec4 Tan() const; /// Calculate the arc sine for each element of this vector (returns value in the range [-PI / 2, PI / 2]) diff --git a/Dependencies/Jolt/Jolt/Physics/Body/AllowedDOFs.h b/Dependencies/Jolt/Jolt/Physics/Body/AllowedDOFs.h index 95548565..8445cb18 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/AllowedDOFs.h +++ b/Dependencies/Jolt/Jolt/Physics/Body/AllowedDOFs.h @@ -11,12 +11,12 @@ enum class EAllowedDOFs : uint8 { None = 0b000000, ///< No degrees of freedom are allowed. Note that this is not valid and will crash. Use a static body instead. All = 0b111111, ///< All degrees of freedom are allowed - TranslationX = 0b000001, ///< Body cannot move in world space X axis - TranslationY = 0b000010, ///< Body cannot move in world space Y axis - TranslationZ = 0b000100, ///< Body cannot move in world space Z axis - RotationX = 0b001000, ///< Body cannot rotate around local space X axis - RotationY = 0b010000, ///< Body cannot rotate around local space Y axis - RotationZ = 0b100000, ///< Body cannot rotate around local space Z axis + TranslationX = 0b000001, ///< Body can move in world space X axis + TranslationY = 0b000010, ///< Body can move in world space Y axis + TranslationZ = 0b000100, ///< Body can move in world space Z axis + RotationX = 0b001000, ///< Body can rotate around world space X axis + RotationY = 0b010000, ///< Body can rotate around world space Y axis + RotationZ = 0b100000, ///< Body can rotate around world space Z axis Plane2D = TranslationX | TranslationY | RotationZ, ///< Body can only move in X and Y axis and rotate around Z axis }; diff --git a/Dependencies/Jolt/Jolt/Physics/Body/Body.cpp b/Dependencies/Jolt/Jolt/Physics/Body/Body.cpp index 7cb8fb9e..085ae427 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/Body.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Body/Body.cpp @@ -75,7 +75,7 @@ void Body::SetAllowSleeping(bool inAllow) { mMotionProperties->mAllowSleeping = inAllow; if (inAllow) - ResetSleepTestSpheres(); + ResetSleepTimer(); } void Body::MoveKinematic(RVec3Arg inTargetPosition, QuatArg inTargetRotation, float inDeltaTime) @@ -99,7 +99,7 @@ void Body::CalculateWorldSpaceBoundsInternal() mBounds = mShape->GetWorldSpaceBounds(GetCenterOfMassTransform(), Vec3::sReplicate(1.0f)); } -void Body::SetPositionAndRotationInternal(RVec3Arg inPosition, QuatArg inRotation, bool inResetSleepTestSpheres) +void Body::SetPositionAndRotationInternal(RVec3Arg inPosition, QuatArg inRotation, bool inResetSleepTimer) { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sPositionAccess, BodyAccess::EAccess::ReadWrite)); @@ -110,8 +110,8 @@ void Body::SetPositionAndRotationInternal(RVec3Arg inPosition, QuatArg inRotatio CalculateWorldSpaceBoundsInternal(); // Reset sleeping test - if (inResetSleepTestSpheres && mMotionProperties != nullptr) - ResetSleepTestSpheres(); + if (inResetSleepTimer && mMotionProperties != nullptr) + ResetSleepTimer(); } void Body::UpdateCenterOfMassInternal(Vec3Arg inPreviousCenterOfMass, bool inUpdateMassProperties) @@ -334,9 +334,11 @@ BodyCreationSettings Body::GetBodyCreationSettings() const result.mAllowedDOFs = mMotionProperties != nullptr? mMotionProperties->GetAllowedDOFs() : EAllowedDOFs::All; result.mAllowDynamicOrKinematic = mMotionProperties != nullptr; result.mIsSensor = IsSensor(); - result.mSensorDetectsStatic = SensorDetectsStatic(); + result.mCollideKinematicVsNonDynamic = GetCollideKinematicVsNonDynamic(); result.mUseManifoldReduction = GetUseManifoldReduction(); + result.mApplyGyroscopicForce = GetApplyGyroscopicForce(); result.mMotionQuality = mMotionProperties != nullptr? mMotionProperties->GetMotionQuality() : EMotionQuality::Discrete; + result.mEnhancedInternalEdgeRemoval = GetEnhancedInternalEdgeRemoval(); result.mAllowSleeping = mMotionProperties != nullptr? GetAllowSleeping() : true; result.mFriction = GetFriction(); result.mRestitution = GetRestitution(); @@ -345,9 +347,39 @@ BodyCreationSettings Body::GetBodyCreationSettings() const result.mMaxLinearVelocity = mMotionProperties != nullptr? mMotionProperties->GetMaxLinearVelocity() : 0.0f; result.mMaxAngularVelocity = mMotionProperties != nullptr? mMotionProperties->GetMaxAngularVelocity() : 0.0f; result.mGravityFactor = mMotionProperties != nullptr? mMotionProperties->GetGravityFactor() : 1.0f; + result.mNumVelocityStepsOverride = mMotionProperties != nullptr? mMotionProperties->GetNumVelocityStepsOverride() : 0; + result.mNumPositionStepsOverride = mMotionProperties != nullptr? mMotionProperties->GetNumPositionStepsOverride() : 0; result.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided; - result.mMassPropertiesOverride.mMass = mMotionProperties != nullptr? 1.0f / mMotionProperties->GetInverseMassUnchecked() : FLT_MAX; - result.mMassPropertiesOverride.mInertia = mMotionProperties != nullptr? mMotionProperties->GetLocalSpaceInverseInertiaUnchecked().Inversed3x3() : Mat44::sIdentity(); + + // Invert inertia and mass + if (mMotionProperties != nullptr) + { + float inv_mass = mMotionProperties->GetInverseMassUnchecked(); + Mat44 inv_inertia = mMotionProperties->GetLocalSpaceInverseInertiaUnchecked(); + + // Get mass + result.mMassPropertiesOverride.mMass = inv_mass != 0.0f? 1.0f / inv_mass : FLT_MAX; + + // Get inertia + Mat44 inertia; + if (inertia.SetInversed3x3(inv_inertia)) + { + // Inertia was invertible, we can use it + result.mMassPropertiesOverride.mInertia = inertia; + } + else + { + // Prevent division by zero + Vec3 diagonal = Vec3::sMax(inv_inertia.GetDiagonal3(), Vec3::sReplicate(FLT_MIN)); + result.mMassPropertiesOverride.mInertia = Mat44::sScale(diagonal.Reciprocal()); + } + } + else + { + result.mMassPropertiesOverride.mMass = FLT_MAX; + result.mMassPropertiesOverride.mInertia = Mat44::sScale(Vec3::sReplicate(FLT_MAX)); + } + result.SetShape(GetShape()); return result; diff --git a/Dependencies/Jolt/Jolt/Physics/Body/Body.h b/Dependencies/Jolt/Jolt/Physics/Body/Body.h index 40e52047..509f55f6 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/Body.h +++ b/Dependencies/Jolt/Jolt/Physics/Body/Body.h @@ -79,14 +79,18 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public /// Check if this body is a sensor. inline bool IsSensor() const { return (mFlags.load(memory_order_relaxed) & uint8(EFlags::IsSensor)) != 0; } - // If this sensor detects static objects entering it. Note that the sensor must be kinematic and active for it to detect static objects. - inline void SetSensorDetectsStatic(bool inDetectsStatic) { JPH_ASSERT(IsRigidBody()); if (inDetectsStatic) mFlags.fetch_or(uint8(EFlags::SensorDetectsStatic), memory_order_relaxed); else mFlags.fetch_and(uint8(~uint8(EFlags::SensorDetectsStatic)), memory_order_relaxed); } + /// If kinematic objects can generate contact points against other kinematic or static objects. + /// Note that turning this on can be CPU intensive as much more collision detection work will be done without any effect on the simulation (kinematic objects are not affected by other kinematic/static objects). + /// This can be used to make sensors detect static objects. Note that the sensor must be kinematic and active for it to detect static objects. + inline void SetCollideKinematicVsNonDynamic(bool inCollide) { JPH_ASSERT(IsRigidBody()); if (inCollide) mFlags.fetch_or(uint8(EFlags::CollideKinematicVsNonDynamic), memory_order_relaxed); else mFlags.fetch_and(uint8(~uint8(EFlags::CollideKinematicVsNonDynamic)), memory_order_relaxed); } - /// Check if this sensor detects static objects entering it. - inline bool SensorDetectsStatic() const { return (mFlags.load(memory_order_relaxed) & uint8(EFlags::SensorDetectsStatic)) != 0; } + /// Check if kinematic objects can generate contact points against other kinematic or static objects. + inline bool GetCollideKinematicVsNonDynamic() const { return (mFlags.load(memory_order_relaxed) & uint8(EFlags::CollideKinematicVsNonDynamic)) != 0; } - /// If PhysicsSettings::mUseManifoldReduction is true, this allows turning off manifold reduction for this specific body. Manifold reduction by default will combine contacts that come from different SubShapeIDs (e.g. different triangles or different compound shapes). + /// If PhysicsSettings::mUseManifoldReduction is true, this allows turning off manifold reduction for this specific body. + /// Manifold reduction by default will combine contacts with similar normals that come from different SubShapeIDs (e.g. different triangles in a mesh shape or different compound shapes). /// If the application requires tracking exactly which SubShapeIDs are in contact, you can turn off manifold reduction. Note that this comes at a performance cost. + /// Consider using BodyInterface::SetUseManifoldReduction if the body could already be in contact with other bodies to ensure that the contact cache is invalidated and you get the correct contact callbacks. inline void SetUseManifoldReduction(bool inUseReduction) { JPH_ASSERT(IsRigidBody()); if (inUseReduction) mFlags.fetch_or(uint8(EFlags::UseManifoldReduction), memory_order_relaxed); else mFlags.fetch_and(uint8(~uint8(EFlags::UseManifoldReduction)), memory_order_relaxed); } /// Check if this body can use manifold reduction. @@ -95,8 +99,25 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public /// Checks if the combination of this body and inBody2 should use manifold reduction inline bool GetUseManifoldReductionWithBody(const Body &inBody2) const { return ((mFlags.load(memory_order_relaxed) & inBody2.mFlags.load(memory_order_relaxed)) & uint8(EFlags::UseManifoldReduction)) != 0; } - /// Motion type of this body + /// Set to indicate that the gyroscopic force should be applied to this body (aka Dzhanibekov effect, see https://en.wikipedia.org/wiki/Tennis_racket_theorem) + inline void SetApplyGyroscopicForce(bool inApply) { JPH_ASSERT(IsRigidBody()); if (inApply) mFlags.fetch_or(uint8(EFlags::ApplyGyroscopicForce), memory_order_relaxed); else mFlags.fetch_and(uint8(~uint8(EFlags::ApplyGyroscopicForce)), memory_order_relaxed); } + + /// Check if the gyroscopic force is being applied for this body + inline bool GetApplyGyroscopicForce() const { return (mFlags.load(memory_order_relaxed) & uint8(EFlags::ApplyGyroscopicForce)) != 0; } + + /// Set to indicate that extra effort should be made to try to remove ghost contacts (collisions with internal edges of a mesh). This is more expensive but makes bodies move smoother over a mesh with convex edges. + inline void SetEnhancedInternalEdgeRemoval(bool inApply) { JPH_ASSERT(IsRigidBody()); if (inApply) mFlags.fetch_or(uint8(EFlags::EnhancedInternalEdgeRemoval), memory_order_relaxed); else mFlags.fetch_and(uint8(~uint8(EFlags::EnhancedInternalEdgeRemoval)), memory_order_relaxed); } + + /// Check if enhanced internal edge removal is turned on + inline bool GetEnhancedInternalEdgeRemoval() const { return (mFlags.load(memory_order_relaxed) & uint8(EFlags::EnhancedInternalEdgeRemoval)) != 0; } + + /// Checks if the combination of this body and inBody2 should use enhanced internal edge removal + inline bool GetEnhancedInternalEdgeRemovalWithBody(const Body &inBody2) const { return ((mFlags.load(memory_order_relaxed) | inBody2.mFlags.load(memory_order_relaxed)) & uint8(EFlags::EnhancedInternalEdgeRemoval)) != 0; } + + /// Get the bodies motion type. inline EMotionType GetMotionType() const { return mMotionType; } + + /// Set the motion type of this body. Consider using BodyInterface::SetMotionType instead of this function if the body may be active or if it needs to be activated. void SetMotionType(EMotionType inMotionType); /// Get broadphase layer, this determines in which broad phase sub-tree the object is placed @@ -110,10 +131,13 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public CollisionGroup & GetCollisionGroup() { return mCollisionGroup; } void SetCollisionGroup(const CollisionGroup &inGroup) { mCollisionGroup = inGroup; } - /// If this body can go to sleep. Note that disabling sleeping on a sleeping object wil not wake it up. + /// If this body can go to sleep. Note that disabling sleeping on a sleeping object will not wake it up. bool GetAllowSleeping() const { return mMotionProperties->mAllowSleeping; } void SetAllowSleeping(bool inAllow); + /// Resets the sleep timer. This does not wake up the body if it is sleeping, but allows resetting the system that detects when a body is sleeping. + inline void ResetSleepTimer(); + /// Friction (dimensionless number, usually between 0 and 1, 0 = no friction, 1 = friction force equals force that presses the two bodies together). Note that bodies can have negative friction but the combined friction (see PhysicsSystem::SetCombineFriction) should never go below zero. inline float GetFriction() const { return mFriction; } void SetFriction(float inFriction) { mFriction = inFriction; } @@ -146,19 +170,19 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public /// Velocity of point inPoint (in world space, e.g. on the surface of the body) of the body (unit: m/s) inline Vec3 GetPointVelocity(RVec3Arg inPoint) const { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sPositionAccess, BodyAccess::EAccess::Read)); return GetPointVelocityCOM(Vec3(inPoint - mPosition)); } - /// Add force (unit: N) at center of mass for the next time step, will be reset after the next call to PhysicsSimulation::Update + /// Add force (unit: N) at center of mass for the next time step, will be reset after the next call to PhysicsSystem::Update inline void AddForce(Vec3Arg inForce) { JPH_ASSERT(IsDynamic()); (Vec3::sLoadFloat3Unsafe(mMotionProperties->mForce) + inForce).StoreFloat3(&mMotionProperties->mForce); } - /// Add force (unit: N) at inPosition for the next time step, will be reset after the next call to PhysicsSimulation::Update + /// Add force (unit: N) at inPosition for the next time step, will be reset after the next call to PhysicsSystem::Update inline void AddForce(Vec3Arg inForce, RVec3Arg inPosition); - /// Add torque (unit: N m) for the next time step, will be reset after the next call to PhysicsSimulation::Update + /// Add torque (unit: N m) for the next time step, will be reset after the next call to PhysicsSystem::Update inline void AddTorque(Vec3Arg inTorque) { JPH_ASSERT(IsDynamic()); (Vec3::sLoadFloat3Unsafe(mMotionProperties->mTorque) + inTorque).StoreFloat3(&mMotionProperties->mTorque); } - // Get the total amount of force applied to the center of mass this time step (through AddForce calls). Note that it will reset to zero after PhysicsSimulation::Update. + // Get the total amount of force applied to the center of mass this time step (through AddForce calls). Note that it will reset to zero after PhysicsSystem::Update. inline Vec3 GetAccumulatedForce() const { JPH_ASSERT(IsDynamic()); return mMotionProperties->GetAccumulatedForce(); } - // Get the total amount of torque applied to the center of mass this time step (through AddForce/AddTorque calls). Note that it will reset to zero after PhysicsSimulation::Update. + // Get the total amount of torque applied to the center of mass this time step (through AddForce/AddTorque calls). Note that it will reset to zero after PhysicsSystem::Update. inline Vec3 GetAccumulatedTorque() const { JPH_ASSERT(IsDynamic()); return mMotionProperties->GetAccumulatedTorque(); } // Reset the total accumulated force, not that this will be done automatically after every time step. @@ -167,6 +191,9 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public // Reset the total accumulated torque, not that this will be done automatically after every time step. JPH_INLINE void ResetTorque() { JPH_ASSERT(IsDynamic()); return mMotionProperties->ResetTorque(); } + // Reset the current velocity and accumulated force and torque. + JPH_INLINE void ResetMotion() { JPH_ASSERT(!IsStatic()); return mMotionProperties->ResetMotion(); } + /// Get inverse inertia tensor in world space inline Mat44 GetInverseInertia() const; @@ -183,13 +210,13 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public void MoveKinematic(RVec3Arg inTargetPosition, QuatArg inTargetRotation, float inDeltaTime); /// Applies an impulse to the body that simulates fluid buoyancy and drag - /// @param inSurfacePosition Position on the fluid surface in world space + /// @param inSurfacePosition Position of the fluid surface in world space /// @param inSurfaceNormal Normal of the fluid surface (should point up) /// @param inBuoyancy The buoyancy factor for the body. 1 = neutral body, < 1 sinks, > 1 floats. Note that we don't use the fluid density since it is harder to configure than a simple number between [0, 2] /// @param inLinearDrag Linear drag factor that slows down the body when in the fluid (approx. 0.5) /// @param inAngularDrag Angular drag factor that slows down rotation when the body is in the fluid (approx. 0.01) /// @param inFluidVelocity The average velocity of the fluid (in m/s) in which the body resides - /// @param inGravity The graviy vector (pointing down) + /// @param inGravity The gravity vector (pointing down) /// @param inDeltaTime Delta time of the next simulation step (in s) /// @return true if an impulse was applied, false if the body was not in the fluid bool ApplyBuoyancyImpulse(RVec3Arg inSurfacePosition, Vec3Arg inSurfaceNormal, float inBuoyancy, float inLinearDrag, float inAngularDrag, Vec3Arg inFluidVelocity, Vec3Arg inGravity, float inDeltaTime); @@ -279,9 +306,9 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public void CalculateWorldSpaceBoundsInternal(); /// Function to update body's position (should only be called by the BodyInterface since it also requires updating the broadphase) - void SetPositionAndRotationInternal(RVec3Arg inPosition, QuatArg inRotation, bool inResetSleepTestSpheres = true); + void SetPositionAndRotationInternal(RVec3Arg inPosition, QuatArg inRotation, bool inResetSleepTimer = true); - /// Updates the center of mass and optionally mass propertes after shifting the center of mass or changes to the shape (should only be called by the BodyInterface since it also requires updating the broadphase) + /// Updates the center of mass and optionally mass properties after shifting the center of mass or changes to the shape (should only be called by the BodyInterface since it also requires updating the broadphase) /// @param inPreviousCenterOfMass Center of mass of the shape before the alterations /// @param inUpdateMassProperties When true, the mass and inertia tensor is recalculated void UpdateCenterOfMassInternal(Vec3Arg inPreviousCenterOfMass, bool inUpdateMassProperties); @@ -313,15 +340,16 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public explicit Body(bool); ///< Alternative constructor that initializes all members inline void GetSleepTestPoints(RVec3 *outPoints) const; ///< Determine points to test for checking if body is sleeping: COM, COM + largest bounding box axis, COM + second largest bounding box axis - inline void ResetSleepTestSpheres(); ///< Reset spheres to current position as returned by GetSleepTestPoints enum class EFlags : uint8 { - IsSensor = 1 << 0, ///< If this object is a sensor. A sensor will receive collision callbacks, but will not cause any collision responses and can be used as a trigger volume. - SensorDetectsStatic = 1 << 1, ///< If this sensor detects static objects entering it. - IsInBroadPhase = 1 << 2, ///< Set this bit to indicate that the body is in the broadphase - InvalidateContactCache = 1 << 3, ///< Set this bit to indicate that all collision caches for this body are invalid, will be reset the next simulation step. - UseManifoldReduction = 1 << 4, ///< Set this bit to indicate that this body can use manifold reduction (if PhysicsSettings::mUseManifoldReduction is true) + IsSensor = 1 << 0, ///< If this object is a sensor. A sensor will receive collision callbacks, but will not cause any collision responses and can be used as a trigger volume. + CollideKinematicVsNonDynamic = 1 << 1, ///< If kinematic objects can generate contact points against other kinematic or static objects. + IsInBroadPhase = 1 << 2, ///< Set this bit to indicate that the body is in the broadphase + InvalidateContactCache = 1 << 3, ///< Set this bit to indicate that all collision caches for this body are invalid, will be reset the next simulation step. + UseManifoldReduction = 1 << 4, ///< Set this bit to indicate that this body can use manifold reduction (if PhysicsSettings::mUseManifoldReduction is true) + ApplyGyroscopicForce = 1 << 5, ///< Set this bit to indicate that the gyroscopic force should be applied to this body (aka Dzhanibekov effect, see https://en.wikipedia.org/wiki/Tennis_racket_theorem) + EnhancedInternalEdgeRemoval = 1 << 6, ///< Set this bit to indicate that enhanced internal edge removal should be used for this body (see BodyCreationSettings::mEnhancedInternalEdgeRemoval) }; // 16 byte aligned @@ -350,14 +378,9 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public atomic mFlags = 0; ///< See EFlags for possible flags // 122 bytes up to here (64-bit mode, single precision, 16-bit ObjectLayer) - -#if JPH_CPU_ADDRESS_BITS == 32 - // Padding for mShape, mMotionProperties, mCollisionGroup.mGroupFilter being 4 instead of 8 bytes in 32 bit mode - uint8 mPadding[12]; -#endif }; -static_assert(sizeof(Body) == JPH_IF_SINGLE_PRECISION_ELSE(128, 160), "Body size is incorrect"); +static_assert(JPH_CPU_ADDRESS_BITS != 64 || sizeof(Body) == JPH_IF_SINGLE_PRECISION_ELSE(128, 160), "Body size is incorrect"); static_assert(alignof(Body) == JPH_RVECTOR_ALIGNMENT, "Body should properly align"); JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Body/Body.inl b/Dependencies/Jolt/Jolt/Physics/Body/Body.inl index 439a2fbc..51bc6487 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/Body.inl +++ b/Dependencies/Jolt/Jolt/Physics/Body/Body.inl @@ -27,29 +27,20 @@ RMat44 Body::GetInverseCenterOfMassTransform() const return RMat44::sInverseRotationTranslation(mRotation, mPosition); } -inline static bool sIsValidSensorBodyPair(const Body &inSensor, const Body &inOther) -{ - // If the sensor is not an actual sensor then this is not a valid pair - if (!inSensor.IsSensor()) - return false; - - if (inSensor.SensorDetectsStatic()) - return !inOther.IsDynamic(); // If the other body is dynamic, the pair will be handled when the bodies are swapped, otherwise we'll detect the collision twice - else - return inOther.IsKinematic(); // Only kinematic bodies are valid -} - inline bool Body::sFindCollidingPairsCanCollide(const Body &inBody1, const Body &inBody2) { // First body should never be a soft body JPH_ASSERT(!inBody1.IsSoftBody()); // One of these conditions must be true + // - We always allow detecting collisions between kinematic and non-dynamic bodies // - One of the bodies must be dynamic to collide - // - A sensor can collide with non-dynamic bodies - if ((!inBody1.IsDynamic() && !inBody2.IsDynamic()) - && !sIsValidSensorBodyPair(inBody1, inBody2) - && !sIsValidSensorBodyPair(inBody2, inBody1)) + // - A kinematic object can collide with a sensor + if (!inBody1.GetCollideKinematicVsNonDynamic() + && !inBody2.GetCollideKinematicVsNonDynamic() + && (!inBody1.IsDynamic() && !inBody2.IsDynamic()) + && !(inBody1.IsKinematic() && inBody2.IsSensor()) + && !(inBody2.IsKinematic() && inBody1.IsSensor())) return false; // Check that body 1 is active @@ -58,9 +49,9 @@ inline bool Body::sFindCollidingPairsCanCollide(const Body &inBody1, const Body // If the pair A, B collides we need to ensure that the pair B, A does not collide or else we will handle the collision twice. // If A is the same body as B we don't want to collide (1) - // If A is dynamic and B is static we should collide (2) - // If A is dynamic / kinematic and B is dynamic / kinematic we should only collide if (kinematic vs kinematic is ruled out by the if above) - // - A is active and B is not yet active (3) + // If A is dynamic / kinematic and B is static we should collide (2) + // If A is dynamic / kinematic and B is dynamic / kinematic we should only collide if + // - A is active and B is not active (3) // - A is active and B will become active during this simulation step (4) // - A is active and B is active, we require a condition that makes A, B collide and B, A not (5) // @@ -80,7 +71,7 @@ inline bool Body::sFindCollidingPairsCanCollide(const Body &inBody1, const Body return false; JPH_ASSERT(inBody1.GetID() != inBody2.GetID(), "Read the comment above, A and B are the same body which should not be possible!"); - // Bodies in the same group don't collide + // Check collision group filter if (!inBody1.GetCollisionGroup().CanCollide(inBody2.GetCollisionGroup())) return false; @@ -196,7 +187,7 @@ void Body::GetSleepTestPoints(RVec3 *outPoints) const } } -void Body::ResetSleepTestSpheres() +void Body::ResetSleepTimer() { RVec3 points[3]; GetSleepTestPoints(points); diff --git a/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.cpp b/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.cpp index f1a6fb8f..9b6d3f92 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.cpp @@ -25,9 +25,11 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(BodyCreationSettings) JPH_ADD_ENUM_ATTRIBUTE(BodyCreationSettings, mAllowedDOFs) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mAllowDynamicOrKinematic) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mIsSensor) - JPH_ADD_ATTRIBUTE(BodyCreationSettings, mSensorDetectsStatic) + JPH_ADD_ATTRIBUTE_WITH_ALIAS(BodyCreationSettings, mCollideKinematicVsNonDynamic, "mSensorDetectsStatic") // This is the old name to keep backwards compatibility JPH_ADD_ATTRIBUTE(BodyCreationSettings, mUseManifoldReduction) + JPH_ADD_ATTRIBUTE(BodyCreationSettings, mApplyGyroscopicForce) JPH_ADD_ENUM_ATTRIBUTE(BodyCreationSettings, mMotionQuality) + JPH_ADD_ATTRIBUTE(BodyCreationSettings, mEnhancedInternalEdgeRemoval) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mAllowSleeping) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mFriction) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mRestitution) @@ -36,6 +38,8 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(BodyCreationSettings) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mMaxLinearVelocity) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mMaxAngularVelocity) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mGravityFactor) + JPH_ADD_ATTRIBUTE(BodyCreationSettings, mNumVelocityStepsOverride) + JPH_ADD_ATTRIBUTE(BodyCreationSettings, mNumPositionStepsOverride) JPH_ADD_ENUM_ATTRIBUTE(BodyCreationSettings, mOverrideMassProperties) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mInertiaMultiplier) JPH_ADD_ATTRIBUTE(BodyCreationSettings, mMassPropertiesOverride) @@ -53,9 +57,11 @@ void BodyCreationSettings::SaveBinaryState(StreamOut &inStream) const inStream.Write(mAllowedDOFs); inStream.Write(mAllowDynamicOrKinematic); inStream.Write(mIsSensor); - inStream.Write(mSensorDetectsStatic); + inStream.Write(mCollideKinematicVsNonDynamic); inStream.Write(mUseManifoldReduction); + inStream.Write(mApplyGyroscopicForce); inStream.Write(mMotionQuality); + inStream.Write(mEnhancedInternalEdgeRemoval); inStream.Write(mAllowSleeping); inStream.Write(mFriction); inStream.Write(mRestitution); @@ -64,6 +70,8 @@ void BodyCreationSettings::SaveBinaryState(StreamOut &inStream) const inStream.Write(mMaxLinearVelocity); inStream.Write(mMaxAngularVelocity); inStream.Write(mGravityFactor); + inStream.Write(mNumVelocityStepsOverride); + inStream.Write(mNumPositionStepsOverride); inStream.Write(mOverrideMassProperties); inStream.Write(mInertiaMultiplier); mMassPropertiesOverride.SaveBinaryState(inStream); @@ -81,9 +89,11 @@ void BodyCreationSettings::RestoreBinaryState(StreamIn &inStream) inStream.Read(mAllowedDOFs); inStream.Read(mAllowDynamicOrKinematic); inStream.Read(mIsSensor); - inStream.Read(mSensorDetectsStatic); + inStream.Read(mCollideKinematicVsNonDynamic); inStream.Read(mUseManifoldReduction); + inStream.Read(mApplyGyroscopicForce); inStream.Read(mMotionQuality); + inStream.Read(mEnhancedInternalEdgeRemoval); inStream.Read(mAllowSleeping); inStream.Read(mFriction); inStream.Read(mRestitution); @@ -92,6 +102,8 @@ void BodyCreationSettings::RestoreBinaryState(StreamIn &inStream) inStream.Read(mMaxLinearVelocity); inStream.Read(mMaxAngularVelocity); inStream.Read(mGravityFactor); + inStream.Read(mNumVelocityStepsOverride); + inStream.Read(mNumPositionStepsOverride); inStream.Read(mOverrideMassProperties); inStream.Read(mInertiaMultiplier); mMassPropertiesOverride.RestoreBinaryState(inStream); diff --git a/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.h b/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.h index 469c6fcc..a0395473 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.h +++ b/Dependencies/Jolt/Jolt/Physics/Body/BodyCreationSettings.h @@ -94,9 +94,11 @@ class JPH_EXPORT BodyCreationSettings EAllowedDOFs mAllowedDOFs = EAllowedDOFs::All; ///< Which degrees of freedom this body has (can be used to limit simulation to 2D) bool mAllowDynamicOrKinematic = false; ///< When this body is created as static, this setting tells the system to create a MotionProperties object so that the object can be switched to kinematic or dynamic bool mIsSensor = false; ///< If this body is a sensor. A sensor will receive collision callbacks, but will not cause any collision responses and can be used as a trigger volume. See description at Body::SetIsSensor. - bool mSensorDetectsStatic = false; ///< If this sensor detects static objects entering it. Note that the sensor must be kinematic and active for it to detect static objects. + bool mCollideKinematicVsNonDynamic = false; ///< If kinematic objects can generate contact points against other kinematic or static objects. See description at Body::SetCollideKinematicVsNonDynamic. bool mUseManifoldReduction = true; ///< If this body should use manifold reduction (see description at Body::SetUseManifoldReduction) + bool mApplyGyroscopicForce = false; ///< Set to indicate that the gyroscopic force should be applied to this body (aka Dzhanibekov effect, see https://en.wikipedia.org/wiki/Tennis_racket_theorem) EMotionQuality mMotionQuality = EMotionQuality::Discrete; ///< Motion quality, or how well it detects collisions when it has a high velocity + bool mEnhancedInternalEdgeRemoval = false; ///< Set to indicate that extra effort should be made to try to remove ghost contacts (collisions with internal edges of a mesh). This is more expensive but makes bodies move smoother over a mesh with convex edges. bool mAllowSleeping = true; ///< If this body can go to sleep or not float mFriction = 0.2f; ///< Friction of the body (dimensionless number, usually between 0 and 1, 0 = no friction, 1 = friction force equals force that presses the two bodies together). Note that bodies can have negative friction but the combined friction (see PhysicsSystem::SetCombineFriction) should never go below zero. float mRestitution = 0.0f; ///< Restitution of body (dimensionless number, usually between 0 and 1, 0 = completely inelastic collision response, 1 = completely elastic collision response). Note that bodies can have negative restitution but the combined restitution (see PhysicsSystem::SetCombineRestitution) should never go below zero. @@ -105,6 +107,8 @@ class JPH_EXPORT BodyCreationSettings float mMaxLinearVelocity = 500.0f; ///< Maximum linear velocity that this body can reach (m/s) float mMaxAngularVelocity = 0.25f * JPH_PI * 60.0f; ///< Maximum angular velocity that this body can reach (rad/s) float mGravityFactor = 1.0f; ///< Value to multiply gravity with for this body + uint mNumVelocityStepsOverride = 0; ///< Used only when this body is dynamic and colliding. Override for the number of solver velocity iterations to run, 0 means use the default in PhysicsSettings::mNumVelocitySteps. The number of iterations to use is the max of all contacts and constraints in the island. + uint mNumPositionStepsOverride = 0; ///< Used only when this body is dynamic and colliding. Override for the number of solver position iterations to run, 0 means use the default in PhysicsSettings::mNumPositionSteps. The number of iterations to use is the max of all contacts and constraints in the island. ///@name Mass properties of the body (by default calculated by the shape) EOverrideMassProperties mOverrideMassProperties = EOverrideMassProperties::CalculateMassAndInertia; ///< Determines how mMassPropertiesOverride will be used diff --git a/Dependencies/Jolt/Jolt/Physics/Body/BodyID.h b/Dependencies/Jolt/Jolt/Physics/Body/BodyID.h index 850b6fc4..f56d96b7 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/BodyID.h +++ b/Dependencies/Jolt/Jolt/Physics/Body/BodyID.h @@ -48,7 +48,7 @@ class BodyID /// Get sequence number of body. /// The sequence number can be used to check if a body ID with the same body index has been reused by another body. /// It is mainly used in multi threaded situations where a body is removed and its body index is immediately reused by a body created from another thread. - /// Functions querying the broadphase can (after aquiring a body lock) detect that the body has been removed (we assume that this won't happen more than 128 times in a row). + /// Functions querying the broadphase can (after acquiring a body lock) detect that the body has been removed (we assume that this won't happen more than 128 times in a row). inline uint8 GetSequenceNumber() const { return uint8(mID >> 24); diff --git a/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.cpp b/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.cpp index b870ea6c..3c98e1fd 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.cpp @@ -933,6 +933,31 @@ float BodyInterface::GetGravityFactor(const BodyID &inBodyID) const return 1.0f; } +void BodyInterface::SetUseManifoldReduction(const BodyID &inBodyID, bool inUseReduction) +{ + BodyLockWrite lock(*mBodyLockInterface, inBodyID); + if (lock.Succeeded()) + { + Body &body = lock.GetBody(); + if (body.GetUseManifoldReduction() != inUseReduction) + { + body.SetUseManifoldReduction(inUseReduction); + + // Flag collision cache invalid for this body + mBodyManager->InvalidateContactCacheForBody(body); + } + } +} + +bool BodyInterface::GetUseManifoldReduction(const BodyID &inBodyID) const +{ + BodyLockRead lock(*mBodyLockInterface, inBodyID); + if (lock.Succeeded()) + return lock.GetBody().GetUseManifoldReduction(); + else + return true; +} + TransformedShape BodyInterface::GetTransformedShape(const BodyID &inBodyID) const { BodyLockRead lock(*mBodyLockInterface, inBodyID); diff --git a/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.h b/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.h index 434cae97..3bdee830 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.h +++ b/Dependencies/Jolt/Jolt/Physics/Body/BodyInterface.h @@ -109,7 +109,7 @@ class JPH_EXPORT BodyInterface : public NonCopyable /// @return Created body ID or an invalid ID when out of bodies BodyID CreateAndAddSoftBody(const SoftBodyCreationSettings &inSettings, EActivation inActivationMode); - /// Broadphase add state handle, used to keep track of a batch while ading to the broadphase. + /// Broadphase add state handle, used to keep track of a batch while adding to the broadphase. using AddState = void *; ///@name Batch adding interface, see Broadphase for further documentation. @@ -250,6 +250,12 @@ class JPH_EXPORT BodyInterface : public NonCopyable float GetGravityFactor(const BodyID &inBodyID) const; ///@} + ///@name Manifold reduction + ///@{ + void SetUseManifoldReduction(const BodyID &inBodyID, bool inUseReduction); + bool GetUseManifoldReduction(const BodyID &inBodyID) const; + ///@} + /// Get transform and shape for this body, used to perform collision detection TransformedShape GetTransformedShape(const BodyID &inBodyID) const; diff --git a/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.cpp b/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.cpp index d3a77cb8..09995ac7 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.cpp @@ -198,10 +198,14 @@ Body *BodyManager::AllocateBody(const BodyCreationSettings &inBodyCreationSettin body->mMotionType = inBodyCreationSettings.mMotionType; if (inBodyCreationSettings.mIsSensor) body->SetIsSensor(true); - if (inBodyCreationSettings.mSensorDetectsStatic) - body->SetSensorDetectsStatic(true); + if (inBodyCreationSettings.mCollideKinematicVsNonDynamic) + body->SetCollideKinematicVsNonDynamic(true); if (inBodyCreationSettings.mUseManifoldReduction) body->SetUseManifoldReduction(true); + if (inBodyCreationSettings.mApplyGyroscopicForce) + body->SetApplyGyroscopicForce(true); + if (inBodyCreationSettings.mEnhancedInternalEdgeRemoval) + body->SetEnhancedInternalEdgeRemoval(true); SetBodyObjectLayerInternal(*body, inBodyCreationSettings.mObjectLayer); body->mObjectLayer = inBodyCreationSettings.mObjectLayer; body->mCollisionGroup = inBodyCreationSettings.mCollisionGroup; @@ -213,14 +217,16 @@ Body *BodyManager::AllocateBody(const BodyCreationSettings &inBodyCreationSettin mp->SetAngularDamping(inBodyCreationSettings.mAngularDamping); mp->SetMaxLinearVelocity(inBodyCreationSettings.mMaxLinearVelocity); mp->SetMaxAngularVelocity(inBodyCreationSettings.mMaxAngularVelocity); - mp->SetLinearVelocity(inBodyCreationSettings.mLinearVelocity); // Needs to happen after setting the max linear/angular velocity + mp->SetMassProperties(inBodyCreationSettings.mAllowedDOFs, inBodyCreationSettings.GetMassProperties()); + mp->SetLinearVelocity(inBodyCreationSettings.mLinearVelocity); // Needs to happen after setting the max linear/angular velocity and setting allowed DOFs mp->SetAngularVelocity(inBodyCreationSettings.mAngularVelocity); mp->SetGravityFactor(inBodyCreationSettings.mGravityFactor); + mp->SetNumVelocityStepsOverride(inBodyCreationSettings.mNumVelocityStepsOverride); + mp->SetNumPositionStepsOverride(inBodyCreationSettings.mNumPositionStepsOverride); mp->mMotionQuality = inBodyCreationSettings.mMotionQuality; mp->mAllowSleeping = inBodyCreationSettings.mAllowSleeping; JPH_IF_ENABLE_ASSERTS(mp->mCachedBodyType = body->mBodyType;) JPH_IF_ENABLE_ASSERTS(mp->mCachedMotionType = body->mMotionType;) - mp->SetMassProperties(inBodyCreationSettings.mAllowedDOFs, inBodyCreationSettings.GetMassProperties()); } // Position body @@ -554,7 +560,7 @@ void BodyManager::ActivateBodies(const BodyID *inBodyIDs, int inNumber) && body.mMotionProperties->mIndexInActiveBodies == Body::cInactiveIndex) { // Reset sleeping - body.ResetSleepTestSpheres(); + body.ResetSleepTimer(); AddBodyToActiveBodies(body); @@ -1073,12 +1079,24 @@ void BodyManager::Draw(const DrawSettings &inDrawSettings, const PhysicsSettings if (inDrawSettings.mDrawSoftBodyVertices) mp->DrawVertices(inRenderer, com); + if (inDrawSettings.mDrawSoftBodyVertexVelocities) + mp->DrawVertexVelocities(inRenderer, com); + if (inDrawSettings.mDrawSoftBodyEdgeConstraints) mp->DrawEdgeConstraints(inRenderer, com); + if (inDrawSettings.mDrawSoftBodyBendConstraints) + mp->DrawBendConstraints(inRenderer, com); + if (inDrawSettings.mDrawSoftBodyVolumeConstraints) mp->DrawVolumeConstraints(inRenderer, com); + if (inDrawSettings.mDrawSoftBodySkinConstraints) + mp->DrawSkinConstraints(inRenderer, com); + + if (inDrawSettings.mDrawSoftBodyLRAConstraints) + mp->DrawLRAConstraints(inRenderer, com); + if (inDrawSettings.mDrawSoftBodyPredictedBounds) mp->DrawPredictedBounds(inRenderer, com); } diff --git a/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.h b/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.h index d39c1369..a1b85967 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.h +++ b/Dependencies/Jolt/Jolt/Physics/Body/BodyManager.h @@ -230,8 +230,12 @@ class JPH_EXPORT BodyManager : public NonCopyable bool mDrawMassAndInertia = false; ///< Draw the mass and inertia (as the box equivalent) for each body bool mDrawSleepStats = false; ///< Draw stats regarding the sleeping algorithm of each body bool mDrawSoftBodyVertices = false; ///< Draw the vertices of soft bodies + bool mDrawSoftBodyVertexVelocities = false; ///< Draw the velocities of the vertices of soft bodies bool mDrawSoftBodyEdgeConstraints = false; ///< Draw the edge constraints of soft bodies + bool mDrawSoftBodyBendConstraints = false; ///< Draw the bend constraints of soft bodies bool mDrawSoftBodyVolumeConstraints = false; ///< Draw the volume constraints of soft bodies + bool mDrawSoftBodySkinConstraints = false; ///< Draw the skin constraints of soft bodies + bool mDrawSoftBodyLRAConstraints = false; ///< Draw the LRA constraints of soft bodies bool mDrawSoftBodyPredictedBounds = false; ///< Draw the predicted bounds of soft bodies }; diff --git a/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.cpp b/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.cpp index 07cb8af0..1acc274b 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.cpp @@ -30,7 +30,13 @@ void MotionProperties::SetMassProperties(EAllowedDOFs inAllowedDOFs, const MassP mInvMass = 1.0f / inMassProperties.mMass; } - if (allowed_rotation_axis == 0b111) + if (allowed_rotation_axis == 0) + { + // No rotation possible + mInvInertiaDiagonal = Vec3::sZero(); + mInertiaRotation = Quat::sIdentity(); + } + else { // Set inverse inertia Mat44 rotation; @@ -48,74 +54,6 @@ void MotionProperties::SetMassProperties(EAllowedDOFs inAllowedDOFs, const MassP mInertiaRotation = Quat::sIdentity(); } } - else if (allowed_rotation_axis == 0) - { - // No rotation possible - mInvInertiaDiagonal = Vec3::sZero(); - mInertiaRotation = Quat::sIdentity(); - } - else - { - uint num_allowed_rotation_axis = CountBits(allowed_rotation_axis); - if (num_allowed_rotation_axis == 1) - { - // We can only rotate around one axis so the inverse inertia is trivial to calculate - mInertiaRotation = Quat::sIdentity(); - mInvInertiaDiagonal = Vec3::sZero(); - for (int axis = 0; axis < 3; ++axis) - if ((allowed_rotation_axis & (1 << axis)) != 0) - mInvInertiaDiagonal.SetComponent(axis, 1.0f / inMassProperties.mInertia(axis, axis)); - } - else - { - JPH_ASSERT(num_allowed_rotation_axis == 2); - uint locked_axis = CountTrailingZeros(~allowed_rotation_axis); - - // Copy the mass properties so we can modify it - MassProperties copy = inMassProperties; - Mat44 &inertia = copy.mInertia; - - // Set the locked row and column to 0 - for (uint axis = 0; axis < 3; ++axis) - { - inertia(axis, locked_axis) = 0.0f; - inertia(locked_axis, axis) = 0.0f; - } - - // Set the diagonal entry to 1 - inertia(locked_axis, locked_axis) = 1.0f; - - // Decompose the inertia matrix, note that using a 2x2 matrix would have been more efficient - // but we happen to have a 3x3 matrix version lying around so we use that. - Mat44 rotation; - Vec3 diagonal; - if (copy.DecomposePrincipalMomentsOfInertia(rotation, diagonal)) - { - mInvInertiaDiagonal = diagonal.Reciprocal(); - mInertiaRotation = rotation.GetQuaternion(); - - // Now set the diagonal entry corresponding to the locked axis to 0 - for (uint axis = 0; axis < 3; ++axis) - if (abs(inertia.GetColumn3(locked_axis).Dot(rotation.GetColumn3(axis))) > 0.999f) - { - mInvInertiaDiagonal.SetComponent(axis, 0.0f); - break; - } - - // Check that we placed a zero - JPH_ASSERT(Vec3::sEquals(mInvInertiaDiagonal, Vec3::sZero()).TestAnyXYZTrue()); - } - else - { - // Failed! Fall back to inaccurate version. - mInertiaRotation = Quat::sIdentity(); - mInvInertiaDiagonal = Vec3::sZero(); - for (uint axis = 0; axis < 3; ++axis) - if (axis != locked_axis) - mInvInertiaDiagonal.SetComponent(axis, 1.0f / inertia.GetColumn3(axis).Length()); - } - } - } JPH_ASSERT(mInvMass != 0.0f || mInvInertiaDiagonal != Vec3::sZero(), "Can't lock all axes, use a static body for this. This will crash with a division by zero later!"); } diff --git a/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.h b/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.h index f1e2dc68..2ed81484 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.h +++ b/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.h @@ -43,19 +43,19 @@ class JPH_EXPORT MotionProperties inline Vec3 GetLinearVelocity() const { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::Read)); return mLinearVelocity; } /// Set world space linear velocity of the center of mass - void SetLinearVelocity(Vec3Arg inLinearVelocity) { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::ReadWrite)); JPH_ASSERT(inLinearVelocity.Length() <= mMaxLinearVelocity); mLinearVelocity = inLinearVelocity; } + void SetLinearVelocity(Vec3Arg inLinearVelocity) { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::ReadWrite)); JPH_ASSERT(inLinearVelocity.Length() <= mMaxLinearVelocity); mLinearVelocity = LockTranslation(inLinearVelocity); } /// Set world space linear velocity of the center of mass, will make sure the value is clamped against the maximum linear velocity - void SetLinearVelocityClamped(Vec3Arg inLinearVelocity) { mLinearVelocity = inLinearVelocity; ClampLinearVelocity(); } + void SetLinearVelocityClamped(Vec3Arg inLinearVelocity) { mLinearVelocity = LockTranslation(inLinearVelocity); ClampLinearVelocity(); } /// Get world space angular velocity of the center of mass inline Vec3 GetAngularVelocity() const { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::Read)); return mAngularVelocity; } /// Set world space angular velocity of the center of mass - void SetAngularVelocity(Vec3Arg inAngularVelocity) { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::ReadWrite)); JPH_ASSERT(inAngularVelocity.Length() <= mMaxAngularVelocity); mAngularVelocity = inAngularVelocity; } + void SetAngularVelocity(Vec3Arg inAngularVelocity) { JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::ReadWrite)); JPH_ASSERT(inAngularVelocity.Length() <= mMaxAngularVelocity); mAngularVelocity = LockAngular(inAngularVelocity); } /// Set world space angular velocity of the center of mass, will make sure the value is clamped against the maximum angular velocity - void SetAngularVelocityClamped(Vec3Arg inAngularVelocity) { mAngularVelocity = inAngularVelocity; ClampAngularVelocity(); } + void SetAngularVelocityClamped(Vec3Arg inAngularVelocity) { mAngularVelocity = LockAngular(inAngularVelocity); ClampAngularVelocity(); } /// Set velocity of body such that it will be rotate/translate by inDeltaPosition/Rotation in inDeltaTime seconds. inline void MoveKinematic(Vec3Arg inDeltaPosition, QuatArg inDeltaRotation, float inDeltaTime); @@ -110,7 +110,7 @@ class JPH_EXPORT MotionProperties /// Set the inverse inertia tensor in local space by setting the diagonal and the rotation: \f$I_{body}^{-1} = R \: D \: R^{-1}\f$. /// Note that mass and inertia are linearly related (e.g. inertia of a sphere with mass m and radius r is \f$2/5 \: m \: r^2\f$). /// If you change inertia, mass should probably change as well. See MassProperties::ScaleToMass. - /// If you don't allow all rotational degrees of freedom, make sure that the corresponding diagonal elements are zero (see EAllowedDOFs). + /// If all your rotation degrees of freedom are restricted, make sure this is zero (see EAllowedDOFs). void SetInverseInertia(Vec3Arg inDiagonal, QuatArg inRot) { mInvInertiaDiagonal = inDiagonal; mInertiaRotation = inRot; } /// Get inverse inertia matrix (\f$I_{body}^{-1}\f$). Will be a matrix of zeros for a static or kinematic object. @@ -128,10 +128,10 @@ class JPH_EXPORT MotionProperties /// Velocity of point inPoint (in center of mass space, e.g. on the surface of the body) of the body (unit: m/s) JPH_INLINE Vec3 GetPointVelocityCOM(Vec3Arg inPointRelativeToCOM) const { return mLinearVelocity + mAngularVelocity.Cross(inPointRelativeToCOM); } - // Get the total amount of force applied to the center of mass this time step (through Body::AddForce calls). Note that it will reset to zero after PhysicsSimulation::Update. + // Get the total amount of force applied to the center of mass this time step (through Body::AddForce calls). Note that it will reset to zero after PhysicsSystem::Update. JPH_INLINE Vec3 GetAccumulatedForce() const { return Vec3::sLoadFloat3Unsafe(mForce); } - // Get the total amount of torque applied to the center of mass this time step (through Body::AddForce/Body::AddTorque calls). Note that it will reset to zero after PhysicsSimulation::Update. + // Get the total amount of torque applied to the center of mass this time step (through Body::AddForce/Body::AddTorque calls). Note that it will reset to zero after PhysicsSystem::Update. JPH_INLINE Vec3 GetAccumulatedTorque() const { return Vec3::sLoadFloat3Unsafe(mTorque); } // Reset the total accumulated force, not that this will be done automatically after every time step. @@ -140,14 +140,48 @@ class JPH_EXPORT MotionProperties // Reset the total accumulated torque, not that this will be done automatically after every time step. JPH_INLINE void ResetTorque() { mTorque = Float3(0, 0, 0); } + // Reset the current velocity and accumulated force and torque. + JPH_INLINE void ResetMotion() + { + JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::ReadWrite)); + mLinearVelocity = mAngularVelocity = Vec3::sZero(); + mForce = mTorque = Float3(0, 0, 0); + } + + /// Returns a vector where the linear components that are not allowed by mAllowedDOFs are set to 0 and the rest to 0xffffffff + JPH_INLINE UVec4 GetLinearDOFsMask() const + { + UVec4 mask(uint32(EAllowedDOFs::TranslationX), uint32(EAllowedDOFs::TranslationY), uint32(EAllowedDOFs::TranslationZ), 0); + return UVec4::sEquals(UVec4::sAnd(UVec4::sReplicate(uint32(mAllowedDOFs)), mask), mask); + } + /// Takes a translation vector inV and returns a vector where the components that are not allowed by mAllowedDOFs are set to 0 - JPH_INLINE Vec3 LockTranslation(Vec3Arg inV) + JPH_INLINE Vec3 LockTranslation(Vec3Arg inV) const { - uint32 allowed_dofs = uint32(mAllowedDOFs); - UVec4 allowed_dofs_mask = UVec4(allowed_dofs << 31, allowed_dofs << 30, allowed_dofs << 29, 0).ArithmeticShiftRight<31>(); - return Vec3::sAnd(inV, Vec3(allowed_dofs_mask.ReinterpretAsFloat())); + return Vec3::sAnd(inV, Vec3(GetLinearDOFsMask().ReinterpretAsFloat())); } + /// Returns a vector where the angular components that are not allowed by mAllowedDOFs are set to 0 and the rest to 0xffffffff + JPH_INLINE UVec4 GetAngularDOFsMask() const + { + UVec4 mask(uint32(EAllowedDOFs::RotationX), uint32(EAllowedDOFs::RotationY), uint32(EAllowedDOFs::RotationZ), 0); + return UVec4::sEquals(UVec4::sAnd(UVec4::sReplicate(uint32(mAllowedDOFs)), mask), mask); + } + + /// Takes an angular velocity / torque vector inV and returns a vector where the components that are not allowed by mAllowedDOFs are set to 0 + JPH_INLINE Vec3 LockAngular(Vec3Arg inV) const + { + return Vec3::sAnd(inV, Vec3(GetAngularDOFsMask().ReinterpretAsFloat())); + } + + /// Used only when this body is dynamic and colliding. Override for the number of solver velocity iterations to run, 0 means use the default in PhysicsSettings::mNumVelocitySteps. The number of iterations to use is the max of all contacts and constraints in the island. + void SetNumVelocityStepsOverride(uint inN) { JPH_ASSERT(inN < 256); mNumVelocityStepsOverride = uint8(inN); } + uint GetNumVelocityStepsOverride() const { return mNumVelocityStepsOverride; } + + /// Used only when this body is dynamic and colliding. Override for the number of solver position iterations to run, 0 means use the default in PhysicsSettings::mNumPositionSteps. The number of iterations to use is the max of all contacts and constraints in the island. + void SetNumPositionStepsOverride(uint inN) { JPH_ASSERT(inN < 256); mNumPositionStepsOverride = uint8(inN); } + uint GetNumPositionStepsOverride() const { return mNumPositionStepsOverride; } + //////////////////////////////////////////////////////////// // FUNCTIONS BELOW THIS LINE ARE FOR INTERNAL USE ONLY //////////////////////////////////////////////////////////// @@ -160,6 +194,9 @@ class JPH_EXPORT MotionProperties inline void SubAngularVelocityStep(Vec3Arg inAngularVelocityChange) { JPH_DET_LOG("SubAngularVelocityStep: " << inAngularVelocityChange); JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::ReadWrite)); mAngularVelocity -= inAngularVelocityChange; JPH_ASSERT(!mAngularVelocity.IsNaN()); } ///@} + /// Apply the gyroscopic force (aka Dzhanibekov effect, see https://en.wikipedia.org/wiki/Tennis_racket_theorem) + inline void ApplyGyroscopicForceInternal(QuatArg inBodyRotation, float inDeltaTime); + /// Apply all accumulated forces, torques and drag (should only be called by the PhysicsSystem) inline void ApplyForceTorqueAndDragInternal(QuatArg inBodyRotation, Vec3Arg inGravity, float inDeltaTime); @@ -219,6 +256,8 @@ class JPH_EXPORT MotionProperties EMotionQuality mMotionQuality; ///< Motion quality, or how well it detects collisions when it has a high velocity bool mAllowSleeping; ///< If this body can go to sleep EAllowedDOFs mAllowedDOFs = EAllowedDOFs::All; ///< Allowed degrees of freedom for this body + uint8 mNumVelocityStepsOverride = 0; ///< Used only when this body is dynamic and colliding. Override for the number of solver velocity iterations to run, 0 means use the default in PhysicsSettings::mNumVelocitySteps. The number of iterations to use is the max of all contacts and constraints in the island. + uint8 mNumPositionStepsOverride = 0; ///< Used only when this body is dynamic and colliding. Override for the number of solver position iterations to run, 0 means use the default in PhysicsSettings::mNumPositionSteps. The number of iterations to use is the max of all contacts and constraints in the island. // 3rd cache line (least frequently used) // 4 byte aligned (or 8 byte if running in double precision) diff --git a/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.inl b/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.inl index 22cbfdee..1fb044ef 100644 --- a/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.inl +++ b/Dependencies/Jolt/Jolt/Physics/Body/MotionProperties.inl @@ -14,13 +14,13 @@ void MotionProperties::MoveKinematic(Vec3Arg inDeltaPosition, QuatArg inDeltaRot JPH_ASSERT(mCachedMotionType != EMotionType::Static); // Calculate required linear velocity - mLinearVelocity = inDeltaPosition / inDeltaTime; + mLinearVelocity = LockTranslation(inDeltaPosition / inDeltaTime); // Calculate required angular velocity Vec3 axis; float angle; inDeltaRotation.GetAxisAngle(axis, angle); - mAngularVelocity = axis * (angle / inDeltaTime); + mAngularVelocity = LockAngular(axis * (angle / inDeltaTime)); } void MotionProperties::ClampLinearVelocity() @@ -62,15 +62,59 @@ Mat44 MotionProperties::GetInverseInertiaForRotation(Mat44Arg inRotation) const Mat44 rotation = inRotation.Multiply3x3(Mat44::sRotation(mInertiaRotation)); Mat44 rotation_mul_scale_transposed(mInvInertiaDiagonal.SplatX() * rotation.GetColumn4(0), mInvInertiaDiagonal.SplatY() * rotation.GetColumn4(1), mInvInertiaDiagonal.SplatZ() * rotation.GetColumn4(2), Vec4(0, 0, 0, 1)); - return rotation.Multiply3x3RightTransposed(rotation_mul_scale_transposed); + Mat44 inverse_inertia = rotation.Multiply3x3RightTransposed(rotation_mul_scale_transposed); + + // We need to mask out both the rows and columns of DOFs that are not allowed + Vec4 angular_dofs_mask = GetAngularDOFsMask().ReinterpretAsFloat(); + inverse_inertia.SetColumn4(0, Vec4::sAnd(inverse_inertia.GetColumn4(0), Vec4::sAnd(angular_dofs_mask, angular_dofs_mask.SplatX()))); + inverse_inertia.SetColumn4(1, Vec4::sAnd(inverse_inertia.GetColumn4(1), Vec4::sAnd(angular_dofs_mask, angular_dofs_mask.SplatY()))); + inverse_inertia.SetColumn4(2, Vec4::sAnd(inverse_inertia.GetColumn4(2), Vec4::sAnd(angular_dofs_mask, angular_dofs_mask.SplatZ()))); + + return inverse_inertia; } Vec3 MotionProperties::MultiplyWorldSpaceInverseInertiaByVector(QuatArg inBodyRotation, Vec3Arg inV) const { JPH_ASSERT(mCachedMotionType == EMotionType::Dynamic); + // Mask out columns of DOFs that are not allowed + Vec3 angular_dofs_mask = Vec3(GetAngularDOFsMask().ReinterpretAsFloat()); + Vec3 v = Vec3::sAnd(inV, angular_dofs_mask); + + // Multiply vector by inverse inertia Mat44 rotation = Mat44::sRotation(inBodyRotation * mInertiaRotation); - return rotation.Multiply3x3(mInvInertiaDiagonal * rotation.Multiply3x3Transposed(inV)); + Vec3 result = rotation.Multiply3x3(mInvInertiaDiagonal * rotation.Multiply3x3Transposed(v)); + + // Mask out rows of DOFs that are not allowed + return Vec3::sAnd(result, angular_dofs_mask); +} + +void MotionProperties::ApplyGyroscopicForceInternal(QuatArg inBodyRotation, float inDeltaTime) +{ + JPH_ASSERT(BodyAccess::sCheckRights(BodyAccess::sVelocityAccess, BodyAccess::EAccess::ReadWrite)); + JPH_ASSERT(mCachedBodyType == EBodyType::RigidBody); + JPH_ASSERT(mCachedMotionType == EMotionType::Dynamic); + + // Calculate local space inertia tensor (a diagonal in local space) + UVec4 is_zero = Vec3::sEquals(mInvInertiaDiagonal, Vec3::sZero()); + Vec3 denominator = Vec3::sSelect(mInvInertiaDiagonal, Vec3::sReplicate(1.0f), is_zero); + Vec3 nominator = Vec3::sSelect(Vec3::sReplicate(1.0f), Vec3::sZero(), is_zero); + Vec3 local_inertia = nominator / denominator; // Avoid dividing by zero, inertia in this axis will be zero + + // Calculate local space angular momentum + Quat inertia_space_to_world_space = inBodyRotation * mInertiaRotation; + Vec3 local_angular_velocity = inertia_space_to_world_space.Conjugated() * mAngularVelocity; + Vec3 local_momentum = local_inertia * local_angular_velocity; + + // The gyroscopic force applies a torque: T = -w x I w where w is angular velocity and I the inertia tensor + // Calculate the new angular momentum by applying the gyroscopic force and make sure the new magnitude is the same as the old one + // to avoid introducing energy into the system due to the Euler step + Vec3 new_local_momentum = local_momentum - inDeltaTime * local_angular_velocity.Cross(local_momentum); + float new_local_momentum_len_sq = new_local_momentum.LengthSq(); + new_local_momentum = new_local_momentum_len_sq > 0.0f? new_local_momentum * sqrt(local_momentum.LengthSq() / new_local_momentum_len_sq) : Vec3::sZero(); + + // Convert back to world space angular velocity + mAngularVelocity = inertia_space_to_world_space * (mInvInertiaDiagonal * new_local_momentum); } void MotionProperties::ApplyForceTorqueAndDragInternal(QuatArg inBodyRotation, Vec3Arg inGravity, float inDeltaTime) diff --git a/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.cpp b/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.cpp index 0dc25da1..b4b12ecf 100644 --- a/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.cpp @@ -19,7 +19,7 @@ JPH_NAMESPACE_BEGIN -CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, PhysicsSystem *inSystem) : +CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, uint64 inUserData, PhysicsSystem *inSystem) : CharacterBase(inSettings, inSystem), mBackFaceMode(inSettings->mBackFaceMode), mPredictiveContactDistance(inSettings->mPredictiveContactDistance), @@ -33,7 +33,8 @@ CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, R mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed), mShapeOffset(inSettings->mShapeOffset), mPosition(inPosition), - mRotation(inRotation) + mRotation(inRotation), + mUserData(inUserData) { // Copy settings SetMaxStrength(inSettings->mMaxStrength); @@ -96,6 +97,7 @@ void CharacterVirtual::sFillContactProperties(const CharacterVirtual *inCharacte outContact.mBodyB = inResult.mBodyID2; outContact.mSubShapeIDB = inResult.mSubShapeID2; outContact.mMotionTypeB = inBody.GetMotionType(); + outContact.mIsSensorB = inBody.IsSensor(); outContact.mUserData = inBody.GetUserData(); outContact.mMaterial = inCollector.GetContext()->GetMaterial(inResult.mSubShapeID2); } @@ -159,16 +161,10 @@ void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResu BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2); if (lock.SucceededAndIsInBroadPhase()) { - // We don't collide with sensors, note that you should set up your collision layers so that sensors don't collide with the character. - // Rejecting the contact here means a lot of extra work for the collision detection system. - const Body &body = lock.GetBody(); - if (!body.IsSensor()) - { - mContacts.emplace_back(); - Contact &contact = mContacts.back(); - sFillContactProperties(mCharacter, contact, body, mUp, mBaseOffset, *this, inResult); - contact.mFraction = 0.0f; - } + mContacts.emplace_back(); + Contact &contact = mContacts.back(); + sFillContactProperties(mCharacter, contact, lock.GetBody(), mUp, mBaseOffset, *this, inResult); + contact.mFraction = 0.0f; } } @@ -193,8 +189,7 @@ void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inRes if (!lock.SucceededAndIsInBroadPhase()) return; - // We don't collide with sensors, note that you should set up your collision layers so that sensors don't collide with the character. - // Rejecting the contact here means a lot of extra work for the collision detection system. + // Sweeps don't result in OnContactAdded callbacks so we can ignore sensors here const Body &body = lock.GetBody(); if (body.IsSensor()) return; @@ -444,6 +439,10 @@ bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstrain mListener->OnContactAdded(this, contact.mBodyB, contact.mSubShapeIDB, contact.mPosition, -contact.mContactNormal, settings); contact.mCanPushCharacter = settings.mCanPushCharacter; + // We don't have any further interaction with sensors beyond an OnContactAdded notification + if (contact.mIsSensorB) + return false; + // If body B cannot receive an impulse, we're done if (!settings.mCanReceiveImpulses || contact.mMotionTypeB != EMotionType::Dynamic) return true; @@ -642,7 +641,7 @@ void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, f // Get the normal of the plane we're hitting Vec3 plane_normal = constraint->mPlane.GetNormal(); - // If we're hitting a steep slope we cancel the velocity towards the slope first so that we don't end up slidinng up the slope + // If we're hitting a steep slope we cancel the velocity towards the slope first so that we don't end up sliding up the slope // (we may hit the slope before the vertical wall constraint we added which will result in a small movement up causing jitter in the character movement) if (constraint->mIsSteepSlope) { @@ -762,7 +761,7 @@ void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck, && c.mDistance < mCollisionTolerance && (inSkipContactVelocityCheck || c.mSurfaceNormal.Dot(mLinearVelocity - c.mLinearVelocity) <= 1.0e-4f)) { - if (ValidateContact(c)) + if (ValidateContact(c) && !c.mIsSensorB) c.mHadCollision = true; else c.mWasDiscarded = true; @@ -1257,7 +1256,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i #endif // JPH_DEBUG_RENDERER // Move down towards the floor. - // Note that we travel the same amount down as we travelled up with the specified extra + // Note that we travel the same amount down as we traveled up with the specified extra Vec3 down = -up + inStepDownExtra; if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter)) return false; // No floor found, we're in mid air, cancel stair walk diff --git a/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.h b/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.h index 37e781c6..a0a126ee 100644 --- a/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.h +++ b/Dependencies/Jolt/Jolt/Physics/Character/CharacterVirtual.h @@ -32,7 +32,7 @@ class JPH_EXPORT CharacterVirtualSettings : public CharacterBaseSettings ///@name Movement settings EBackFaceMode mBackFaceMode = EBackFaceMode::CollideWithBackFaces; ///< When colliding with back faces, the character will not be able to move through back facing triangles. Use this if you have triangles that need to collide on both sides. - float mPredictiveContactDistance = 0.1f; ///< How far to scan outside of the shape for predictive contacts. A value of 0 will most likely cause the character to get stuck as it properly calculate a sliding direction anymore. A value that's too high will cause ghost collisions. + float mPredictiveContactDistance = 0.1f; ///< How far to scan outside of the shape for predictive contacts. A value of 0 will most likely cause the character to get stuck as it cannot properly calculate a sliding direction anymore. A value that's too high will cause ghost collisions. uint mMaxCollisionIterations = 5; ///< Max amount of collision loops uint mMaxConstraintIterations = 15; ///< How often to try stepping in the constraint solving float mMinTimeRemaining = 1.0e-4f; ///< Early out condition: If this much time is left to simulate we are done @@ -65,7 +65,7 @@ class JPH_EXPORT CharacterContactListener /// Checks if a character can collide with specified body. Return true if the contact is valid. virtual bool OnContactValidate(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2) { return true; } - /// Called whenever the character collides with a body. Returns true if the contact can push the character. + /// Called whenever the character collides with a body. /// @param inCharacter Character that is being solved /// @param inBodyID2 Body ID of body that is being hit /// @param inSubShapeID2 Sub shape ID of shape that is being hit @@ -101,8 +101,12 @@ class JPH_EXPORT CharacterVirtual : public CharacterBase /// @param inSettings The settings for the character /// @param inPosition Initial position for the character /// @param inRotation Initial rotation for the character (usually only around the up-axis) + /// @param inUserData Application specific value /// @param inSystem Physics system that this character will be added to later - CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, PhysicsSystem *inSystem); + CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, uint64 inUserData, PhysicsSystem *inSystem); + + /// Constructor without user data + CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, PhysicsSystem *inSystem) : CharacterVirtual(inSettings, inPosition, inRotation, 0, inSystem) { } /// Set the contact listener void SetListener(CharacterContactListener *inListener) { mListener = inListener; } @@ -167,6 +171,10 @@ class JPH_EXPORT CharacterVirtual : public CharacterBase Vec3 GetShapeOffset() const { return mShapeOffset; } void SetShapeOffset(Vec3Arg inShapeOffset) { mShapeOffset = inShapeOffset; } + /// Access to the user data, can be used for anything by the application + uint64 GetUserData() const { return mUserData; } + void SetUserData(uint64 inUserData) { mUserData = inUserData; } + /// This function can be called prior to calling Update() to convert a desired velocity into a velocity that won't make the character move further onto steep slopes. /// This velocity can then be set on the character using SetLinearVelocity() /// @param inDesiredVelocity Velocity to clamp against steep walls @@ -187,7 +195,7 @@ class JPH_EXPORT CharacterVirtual : public CharacterBase /// This function will return true if the character has moved into a slope that is too steep (e.g. a vertical wall). /// You would call WalkStairs to attempt to step up stairs. - /// @param inLinearVelocity The linear velocity that the player desired. This is used to determine if we're pusing into a step. + /// @param inLinearVelocity The linear velocity that the player desired. This is used to determine if we're pushing into a step. bool CanWalkStairs(Vec3Arg inLinearVelocity) const; /// When stair walking is needed, you can call the WalkStairs function to cast up, forward and down again to try to find a valid position @@ -299,6 +307,7 @@ class JPH_EXPORT CharacterVirtual : public CharacterBase BodyID mBodyB; ///< ID of body we're colliding with SubShapeID mSubShapeIDB; ///< Sub shape ID of body we're colliding with EMotionType mMotionTypeB; ///< Motion type of B, used to determine the priority of the contact + bool mIsSensorB; ///< If B is a sensor uint64 mUserData; ///< User data of B const PhysicsMaterial * mMaterial; ///< Material of B bool mHadCollision = false; ///< If the character actually collided with the contact (can be false if a predictive contact never becomes a real one) @@ -449,7 +458,7 @@ class JPH_EXPORT CharacterVirtual : public CharacterBase // Movement settings EBackFaceMode mBackFaceMode; // When colliding with back faces, the character will not be able to move through back facing triangles. Use this if you have triangles that need to collide on both sides. - float mPredictiveContactDistance; // How far to scan outside of the shape for predictive contacts. A value of 0 will most likely cause the character to get stuck as it properly calculate a sliding direction anymore. A value that's too high will cause ghost collisions. + float mPredictiveContactDistance; // How far to scan outside of the shape for predictive contacts. A value of 0 will most likely cause the character to get stuck as it cannot properly calculate a sliding direction anymore. A value that's too high will cause ghost collisions. uint mMaxCollisionIterations; // Max amount of collision loops uint mMaxConstraintIterations; // How often to try stepping in the constraint solving float mMinTimeRemaining; // Early out condition: If this much time is left to simulate we are done @@ -485,6 +494,9 @@ class JPH_EXPORT CharacterVirtual : public CharacterBase // Remember if we exceeded the maximum number of hits and had to remove similar contacts mutable bool mMaxHitsExceeded = false; + + // User data, can be used for anything by the application + uint64 mUserData = 0; }; JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhase.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhase.h index 6e96a2d1..8b6506e9 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhase.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhase.h @@ -95,6 +95,9 @@ class JPH_EXPORT BroadPhase : public BroadPhaseQuery /// Same as BroadPhaseQuery::CastAABox but can be implemented in a way to take no broad phase locks. virtual void CastAABoxNoLock(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const = 0; + /// Get the bounding box of all objects in the broadphase + virtual AABox GetBounds() const = 0; + #ifdef JPH_TRACK_BROADPHASE_STATS /// Trace the collected broadphase stats in CSV form. /// This report can be used to judge and tweak the efficiency of the broadphase. diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp index 615bc28c..10cf30a2 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp @@ -300,4 +300,14 @@ void BroadPhaseBruteForce::FindCollidingPairs(BodyID *ioActiveBodies, int inNumA } } +AABox BroadPhaseBruteForce::GetBounds() const +{ + shared_lock lock(mMutex); + + AABox bounds; + for (BodyID b : mBodyIDs) + bounds.Encapsulate(mBodyManager->GetBody(b).GetWorldSpaceBounds()); + return bounds; +} + JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h index b24b5b32..c3e20f5c 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h @@ -28,6 +28,7 @@ class JPH_EXPORT BroadPhaseBruteForce final : public BroadPhase virtual void CastAABoxNoLock(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const override; virtual void CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const override; virtual void FindCollidingPairs(BodyID *ioActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, const ObjectVsBroadPhaseLayerFilter &inObjectVsBroadPhaseLayerFilter, const ObjectLayerPairFilter &inObjectLayerPairFilter, BodyPairCollector &ioPairCollector) const override; + virtual AABox GetBounds() const override; private: Array mBodyIDs; diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h index a0094d5d..bd22cc3a 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h @@ -44,6 +44,11 @@ class BroadPhaseLayer return mValue; } + JPH_INLINE Type GetValue() const + { + return mValue; + } + private: Type mValue; }; diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h new file mode 100644 index 00000000..15a894bb --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h @@ -0,0 +1,92 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +JPH_NAMESPACE_BEGIN + +/// BroadPhaseLayerInterface implementation. +/// This defines a mapping between object and broadphase layers. +/// This implementation works together with ObjectLayerPairFilterMask and ObjectVsBroadPhaseLayerFilterMask. +/// A broadphase layer is suitable for an object if its group & inGroupsToInclude is not zero and its group & inGroupsToExclude is zero. +/// The broadphase layers are iterated from lowest to highest value and the first one that matches is taken. If none match then it takes the last layer. +class BroadPhaseLayerInterfaceMask : public BroadPhaseLayerInterface +{ +public: + JPH_OVERRIDE_NEW_DELETE + + explicit BroadPhaseLayerInterfaceMask(uint inNumBroadPhaseLayers) + { + JPH_ASSERT(inNumBroadPhaseLayers > 0); + mMapping.resize(inNumBroadPhaseLayers); + +#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + mBroadPhaseLayerNames.resize(inNumBroadPhaseLayers, "Undefined"); +#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED + } + + // Configures a broadphase layer. + void ConfigureLayer(BroadPhaseLayer inBroadPhaseLayer, uint32 inGroupsToInclude, uint32 inGroupsToExclude) + { + JPH_ASSERT((BroadPhaseLayer::Type)inBroadPhaseLayer < (uint)mMapping.size()); + Mapping &m = mMapping[(BroadPhaseLayer::Type)inBroadPhaseLayer]; + m.mGroupsToInclude = inGroupsToInclude; + m.mGroupsToExclude = inGroupsToExclude; + } + + virtual uint GetNumBroadPhaseLayers() const override + { + return (uint)mMapping.size(); + } + + virtual BroadPhaseLayer GetBroadPhaseLayer(ObjectLayer inLayer) const override + { + // Try to find the first broadphase layer that matches + uint32 group = ObjectLayerPairFilterMask::sGetGroup(inLayer); + for (const Mapping &m : mMapping) + if ((group & m.mGroupsToInclude) != 0 && (group & m.mGroupsToExclude) == 0) + return BroadPhaseLayer(BroadPhaseLayer::Type(&m - mMapping.data())); + + // Fall back to the last broadphase layer + return BroadPhaseLayer(BroadPhaseLayer::Type(mMapping.size() - 1)); + } + + /// Returns true if an object layer should collide with a broadphase layer, this function is being called from ObjectVsBroadPhaseLayerFilterMask + inline bool ShouldCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2) const + { + uint32 mask = ObjectLayerPairFilterMask::sGetMask(inLayer1); + const Mapping &m = mMapping[(BroadPhaseLayer::Type)inLayer2]; + return &m == &mMapping.back() // Last layer may collide with anything + || (m.mGroupsToInclude & mask) != 0; // Mask allows it to collide with objects that could reside in this layer + } + +#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + void SetBroadPhaseLayerName(BroadPhaseLayer inLayer, const char *inName) + { + mBroadPhaseLayerNames[(BroadPhaseLayer::Type)inLayer] = inName; + } + + virtual const char * GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override + { + return mBroadPhaseLayerNames[(BroadPhaseLayer::Type)inLayer]; + } +#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED + +private: + struct Mapping + { + uint32 mGroupsToInclude = 0; + uint32 mGroupsToExclude = ~uint32(0); + }; + Array mMapping; + +#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + Array mBroadPhaseLayerNames; +#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h new file mode 100644 index 00000000..e777a085 --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h @@ -0,0 +1,64 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// BroadPhaseLayerInterface implementation. +/// This defines a mapping between object and broadphase layers. +/// This implementation uses a simple table +class BroadPhaseLayerInterfaceTable : public BroadPhaseLayerInterface +{ +public: + JPH_OVERRIDE_NEW_DELETE + + BroadPhaseLayerInterfaceTable(uint inNumObjectLayers, uint inNumBroadPhaseLayers) : + mNumBroadPhaseLayers(inNumBroadPhaseLayers) + { + mObjectToBroadPhase.resize(inNumObjectLayers, BroadPhaseLayer(0)); +#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + mBroadPhaseLayerNames.resize(inNumBroadPhaseLayers, "Undefined"); +#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED + } + + void MapObjectToBroadPhaseLayer(ObjectLayer inObjectLayer, BroadPhaseLayer inBroadPhaseLayer) + { + JPH_ASSERT((BroadPhaseLayer::Type)inBroadPhaseLayer < mNumBroadPhaseLayers); + mObjectToBroadPhase[inObjectLayer] = inBroadPhaseLayer; + } + + virtual uint GetNumBroadPhaseLayers() const override + { + return mNumBroadPhaseLayers; + } + + virtual BroadPhaseLayer GetBroadPhaseLayer(ObjectLayer inLayer) const override + { + return mObjectToBroadPhase[inLayer]; + } + +#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + void SetBroadPhaseLayerName(BroadPhaseLayer inLayer, const char *inName) + { + mBroadPhaseLayerNames[(BroadPhaseLayer::Type)inLayer] = inName; + } + + virtual const char * GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override + { + return mBroadPhaseLayerNames[(BroadPhaseLayer::Type)inLayer]; + } +#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED + +private: + uint mNumBroadPhaseLayers; + Array mObjectToBroadPhase; +#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + Array mBroadPhaseLayerNames; +#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp index f7bc8275..0c3d480b 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp @@ -579,6 +579,17 @@ void BroadPhaseQuadTree::FindCollidingPairs(BodyID *ioActiveBodies, int inNumAct } } +AABox BroadPhaseQuadTree::GetBounds() const +{ + // Prevent this from running in parallel with node deletion in FrameSync(), see notes there + shared_lock lock(mQueryLocks[mQueryLockIdx]); + + AABox bounds; + for (BroadPhaseLayer::Type l = 0; l < mNumLayers; ++l) + bounds.Encapsulate(mLayers[l].GetBounds()); + return bounds; +} + #ifdef JPH_TRACK_BROADPHASE_STATS void BroadPhaseQuadTree::ReportStats() diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h index 78e6b977..ae97c10f 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h @@ -41,6 +41,7 @@ class JPH_EXPORT BroadPhaseQuadTree final : public BroadPhase virtual void CastAABoxNoLock(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const override; virtual void CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const override; virtual void FindCollidingPairs(BodyID *ioActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, const ObjectVsBroadPhaseLayerFilter &inObjectVsBroadPhaseLayerFilter, const ObjectLayerPairFilter &inObjectLayerPairFilter, BodyPairCollector &ioPairCollector) const override; + virtual AABox GetBounds() const override; #ifdef JPH_TRACK_BROADPHASE_STATS virtual void ReportStats() override; #endif // JPH_TRACK_BROADPHASE_STATS diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h new file mode 100644 index 00000000..20305a5f --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h @@ -0,0 +1,35 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// Class that determines if an object layer can collide with a broadphase layer. +/// This implementation works together with BroadPhaseLayerInterfaceMask and ObjectLayerPairFilterMask +class ObjectVsBroadPhaseLayerFilterMask : public ObjectVsBroadPhaseLayerFilter +{ +public: + JPH_OVERRIDE_NEW_DELETE + +/// Constructor + ObjectVsBroadPhaseLayerFilterMask(const BroadPhaseLayerInterfaceMask &inBroadPhaseLayerInterface) : + mBroadPhaseLayerInterface(inBroadPhaseLayerInterface) + { + } + + /// Returns true if an object layer should collide with a broadphase layer + virtual bool ShouldCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2) const override + { + // Just defer to BroadPhaseLayerInterface + return mBroadPhaseLayerInterface.ShouldCollide(inLayer1, inLayer2); + } + +private: + const BroadPhaseLayerInterfaceMask &mBroadPhaseLayerInterface; +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h new file mode 100644 index 00000000..532ce6da --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h @@ -0,0 +1,66 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// Class that determines if an object layer can collide with a broadphase layer. +/// This implementation uses a table and constructs itself from an ObjectLayerPairFilter and a BroadPhaseLayerInterface. +class ObjectVsBroadPhaseLayerFilterTable : public ObjectVsBroadPhaseLayerFilter +{ +private: + /// Get which bit corresponds to the pair (inLayer1, inLayer2) + uint GetBit(ObjectLayer inLayer1, BroadPhaseLayer inLayer2) const + { + // Calculate at which bit the entry for this pair resides + return inLayer1 * mNumBroadPhaseLayers + (BroadPhaseLayer::Type)inLayer2; + } + +public: + JPH_OVERRIDE_NEW_DELETE + + /// Construct the table + /// @param inBroadPhaseLayerInterface The broad phase layer interface that maps object layers to broad phase layers + /// @param inNumBroadPhaseLayers Number of broad phase layers + /// @param inObjectLayerPairFilter The object layer pair filter that determines which object layers can collide + /// @param inNumObjectLayers Number of object layers + ObjectVsBroadPhaseLayerFilterTable(const BroadPhaseLayerInterface &inBroadPhaseLayerInterface, uint inNumBroadPhaseLayers, const ObjectLayerPairFilter &inObjectLayerPairFilter, uint inNumObjectLayers) : + mNumBroadPhaseLayers(inNumBroadPhaseLayers) + { + // Resize table and set all entries to false + mTable.resize((inNumBroadPhaseLayers * inNumObjectLayers + 7) / 8, 0); + + // Loop over all object layer pairs + for (ObjectLayer o1 = 0; o1 < inNumObjectLayers; ++o1) + for (ObjectLayer o2 = 0; o2 < inNumObjectLayers; ++o2) + { + // Get the broad phase layer for the second object layer + BroadPhaseLayer b2 = inBroadPhaseLayerInterface.GetBroadPhaseLayer(o2); + JPH_ASSERT((BroadPhaseLayer::Type)b2 < inNumBroadPhaseLayers); + + // If the object layers collide then so should the object and broadphase layer + if (inObjectLayerPairFilter.ShouldCollide(o1, o2)) + { + uint bit = GetBit(o1, b2); + mTable[bit >> 3] |= 1 << (bit & 0b111); + } + } + } + + /// Returns true if an object layer should collide with a broadphase layer + virtual bool ShouldCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2) const override + { + uint bit = GetBit(inLayer1, inLayer2); + return (mTable[bit >> 3] & (1 << (bit & 0b111))) != 0; + } + +private: + uint mNumBroadPhaseLayers; ///< The total number of broadphase layers + Array mTable; ///< The table of bits that indicates which layers collide +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp index f198a3e3..4c1e740c 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp @@ -190,7 +190,7 @@ uint32 QuadTree::AllocateNode(bool inIsChanged) if (index == Allocator::cInvalidObjectIndex) { Trace("QuadTree: Out of nodes!"); - JPH_CRASH; + std::abort(); } return index; } @@ -221,6 +221,17 @@ void QuadTree::DiscardOldTree() } } +AABox QuadTree::GetBounds() const +{ + uint32 node_idx = GetCurrentRoot().mIndex; + JPH_ASSERT(node_idx != cInvalidNodeIndex); + const Node &node = mAllocator->Get(node_idx); + + AABox bounds; + node.GetNodeBounds(bounds); + return bounds; +} + void QuadTree::UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTracking, UpdateState &outUpdateState, bool inFullRebuild) { #ifdef JPH_ENABLE_ASSERTS @@ -1319,10 +1330,11 @@ void QuadTree::CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioColle JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) { // Enlarge them by the casted aabox extents - AABox4EnlargeWithExtent(mExtent, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ); + Vec4 bounds_min_x = inBoundsMinX, bounds_min_y = inBoundsMinY, bounds_min_z = inBoundsMinZ, bounds_max_x = inBoundsMaxX, bounds_max_y = inBoundsMaxY, bounds_max_z = inBoundsMaxZ; + AABox4EnlargeWithExtent(mExtent, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z); // Test 4 children - Vec4 fraction = RayAABox4(mOrigin, mInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ); + Vec4 fraction = RayAABox4(mOrigin, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z); // Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom) return SortReverseAndStore(fraction, mCollector.GetPositiveEarlyOutFraction(), ioChildNodeIDs, &mFractionStack[inStackTop]); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.h b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.h index b3179076..053ea4f7 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/BroadPhase/QuadTree.h @@ -197,6 +197,9 @@ class JPH_EXPORT QuadTree : public NonCopyable /// Will throw away the previous frame's nodes so that we can start building a new tree in the background void DiscardOldTree(); + /// Get the bounding box for this tree + AABox GetBounds() const; + /// Update the broadphase, needs to be called regularly to achieve a tight fit of the tree when bodies have been modified. /// UpdatePrepare() will build the tree, UpdateFinalize() will lock the root of the tree shortly and swap the trees and afterwards clean up temporary data structures. void UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTracking, UpdateState &outUpdateState, bool inFullRebuild); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CastConvexVsTriangles.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/CastConvexVsTriangles.cpp index ec534c14..a69208a0 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/CastConvexVsTriangles.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CastConvexVsTriangles.cpp @@ -51,7 +51,7 @@ void CastConvexVsTriangles::Cast(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 if (mSupport == nullptr) { // Determine if we want to use the actual shape or a shrunken shape with convex radius - ConvexShape::ESupportMode support_mode = mShapeCastSettings.mUseShrunkenShapeAndConvexRadius? ConvexShape::ESupportMode::ExcludeConvexRadius : ConvexShape::ESupportMode::IncludeConvexRadius; + ConvexShape::ESupportMode support_mode = mShapeCastSettings.mUseShrunkenShapeAndConvexRadius? ConvexShape::ESupportMode::ExcludeConvexRadius : ConvexShape::ESupportMode::Default; // Create support function mSupport = static_cast(mShapeCast.mShape)->GetSupportFunction(support_mode, mSupportBuffer, mShapeCast.mScale); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CastResult.h b/Dependencies/Jolt/Jolt/Physics/Collision/CastResult.h index fe4a3ccd..6bb49feb 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/CastResult.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CastResult.h @@ -18,6 +18,9 @@ class BroadPhaseCastResult /// Function required by the CollisionCollector. A smaller fraction is considered to be a 'better hit'. For rays/cast shapes we can just use the collision fraction. inline float GetEarlyOutFraction() const { return mFraction; } + /// Reset this result so it can be reused for a new cast. + inline void Reset() { mBodyID = BodyID(); mFraction = 1.0f + FLT_EPSILON; } + BodyID mBodyID; ///< Body that was hit float mFraction = 1.0f + FLT_EPSILON; ///< Hit fraction of the ray/object [0, 1], HitPoint = Start + mFraction * (End - Start) }; diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CastSphereVsTriangles.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/CastSphereVsTriangles.cpp index 1c0fe688..166883be 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/CastSphereVsTriangles.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CastSphereVsTriangles.cpp @@ -111,7 +111,7 @@ float CastSphereVsTriangles::RayCylinder(Vec3Arg inRayDirection, Vec3Arg inCylin if (t < 0.0f || t > 1.0f) return FLT_MAX; // Intersection lies outside segment if (start_dot_axis + t * direction_dot_axis < 0.0f || start_dot_axis + t * direction_dot_axis > axis_len_sq) - return FLT_MAX; // Intersection outside the end point of the cyclinder, stop processing, we will possibly hit a vertex + return FLT_MAX; // Intersection outside the end point of the cylinder, stop processing, we will possibly hit a vertex return t; } @@ -183,8 +183,8 @@ void CastSphereVsTriangles::Cast(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 // Check if this is an interior point float u, v, w; - ClosestPoint::GetBaryCentricCoordinates(v0 - p, v1 - p, v2 - p, u, v, w); - if (u >= 0.0f && v >= 0.0f && w >= 0.0f) + if (ClosestPoint::GetBaryCentricCoordinates(v0 - p, v1 - p, v2 - p, u, v, w) + && u >= 0.0f && v >= 0.0f && w >= 0.0f) { // Interior point, we found the collision point. We don't need to check active edges. AddHit(back_facing, inSubShapeID2, plane_intersection, p, p, back_facing? triangle_normal : -triangle_normal); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CollideConvexVsTriangles.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/CollideConvexVsTriangles.cpp index 1efe4369..f00b0023 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/CollideConvexVsTriangles.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CollideConvexVsTriangles.cpp @@ -136,11 +136,10 @@ void CollideConvexVsTriangles::Collide(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, mShape1->GetSupportingFace(SubShapeID(), -penetration_axis, mScale1, mTransform1, result.mShape1Face); // Get face of the triangle - triangle.GetSupportingFace(penetration_axis, result.mShape2Face); - - // Convert to world space - for (Vec3 &p : result.mShape2Face) - p = mTransform1 * p; + result.mShape2Face.resize(3); + result.mShape2Face[0] = mTransform1 * v0; + result.mShape2Face[1] = mTransform1 * v1; + result.mShape2Face[2] = mTransform1 * v2; } // Notify the collector diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h b/Dependencies/Jolt/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h new file mode 100644 index 00000000..4385e15c --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h @@ -0,0 +1,98 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +JPH_NAMESPACE_BEGIN + +/// Collision detection helper that collides soft body vertices vs triangles +class JPH_EXPORT CollideSoftBodyVerticesVsTriangles +{ +public: + CollideSoftBodyVerticesVsTriangles(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) : + mTransform(inCenterOfMassTransform * Mat44::sScale(inScale)), + mInvTransform(mTransform.Inversed()), + mNormalSign(ScaleHelpers::IsInsideOut(inScale)? -1.0f : 1.0f) + { + } + + JPH_INLINE void StartVertex(const SoftBodyVertex &inVertex) + { + mLocalPosition = mInvTransform * inVertex.mPosition; + mClosestDistanceSq = FLT_MAX; + } + + JPH_INLINE void ProcessTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2) + { + // Get the closest point from the vertex to the triangle + uint32 set; + Vec3 closest_point = ClosestPoint::GetClosestPointOnTriangle(inV0 - mLocalPosition, inV1 - mLocalPosition, inV2 - mLocalPosition, set); + float dist_sq = closest_point.LengthSq(); + if (dist_sq < mClosestDistanceSq) + { + mV0 = inV0; + mV1 = inV1; + mV2 = inV2; + mClosestPoint = closest_point; + mClosestDistanceSq = dist_sq; + mSet = set; + } + } + + JPH_INLINE void FinishVertex(SoftBodyVertex &ioVertex, int inCollidingShapeIndex) const + { + if (mClosestDistanceSq < FLT_MAX) + { + // Convert triangle to world space + Vec3 v0 = mTransform * mV0; + Vec3 v1 = mTransform * mV1; + Vec3 v2 = mTransform * mV2; + Vec3 triangle_normal = mNormalSign * (v1 - v0).Cross(v2 - v0).NormalizedOr(Vec3::sAxisY()); + + if (mSet == 0b111) + { + // Closest is interior to the triangle, use plane as collision plane but don't allow more than 0.1 m penetration + // because otherwise a triangle half a level a way will have a huge penetration if it is back facing + float penetration = min(triangle_normal.Dot(v0 - ioVertex.mPosition), 0.1f); + if (penetration > ioVertex.mLargestPenetration) + { + ioVertex.mLargestPenetration = penetration; + ioVertex.mCollisionPlane = Plane::sFromPointAndNormal(v0, triangle_normal); + ioVertex.mCollidingShapeIndex = inCollidingShapeIndex; + } + } + else + { + // Closest point is on an edge or vertex, use closest point as collision plane + Vec3 closest_point = mTransform * (mLocalPosition + mClosestPoint); + Vec3 normal = ioVertex.mPosition - closest_point; + if (normal.Dot(triangle_normal) > 0.0f) // Ignore back facing edges + { + float normal_length = normal.Length(); + float penetration = -normal_length; + if (penetration > ioVertex.mLargestPenetration) + { + ioVertex.mLargestPenetration = penetration; + ioVertex.mCollisionPlane = Plane::sFromPointAndNormal(closest_point, normal_length > 0.0f? normal / normal_length : triangle_normal); + ioVertex.mCollidingShapeIndex = inCollidingShapeIndex; + } + } + } + } + } + + Mat44 mTransform; + Mat44 mInvTransform; + Vec3 mLocalPosition; + Vec3 mV0, mV1, mV2; + Vec3 mClosestPoint; + float mNormalSign; + float mClosestDistanceSq; + uint32 mSet; +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CollideSphereVsTriangles.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/CollideSphereVsTriangles.cpp index e6a1b3dd..92ed28a7 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/CollideSphereVsTriangles.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CollideSphereVsTriangles.cpp @@ -21,7 +21,7 @@ static constexpr uint8 sClosestFeatureToActiveEdgesMask[] = { 0b001, // 0b011: Vertex 1 & 2 -> edge 1 0b110, // 0b100: Vertex 3 -> edge 2 or 3 0b100, // 0b101: Vertex 1 & 3 -> edge 3 - 0b010, // 0b110: Vertex 2 & 3 -> egde 2 + 0b010, // 0b110: Vertex 2 & 3 -> edge 2 // 0b111: Vertex 1, 2 & 3 -> interior, guarded by an if }; @@ -103,7 +103,17 @@ void CollideSphereVsTriangles::Collide(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, // Create collision result CollideShapeResult result(point1, point2, penetration_axis_world, penetration_depth, mSubShapeID1, inSubShapeID2, TransformedShape::sGetBodyID(mCollector.GetContext())); - // Note: We don't gather faces here because that's only useful if both shapes have a face. Since the sphere always has only 1 contact point, the manifold is always a point. + // Gather faces + if (mCollideShapeSettings.mCollectFacesMode == ECollectFacesMode::CollectFaces) + { + // The sphere doesn't have a supporting face + + // Get face of triangle 2 + result.mShape2Face.resize(3); + result.mShape2Face[0] = mTransform2 * (mSphereCenterIn2 + v0); + result.mShape2Face[1] = mTransform2 * (mSphereCenterIn2 + v1); + result.mShape2Face[2] = mTransform2 * (mSphereCenterIn2 + v2); + } // Notify the collector JPH_IF_TRACK_NARROWPHASE_STATS(TrackNarrowPhaseCollector track;) diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CollisionCollector.h b/Dependencies/Jolt/Jolt/Physics/Collision/CollisionCollector.h index cf1729c6..274e8f03 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/CollisionCollector.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CollisionCollector.h @@ -32,7 +32,7 @@ class CollisionCollectorTraitsCollideShape { public: /// For shape collisions we use -penetration depth to order hits. - static constexpr float InitialEarlyOutFraction = FLT_MAX; ///< Most shallow hit: Separatation is infinite + static constexpr float InitialEarlyOutFraction = FLT_MAX; ///< Most shallow hit: Separation is infinite static constexpr float ShouldEarlyOutFraction = -FLT_MAX; ///< Deepest hit: Penetration is infinite }; @@ -77,7 +77,7 @@ class CollisionCollector inline void UpdateEarlyOutFraction(float inFraction) { JPH_ASSERT(inFraction <= mEarlyOutFraction); mEarlyOutFraction = inFraction; } /// Reset the early out fraction to a specific value - inline void ResetEarlyOutFraction(float inFraction) { mEarlyOutFraction = inFraction; } + inline void ResetEarlyOutFraction(float inFraction = TraitsType::InitialEarlyOutFraction) { mEarlyOutFraction = inFraction; } /// Force the collision detection algorithm to terminate as soon as possible. Call this from the AddHit function when a satisfying hit is found. inline void ForceEarlyOut() { mEarlyOutFraction = TraitsType::ShouldEarlyOutFraction; } diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/CollisionDispatch.h b/Dependencies/Jolt/Jolt/Physics/Collision/CollisionDispatch.h index be89278e..2b8f5ad4 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/CollisionDispatch.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/CollisionDispatch.h @@ -39,7 +39,7 @@ class JPH_EXPORT CollisionDispatch sCollideShape[(int)inShape1->GetSubType()][(int)inShape2->GetSubType()](inShape1, inShape2, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1, inSubShapeIDCreator2, inCollideShapeSettings, ioCollector, inShapeFilter); } - /// Cast a shape againt this shape, passes any hits found to ioCollector. + /// Cast a shape against this shape, passes any hits found to ioCollector. /// Note: This version takes the shape cast in local space relative to the center of mass of inShape, take a look at sCastShapeVsShapeWorldSpace if you have a shape cast in world space. /// @param inShapeCastLocal The shape to cast against the other shape and its start and direction. /// @param inShapeCastSettings Settings for performing the cast diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/ContactListener.h b/Dependencies/Jolt/Jolt/Physics/Collision/ContactListener.h index dac76310..d347dc47 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/ContactListener.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/ContactListener.h @@ -28,8 +28,8 @@ class ContactManifold RVec3 mBaseOffset; ///< Offset to which all the contact points are relative Vec3 mWorldSpaceNormal; ///< Normal for this manifold, direction along which to move body 2 out of collision along the shortest path - float mPenetrationDepth; ///< Penetration depth (move shape 2 by this distance to resolve the collision) - SubShapeID mSubShapeID1; ///< Sub shapes that formed this manifold (note that when multiple manifolds are combined because they're coplanar, we lose some information here because we only keep track of one sub shape pair that we encounter) + float mPenetrationDepth; ///< Penetration depth (move shape 2 by this distance to resolve the collision). If this value is negative, this is a speculative contact point and may not actually result in a velocity change as during solving the bodies may not actually collide. + SubShapeID mSubShapeID1; ///< Sub shapes that formed this manifold (note that when multiple manifolds are combined because they're coplanar, we lose some information here because we only keep track of one sub shape pair that we encounter, see description at Body::SetUseManifoldReduction) SubShapeID mSubShapeID2; ContactPoints mRelativeContactPointsOn1; ///< Contact points on the surface of shape 1 relative to mBaseOffset. ContactPoints mRelativeContactPointsOn2; ///< Contact points on the surface of shape 2 relative to mBaseOffset. If there's no penetration, this will be the same as mRelativeContactPointsOn1. If there is penetration they will be different. @@ -61,7 +61,7 @@ enum class ValidateResult RejectAllContactsForThisBodyPair ///< Rejects this and any further contact points for this body pair }; -/// A listener class that receives collision contact events events. +/// A listener class that receives collision contact events. /// It can be registered with the ContactConstraintManager (or PhysicsSystem). /// Note that contact listener callbacks are called from multiple threads at the same time when all bodies are locked, you're only allowed to read from the bodies and you can't change physics state. class ContactListener @@ -71,13 +71,13 @@ class ContactListener virtual ~ContactListener() = default; /// Called after detecting a collision between a body pair, but before calling OnContactAdded and before adding the contact constraint. - /// If the function returns false, the contact will not be added and any other contacts between this body pair will not be processed. + /// If the function rejects the contact, the contact will not be added and any other contacts between this body pair will not be processed. /// This function will only be called once per PhysicsSystem::Update per body pair and may not be called again the next update /// if a contact persists and no new contact pairs between sub shapes are found. /// This is a rather expensive time to reject a contact point since a lot of the collision detection has happened already, make sure you /// filter out the majority of undesired body pairs through the ObjectLayerPairFilter that is registered on the PhysicsSystem. /// Note that this callback is called when all bodies are locked, so don't use any locking functions! - /// The order of body 1 and 2 is undefined, but when one of the two bodies is dynamic it will be body 1. + /// Body 1 will have a motion type that is larger or equal than body 2's motion type (order from large to small: dynamic -> kinematic -> static). When motion types are equal, they are ordered by BodyID. /// The collision result (inCollisionResult) is reported relative to inBaseOffset. virtual ValidateResult OnContactValidate([[maybe_unused]] const Body &inBody1, [[maybe_unused]] const Body &inBody2, [[maybe_unused]] RVec3Arg inBaseOffset, [[maybe_unused]] const CollideShapeResult &inCollisionResult) { return ValidateResult::AcceptAllContactsForThisBodyPair; } diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/EstimateCollisionResponse.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/EstimateCollisionResponse.cpp index c839cf79..53cf12b5 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/EstimateCollisionResponse.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/EstimateCollisionResponse.cpp @@ -185,7 +185,7 @@ void EstimateCollisionResponse(const Body &inBody1, const Body &inBody2, const C float lambda1 = impulse.mFrictionImpulse1 + constraint.mFriction1.SolveGetLambda(outResult.mTangent1, outResult); float lambda2 = impulse.mFrictionImpulse2 + constraint.mFriction2.SolveGetLambda(outResult.mTangent2, outResult); - // Caclulate max impulse based on contact impulse + // Calculate max impulse based on contact impulse float max_impulse = inCombinedFriction * impulse.mContactImpulse; // If the total lambda that we will apply is too large, scale it back diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/GroupFilterTable.h b/Dependencies/Jolt/Jolt/Physics/Collision/GroupFilterTable.h index 6647e5b1..62ce34ce 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/GroupFilterTable.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/GroupFilterTable.h @@ -12,24 +12,24 @@ JPH_NAMESPACE_BEGIN /// Implementation of GroupFilter that stores a bit table with one bit per sub shape ID pair to determine if they collide or not /// /// The collision rules: -/// - If one of the objects is in the cInvalidGroup the objects will collide -/// - If the objects are in different groups they will collide -/// - If they're in the same group but their collision filter is different they will not collide -/// - If they're in the same group and their collision filters match, we'll use the SubGroupID and the table below: +/// - If one of the objects is in the cInvalidGroup the objects will collide. +/// - If the objects are in different groups they will collide. +/// - If they're in the same group but their collision filter is different they will not collide. +/// - If they're in the same group and their collision filters match, we'll use the SubGroupID and the table below. /// -/// For N = 6 sub shapes the table will look like: +/// For N = 6 sub groups the table will look like: /// -/// group 1 ---> -/// group 2 x..... -/// | ox.... -/// | oox... -/// V ooox.. -/// oooox. -/// ooooox +/// sub group 1 ---> +/// sub group 2 x..... +/// | ox.... +/// | oox... +/// V ooox.. +/// oooox. +/// ooooox /// -/// x means group 1 == group 2 and we define this to never collide -/// o is a bit that we have to store -/// . is a bit we don't need to store because the table is symmetric, we take care that group 2 > group 1 always by swapping the elements if needed +/// * 'x' means sub group 1 == sub group 2 and we define this to never collide. +/// * 'o' is a bit that we have to store that defines if the sub groups collide or not. +/// * '.' is a bit we don't need to store because the table is symmetric, we take care that group 2 > group 1 by swapping sub group 1 and sub group 2 if needed. /// /// The total number of bits we need to store is (N * (N - 1)) / 2 class JPH_EXPORT GroupFilterTable final : public GroupFilter diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h b/Dependencies/Jolt/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h new file mode 100644 index 00000000..46e37534 --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h @@ -0,0 +1,233 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +//#define JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG + +#ifdef JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG +#include +#endif // JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG + +JPH_NAMESPACE_BEGIN + +/// Removes internal edges from collision results. Can be used to filter out 'ghost collisions'. +/// Based on: Contact generation for meshes - Pierre Terdiman (https://www.codercorner.com/MeshContacts.pdf) +class InternalEdgeRemovingCollector : public CollideShapeCollector +{ + static constexpr uint cMaxDelayedResults = 16; + static constexpr uint cMaxVoidedFeatures = 128; + + /// Check if a vertex is voided + inline bool IsVoided(Vec3 inV) const + { + for (const Float3 &vf : mVoidedFeatures) + if (inV.IsClose(Vec3::sLoadFloat3Unsafe(vf), 1.0e-8f)) + return true; + return false; + } + + /// Add all vertices of a face to the voided features + inline void VoidFeatures(const CollideShapeResult &inResult) + { + for (const Vec3 &v : inResult.mShape2Face) + if (!IsVoided(v)) + { + if (mVoidedFeatures.size() == cMaxVoidedFeatures) + break; + Float3 f; + v.StoreFloat3(&f); + mVoidedFeatures.push_back(f); + } + } + + /// Call the chained collector + inline void Chain(const CollideShapeResult &inResult) + { + // Make sure the chained collector has the same context as we do + mChainedCollector.SetContext(GetContext()); + + // Forward the hit + mChainedCollector.AddHit(inResult); + + // If our chained collector updated its early out fraction, we need to follow + UpdateEarlyOutFraction(mChainedCollector.GetEarlyOutFraction()); + } + + /// Call the chained collector and void all features of inResult + inline void ChainAndVoid(const CollideShapeResult &inResult) + { + Chain(inResult); + VoidFeatures(inResult); + + #ifdef JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG + DebugRenderer::sInstance->DrawWirePolygon(RMat44::sIdentity(), inResult.mShape2Face, Color::sGreen); + DebugRenderer::sInstance->DrawArrow(RVec3(inResult.mContactPointOn2), RVec3(inResult.mContactPointOn2) + inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero()), Color::sGreen, 0.1f); + #endif // JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG + } + +public: + /// Constructor, configures a collector to be called with all the results that do not hit internal edges + explicit InternalEdgeRemovingCollector(CollideShapeCollector &inChainedCollector) : + mChainedCollector(inChainedCollector) + { + } + + // See: CollideShapeCollector::Reset + virtual void Reset() override + { + CollideShapeCollector::Reset(); + + mChainedCollector.Reset(); + + mVoidedFeatures.clear(); + mDelayedResults.clear(); + } + + // See: CollideShapeCollector::OnBody + virtual void OnBody(const Body &inBody) override + { + // Just forward the call to our chained collector + mChainedCollector.OnBody(inBody); + } + + // See: CollideShapeCollector::AddHit + virtual void AddHit(const CollideShapeResult &inResult) override + { + // We only support welding when the shape is a triangle or has more vertices so that we can calculate a normal + if (inResult.mShape2Face.size() < 3) + return ChainAndVoid(inResult); + + // Get the triangle normal of shape 2 face + Vec3 triangle_normal = (inResult.mShape2Face[1] - inResult.mShape2Face[0]).Cross(inResult.mShape2Face[2] - inResult.mShape2Face[0]); + float triangle_normal_len = triangle_normal.Length(); + if (triangle_normal_len < 1e-6f) + return ChainAndVoid(inResult); + + // If the triangle normal matches the contact normal within 1 degree, we can process the contact immediately + // We make the assumption here that if the contact normal and the triangle normal align that the we're dealing with a 'face contact' + Vec3 contact_normal = -inResult.mPenetrationAxis; + float contact_normal_len = inResult.mPenetrationAxis.Length(); + if (triangle_normal.Dot(contact_normal) > 0.999848f * contact_normal_len * triangle_normal_len) // cos(1 degree) + return ChainAndVoid(inResult); + + // Delayed processing + if (mDelayedResults.size() == cMaxDelayedResults) + return ChainAndVoid(inResult); + mDelayedResults.push_back(inResult); + } + + /// After all hits have been added, call this function to process the delayed results + void Flush() + { + // Sort on biggest penetration depth first + uint sorted_indices[cMaxDelayedResults]; + for (uint i = 0; i < uint(mDelayedResults.size()); ++i) + sorted_indices[i] = i; + QuickSort(sorted_indices, sorted_indices + mDelayedResults.size(), [this](uint inLHS, uint inRHS) { return mDelayedResults[inLHS].mPenetrationDepth > mDelayedResults[inRHS].mPenetrationDepth; }); + + // Loop over all results + for (uint i = 0; i < uint(mDelayedResults.size()); ++i) + { + const CollideShapeResult &r = mDelayedResults[sorted_indices[i]]; + + // Determine which vertex or which edge is the closest to the contact point + float best_dist_sq = FLT_MAX; + uint best_v1_idx = 0; + uint best_v2_idx = 0; + uint num_v = uint(r.mShape2Face.size()); + uint v1_idx = num_v - 1; + Vec3 v1 = r.mShape2Face[v1_idx] - r.mContactPointOn2; + for (uint v2_idx = 0; v2_idx < num_v; ++v2_idx) + { + Vec3 v2 = r.mShape2Face[v2_idx] - r.mContactPointOn2; + Vec3 v1_v2 = v2 - v1; + float denominator = v1_v2.LengthSq(); + if (denominator < Square(FLT_EPSILON)) + { + // Degenerate, assume v1 is closest, v2 will be tested in a later iteration + float v1_len_sq = v1.LengthSq(); + if (v1_len_sq < best_dist_sq) + { + best_dist_sq = v1_len_sq; + best_v1_idx = v1_idx; + best_v2_idx = v1_idx; + } + } + else + { + // Taken from ClosestPoint::GetBaryCentricCoordinates + float fraction = -v1.Dot(v1_v2) / denominator; + if (fraction < 1.0e-6f) + { + // Closest lies on v1 + float v1_len_sq = v1.LengthSq(); + if (v1_len_sq < best_dist_sq) + { + best_dist_sq = v1_len_sq; + best_v1_idx = v1_idx; + best_v2_idx = v1_idx; + } + } + else if (fraction < 1.0f - 1.0e-6f) + { + // Closest lies on the line segment v1, v2 + Vec3 closest = v1 + fraction * v1_v2; + float closest_len_sq = closest.LengthSq(); + if (closest_len_sq < best_dist_sq) + { + best_dist_sq = closest_len_sq; + best_v1_idx = v1_idx; + best_v2_idx = v2_idx; + } + } + // else closest is v2, but v2 will be tested in a later iteration + } + + v1_idx = v2_idx; + v1 = v2; + } + + // Check if this vertex/edge is voided + bool voided = IsVoided(r.mShape2Face[best_v1_idx]) + && (best_v1_idx == best_v2_idx || IsVoided(r.mShape2Face[best_v2_idx])); + + #ifdef JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG + Color color = voided? Color::sRed : Color::sYellow; + DebugRenderer::sInstance->DrawText3D(RVec3(r.mContactPointOn2), StringFormat("%d: %g", i, r.mPenetrationDepth), color, 0.1f); + DebugRenderer::sInstance->DrawWirePolygon(RMat44::sIdentity(), r.mShape2Face, color); + DebugRenderer::sInstance->DrawArrow(RVec3(r.mContactPointOn2), RVec3(r.mContactPointOn2) + r.mPenetrationAxis.NormalizedOr(Vec3::sZero()), color, 0.1f); + DebugRenderer::sInstance->DrawMarker(RVec3(r.mShape2Face[best_v1_idx]), IsVoided(r.mShape2Face[best_v1_idx])? Color::sRed : Color::sYellow, 0.1f); + DebugRenderer::sInstance->DrawMarker(RVec3(r.mShape2Face[best_v2_idx]), IsVoided(r.mShape2Face[best_v2_idx])? Color::sRed : Color::sYellow, 0.1f); + #endif // JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG + + // No voided features, accept the contact + if (!voided) + Chain(r); + + // Void the features of this face + VoidFeatures(r); + } + } + + /// Version of CollisionDispatch::sCollideShapeVsShape that removes internal edges + static void sCollideShapeVsShape(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) + { + JPH_ASSERT(inCollideShapeSettings.mCollectFacesMode == ECollectFacesMode::CollectFaces); // Won't work without collecting faces + + InternalEdgeRemovingCollector wrapper(ioCollector); + CollisionDispatch::sCollideShapeVsShape(inShape1, inShape2, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1, inSubShapeIDCreator2, inCollideShapeSettings, wrapper, inShapeFilter); + wrapper.Flush(); + } + +private: + CollideShapeCollector & mChainedCollector; + StaticArray mVoidedFeatures; // Read with Vec3::sLoadFloat3Unsafe so must not be the last member + StaticArray mDelayedResults; +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp index ec2cc92a..3331f5d7 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp @@ -134,7 +134,7 @@ void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioContactPoint ioContactPointsOn2 = points_to_keep_on_2; } -void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inSpeculativeContactDistanceSq, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass)) +void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistanceSq , const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass)) { #ifdef JPH_DEBUG_RENDERER if (ContactConstraintManager::sDrawContactPoint) @@ -188,7 +188,7 @@ void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, V for (Vec3 p2 : clipped_face) { float distance = (p2 - plane_origin).Dot(plane_normal); // Note should divide by length of plane_normal (unnormalized here) - if (distance <= 0.0f || Square(distance) < inSpeculativeContactDistanceSq * plane_normal_len_sq) // Must be close enough to plane, note we correct for not dividing by plane normal length here + if (distance <= 0.0f || Square(distance) < inMaxContactDistanceSq * plane_normal_len_sq) // Must be close enough to plane, note we correct for not dividing by plane normal length here { // Project point back on shape 1 using the normal, note we correct for not dividing by plane normal length here: // p1 = p2 - (distance / sqrt(plane_normal_len_sq)) * (plane_normal / sqrt(plane_normal_len_sq)); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h b/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h index 570dccfe..72a5b8f0 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h @@ -27,7 +27,7 @@ JPH_EXPORT void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioC /// @param inContactPoint1 The contact point on shape 1 relative to inCenterOfMass /// @param inContactPoint2 The contact point on shape 2 relative to inCenterOfMass /// @param inPenetrationAxis The local space penetration axis in world space -/// @param inSpeculativeContactDistanceSq Squared speculative contact distance, any contact further apart than this distance will be discarded +/// @param inMaxContactDistanceSq After face 2 is clipped against face 1, each remaining point on face 2 is tested against the plane of face 1. If the distance^2 on the positive side of the plane is larger than this distance, the point will be discarded as a contact point. /// @param inShape1Face The supporting faces on shape 1 relative to inCenterOfMass /// @param inShape2Face The supporting faces on shape 2 relative to inCenterOfMass /// @param outContactPoints1 Returns the contact points between the two shapes for shape 1 relative to inCenterOfMass (any existing points in the output array are left as is) @@ -35,7 +35,7 @@ JPH_EXPORT void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioC #ifdef JPH_DEBUG_RENDERER /// @param inCenterOfMass Center of mass position of body 1 #endif -JPH_EXPORT void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inSpeculativeContactDistanceSq, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 +JPH_EXPORT void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistanceSq, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 #ifdef JPH_DEBUG_RENDERER , RVec3Arg inCenterOfMass #endif diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h b/Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h new file mode 100644 index 00000000..dc3494c2 --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h @@ -0,0 +1,52 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// Filter class to test if two objects can collide based on their object layer. Used while finding collision pairs. +/// Uses group bits and mask bits. Two layers can collide if Object1.Group & Object2.Mask is non-zero and Object2.Group & Object1.Mask is non-zero. +/// The behavior is similar to that in e.g. Bullet. +/// This implementation works together with BroadPhaseLayerInterfaceMask and ObjectVsBroadPhaseLayerFilterMask +class ObjectLayerPairFilterMask : public ObjectLayerPairFilter +{ +public: + JPH_OVERRIDE_NEW_DELETE + + /// Number of bits for the group and mask bits + static constexpr uint32 cNumBits = JPH_OBJECT_LAYER_BITS / 2; + static constexpr uint32 cMask = (1 << cNumBits) - 1; + + /// Construct an ObjectLayer from a group and mask bits + static ObjectLayer sGetObjectLayer(uint32 inGroup, uint32 inMask = cMask) + { + JPH_ASSERT((inGroup & ~cMask) == 0); + JPH_ASSERT((inMask & ~cMask) == 0); + return ObjectLayer((inGroup & cMask) | (inMask << cNumBits)); + } + + /// Get the group bits from an ObjectLayer + static inline uint32 sGetGroup(ObjectLayer inObjectLayer) + { + return uint32(inObjectLayer) & cMask; + } + + /// Get the mask bits from an ObjectLayer + static inline uint32 sGetMask(ObjectLayer inObjectLayer) + { + return uint32(inObjectLayer) >> cNumBits; + } + + /// Returns true if two layers can collide + virtual bool ShouldCollide(ObjectLayer inObject1, ObjectLayer inObject2) const override + { + return (sGetGroup(inObject1) & sGetMask(inObject2)) != 0 + && (sGetGroup(inObject2) & sGetMask(inObject1)) != 0; + } +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h b/Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h new file mode 100644 index 00000000..1d62178a --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h @@ -0,0 +1,78 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// Filter class to test if two objects can collide based on their object layer. Used while finding collision pairs. +/// This implementation uses a table to determine if two layers can collide. +class ObjectLayerPairFilterTable : public ObjectLayerPairFilter +{ +private: + /// Get which bit corresponds to the pair (inLayer1, inLayer2) + uint GetBit(ObjectLayer inLayer1, ObjectLayer inLayer2) const + { + // We store the lower left half only, so swap the inputs when trying to access the top right half + if (inLayer1 > inLayer2) + swap(inLayer1, inLayer2); + + JPH_ASSERT(inLayer2 < mNumObjectLayers); + + // Calculate at which bit the entry for this pair resides + // We use the fact that a row always starts at inLayer2 * (inLayer2 + 1) / 2 + // (this is the amount of bits needed to store a table of inLayer2 entries) + return (inLayer2 * (inLayer2 + 1)) / 2 + inLayer1; + } + +public: + JPH_OVERRIDE_NEW_DELETE + + /// Constructs the table with inNumObjectLayers Layers, initially all layer pairs are disabled + explicit ObjectLayerPairFilterTable(uint inNumObjectLayers) : + mNumObjectLayers(inNumObjectLayers) + { + // By default nothing collides + // For the first layer we only need to store 1 bit, for the second 2 bits, for the third 3 bits, etc. + // We use the formula Sum_i=1^N i = N * (N + 1) / 2 to calculate the size of the table + int table_size = (inNumObjectLayers * (inNumObjectLayers + 1) / 2 + 7) / 8; + mTable.resize(table_size, 0); + } + + /// Get the number of object layers + uint GetNumObjectLayers() const + { + return mNumObjectLayers; + } + + /// Disable collision between two object layers + void DisableCollision(ObjectLayer inLayer1, ObjectLayer inLayer2) + { + uint bit = GetBit(inLayer1, inLayer2); + mTable[bit >> 3] &= (0xff ^ (1 << (bit & 0b111))); + } + + /// Enable collision between two object layers + void EnableCollision(ObjectLayer inLayer1, ObjectLayer inLayer2) + { + uint bit = GetBit(inLayer1, inLayer2); + mTable[bit >> 3] |= 1 << (bit & 0b111); + } + + /// Returns true if two layers can collide + virtual bool ShouldCollide(ObjectLayer inObject1, ObjectLayer inObject2) const override + { + // Test if the bit is set for this group pair + uint bit = GetBit(inObject1, inObject2); + return (mTable[bit >> 3] & (1 << (bit & 0b111))) != 0; + } + +private: + uint mNumObjectLayers; ///< The number of layers that this table supports + Array mTable; ///< The table of bits that indicates which layers collide +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.cpp index fd75f786..da6b08c4 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.cpp @@ -103,6 +103,7 @@ const ConvexShape::Support *BoxShape::GetSupportFunction(ESupportMode inMode, Su switch (inMode) { case ESupportMode::IncludeConvexRadius: + case ESupportMode::Default: { // Make box out of our half extents AABox box = AABox(-scaled_half_extent, scaled_half_extent); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.h index bb62ad62..dc53772d 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/BoxShape.h @@ -76,7 +76,7 @@ class JPH_EXPORT BoxShape final : public ConvexShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::GetTrianglesStart diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.cpp index e030e9e4..5f050ea6 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.cpp @@ -165,6 +165,7 @@ const ConvexShape::Support *CapsuleShape::GetSupportFunction(ESupportMode inMode return new (&inBuffer) CapsuleWithConvex(scaled_half_height_of_cylinder, scaled_radius); case ESupportMode::ExcludeConvexRadius: + case ESupportMode::Default: return new (&inBuffer) CapsuleNoConvex(scaled_half_height_of_cylinder, scaled_radius); } @@ -219,29 +220,27 @@ MassProperties CapsuleShape::GetMassProperties() const // Calculate inertia and mass according to: // https://www.gamedev.net/resources/_/technical/math-and-physics/capsule-inertia-tensor-r3856 - float radius_sq = mRadius * mRadius; + // Note that there is an error in eq 14, H^2/2 should be H^2/4 in Ixx and Izz, eq 12 does contain the correct value + float radius_sq = Square(mRadius); float height = 2.0f * mHalfHeightOfCylinder; float cylinder_mass = JPH_PI * height * radius_sq * density; float hemisphere_mass = (2.0f * JPH_PI / 3.0f) * radius_sq * mRadius * density; // From cylinder + float height_sq = Square(height); float inertia_y = radius_sq * cylinder_mass * 0.5f; - float inertia_x = inertia_y * 0.5f + cylinder_mass * height * height / 12.0f; - float inertia_z = inertia_x; + float inertia_xz = inertia_y * 0.5f + cylinder_mass * height_sq / 12.0f; // From hemispheres - float temp0 = hemisphere_mass * 2.0f * radius_sq / 5.0f; - inertia_y += temp0 * 2.0f; - float temp1 = mHalfHeightOfCylinder; - float temp2 = temp0 + hemisphere_mass * (temp1 * temp1 + (3.0f / 8.0f) * height * mRadius); - inertia_x += temp2 * 2.0f; - inertia_z += temp2 * 2.0f; + float temp = hemisphere_mass * 4.0f * radius_sq / 5.0f; + inertia_y += temp; + inertia_xz += temp + hemisphere_mass * (0.5f * height_sq + (3.0f / 4.0f) * height * mRadius); // Mass is cylinder + hemispheres p.mMass = cylinder_mass + hemisphere_mass * 2.0f; // Set inertia - p.mInertia = Mat44::sScale(Vec3(inertia_x, inertia_y, inertia_z)); + p.mInertia = Mat44::sScale(Vec3(inertia_xz, inertia_y, inertia_xz)); return p; } @@ -385,7 +384,7 @@ void CapsuleShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedS { Vec3 scale; Mat44 transform = inCenterOfMassTransform.Decompose(scale); - TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetRotation().GetQuaternion(), this, BodyID(), SubShapeIDCreator()); + TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator()); ts.SetShapeScale(ScaleHelpers::MakeUniformScale(scale.Abs())); ioCollector.AddHit(ts); } diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.h index d9c966fa..73281d0f 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CapsuleShape.h @@ -85,7 +85,7 @@ class JPH_EXPORT CapsuleShape final : public ConvexShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::TransformShape diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.cpp index a4ba01be..20a61f28 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.cpp @@ -211,7 +211,7 @@ void CompoundShape::GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg outCenterOfBuoyancy /= outSubmergedVolume; #ifdef JPH_DEBUG_RENDERER - // Draw senter of buoyancy + // Draw center of buoyancy if (sDrawSubmergedVolumes) DebugRenderer::sInstance->DrawWireSphere(inBaseOffset + outCenterOfBuoyancy, 0.05f, Color::sRed, 1); #endif // JPH_DEBUG_RENDERER @@ -249,7 +249,10 @@ void CompoundShape::DrawGetSupportingFace(DebugRenderer *inRenderer, RMat44Arg i void CompoundShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const { for (const SubShape &shape : mSubShapes) - shape.mShape->CollideSoftBodyVertices(inCenterOfMassTransform * Mat44::sRotationTranslation(shape.GetRotation(), shape.GetPositionCOM()), shape.TransformScale(inScale), ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex); + { + Mat44 transform = shape.GetLocalTransformNoScale(inScale); + shape.mShape->CollideSoftBodyVertices(inCenterOfMassTransform * transform, shape.TransformScale(inScale), ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex); + } } void CompoundShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const @@ -302,16 +305,11 @@ void CompoundShape::SaveBinaryState(StreamOut &inStream) const inStream.Write(mInnerRadius); // Write sub shapes - size_t len = mSubShapes.size(); - inStream.Write(len); - if (!inStream.IsFailed()) - for (size_t i = 0; i < len; ++i) - { - const SubShape &s = mSubShapes[i]; - inStream.Write(s.mUserData); - inStream.Write(s.mPositionCOM); - inStream.Write(s.mRotation); - } + inStream.Write(mSubShapes, [](const SubShape &inElement, StreamOut &inS) { + inS.Write(inElement.mUserData); + inS.Write(inElement.mPositionCOM); + inS.Write(inElement.mRotation); + }); } void CompoundShape::RestoreBinaryState(StreamIn &inStream) @@ -324,20 +322,12 @@ void CompoundShape::RestoreBinaryState(StreamIn &inStream) inStream.Read(mInnerRadius); // Read sub shapes - size_t len = 0; - inStream.Read(len); - if (!inStream.IsEOF() && !inStream.IsFailed()) - { - mSubShapes.resize(len); - for (size_t i = 0; i < len; ++i) - { - SubShape &s = mSubShapes[i]; - inStream.Read(s.mUserData); - inStream.Read(s.mPositionCOM); - inStream.Read(s.mRotation); - s.mIsRotationIdentity = s.mRotation == Float3(0, 0, 0); - } - } + inStream.Read(mSubShapes, [](StreamIn &inS, SubShape &outElement) { + inS.Read(outElement.mUserData); + inS.Read(outElement.mPositionCOM); + inS.Read(outElement.mRotation); + outElement.mIsRotationIdentity = outElement.mRotation == Float3(0, 0, 0); + }); } void CompoundShape::SaveSubShapeState(ShapeList &outSubShapes) const diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.h index e2f0769c..7694bf4e 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShape.h @@ -105,7 +105,7 @@ class JPH_EXPORT CompoundShape : public Shape virtual void DrawGetSupportingFace(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale) const override; #endif // JPH_DEBUG_RENDERER - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::TransformShape diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h index b6266761..ecccb658 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h @@ -175,7 +175,7 @@ struct CompoundShape::CastShapeVisitor return mCollector.ShouldEarlyOut(); } - /// Tests the shape cast against 4 boundign boxes, returns the distance along the shape cast where the shape first enters the bounding box + /// Tests the shape cast against 4 bounding boxes, returns the distance along the shape cast where the shape first enters the bounding box JPH_INLINE Vec4 TestBounds(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ) const { // Scale the bounding boxes diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp index ac837bbf..d1de6371 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp @@ -123,7 +123,7 @@ ConvexHullShape::ConvexHullShape(const ConvexHullShapeSettings &inSettings, Shap // Calculate inertia matrix assuming density is 1, note that element (3, 3) is garbage mInertia = Mat44::sIdentity() * (covariance_matrix(0, 0) + covariance_matrix(1, 1) + covariance_matrix(2, 2)) - covariance_matrix; - // Convert polygons fron the builder to our internal representation + // Convert polygons from the builder to our internal representation using VtxMap = UnorderedMap; VtxMap vertex_map; for (BuilderFace *builder_face : builder_faces) @@ -324,7 +324,7 @@ ConvexHullShape::ConvexHullShape(const ConvexHullShapeSettings &inSettings, Shap // |n2x n2y n2z| |y| = - | r + c2 | <=> n point = -r (1, 1, 1) - (c1, c2, c3) // |n3x n3y n3z| |z| | r + c3 | // Where point = (x, y, z), n1x is the x component of the first plane, c1 = plane constant of plane 1, etc. - // The relation between how much the interesection point shifts as a function of r is: -r * n^-1 (1, 1, 1) = r * offset + // The relation between how much the intersection point shifts as a function of r is: -r * n^-1 (1, 1, 1) = r * offset // Where offset = -n^-1 (1, 1, 1) or -n^-1 (1, 1, 0) in case only the first 2 planes are offset // The error that is introduced by a convex radius r is: error = r * |offset| - r // So the max convex radius given error is: r = error / (|offset| - 1) @@ -542,6 +542,7 @@ const ConvexShape::Support *ConvexHullShape::GetSupportFunction(ESupportMode inM switch (inMode) { case ESupportMode::IncludeConvexRadius: + case ESupportMode::Default: if (ScaleHelpers::IsNotScaled(inScale)) return new (&inBuffer) HullWithConvex(this); else diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.h index 2bd80fc4..8837a82d 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexHullShape.h @@ -89,7 +89,7 @@ class JPH_EXPORT ConvexHullShape final : public ConvexShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::GetTrianglesStart diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.cpp index 3489add5..55072358 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.cpp @@ -259,7 +259,7 @@ void ConvexShape::sCastConvexVsConvex(const ShapeCast &inShapeCast, const ShapeC const ConvexShape *shape = static_cast(inShape); // Determine if we want to use the actual shape or a shrunken shape with convex radius - ConvexShape::ESupportMode support_mode = inShapeCastSettings.mUseShrunkenShapeAndConvexRadius? ConvexShape::ESupportMode::ExcludeConvexRadius : ConvexShape::ESupportMode::IncludeConvexRadius; + ConvexShape::ESupportMode support_mode = inShapeCastSettings.mUseShrunkenShapeAndConvexRadius? ConvexShape::ESupportMode::ExcludeConvexRadius : ConvexShape::ESupportMode::Default; // Create support function for shape to cast SupportBuffer cast_buffer; diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.h index 0d6ef077..562403b5 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ConvexShape.h @@ -90,8 +90,9 @@ class JPH_EXPORT ConvexShape : public Shape /// How the GetSupport function should behave enum class ESupportMode { - ExcludeConvexRadius, ///< Return the shape excluding the convex radius - IncludeConvexRadius, ///< Return the shape including the convex radius + ExcludeConvexRadius, ///< Return the shape excluding the convex radius, Support::GetConvexRadius will return the convex radius if there is one, but adding this radius may not result in the most accurate/efficient representation of shapes with sharp edges + IncludeConvexRadius, ///< Return the shape including the convex radius, Support::GetSupport includes the convex radius if there is one, Support::GetConvexRadius will return 0 + Default, ///< Use both Support::GetSupport add Support::GetConvexRadius to get a support point that matches the original shape as accurately/efficiently as possible }; /// Returns an object that provides the GetSupport function for this shape. diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.cpp index 587dfaf0..51a9f3c5 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.cpp @@ -177,6 +177,7 @@ const ConvexShape::Support *CylinderShape::GetSupportFunction(ESupportMode inMod switch (inMode) { case ESupportMode::IncludeConvexRadius: + case ESupportMode::Default: return new (&inBuffer) Cylinder(scaled_half_height, scaled_radius, 0.0f); case ESupportMode::ExcludeConvexRadius: @@ -363,7 +364,7 @@ void CylinderShape::TransformShape(Mat44Arg inCenterOfMassTransform, Transformed { Vec3 scale; Mat44 transform = inCenterOfMassTransform.Decompose(scale); - TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetRotation().GetQuaternion(), this, BodyID(), SubShapeIDCreator()); + TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator()); Vec3 abs_scale = scale.Abs(); float xz = 0.5f * (abs_scale.GetX() + abs_scale.GetZ()); ts.SetShapeScale(Vec3(xz, abs_scale.GetY(), xz)); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.h index 1a62058f..52108b13 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/CylinderShape.h @@ -80,7 +80,7 @@ class JPH_EXPORT CylinderShape final : public ConvexShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::TransformShape diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/GetTrianglesContext.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/GetTrianglesContext.h index d47b314e..c594ece6 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/GetTrianglesContext.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/GetTrianglesContext.h @@ -85,7 +85,7 @@ class GetTrianglesContextVertexList sCreateUnitSphereHelper(ioVertices, -Vec3::sAxisY(), -Vec3::sAxisX(), -Vec3::sAxisZ(), inDetailLevel); } - /// Helper function that creates an open cyclinder of half height 1 and radius 1 + /// Helper function that creates an open cylinder of half height 1 and radius 1 static void sCreateUnitOpenCylinder(std::vector &ioVertices, int inDetailLevel) { const Vec3 bottom_offset(0.0f, -2.0f, 0.0f); diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp index dd983718..7a0352f6 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -321,7 +322,7 @@ void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSe /* Store active edges. The triangles are organized like this: x ---> - + y + + | \ T1B | \ T2B | e0 e2 | \ @@ -929,7 +930,7 @@ void HeightFieldShape::SetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY heights = temp_heights; // We need to fill in the following areas: - // + // // +-----------------+ // | 2 | // |---+---------+---| @@ -1128,6 +1129,193 @@ void HeightFieldShape::SetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY #endif } +void HeightFieldShape::GetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, uint8 *outMaterials, uint inMaterialsStride) const +{ + if (inSizeX == 0 || inSizeY == 0) + return; + + if (mMaterialIndices.empty()) + { + // Return all 0's + for (uint y = 0; y < inSizeY; ++y) + { + uint8 *out_indices = outMaterials + y * inMaterialsStride; + for (uint x = 0; x < inSizeX; ++x) + *out_indices++ = 0; + } + return; + } + + JPH_ASSERT(inX < mSampleCount && inY < mSampleCount); + JPH_ASSERT(inX + inSizeX < mSampleCount && inY + inSizeY < mSampleCount); + + uint count_min_1 = mSampleCount - 1; + uint16 material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1); + + for (uint y = 0; y < inSizeY; ++y) + { + // Calculate input position + uint bit_pos = (inX + (inY + y) * count_min_1) * mNumBitsPerMaterialIndex; + const uint8 *in_indices = mMaterialIndices.data() + (bit_pos >> 3); + bit_pos &= 0b111; + + // Calculate output position + uint8 *out_indices = outMaterials + y * inMaterialsStride; + + for (uint x = 0; x < inSizeX; ++x) + { + // Get material index + uint16 material_index = uint16(in_indices[0]) + uint16(uint16(in_indices[1]) << 8); + material_index >>= bit_pos; + material_index &= material_index_mask; + *out_indices = uint8(material_index); + + // Go to the next index + bit_pos += mNumBitsPerMaterialIndex; + in_indices += bit_pos >> 3; + bit_pos &= 0b111; + ++out_indices; + } + } +} + +bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, const uint8 *inMaterials, uint inMaterialsStride, const PhysicsMaterialList *inMaterialList, TempAllocator &inAllocator) +{ + if (inSizeX == 0 || inSizeY == 0) + return true; + + JPH_ASSERT(inX < mSampleCount && inY < mSampleCount); + JPH_ASSERT(inX + inSizeX < mSampleCount && inY + inSizeY < mSampleCount); + + // Remap materials + uint material_remap_table_size = uint(inMaterialList != nullptr? inMaterialList->size() : mMaterials.size()); + uint8 *material_remap_table = (uint8 *)inAllocator.Allocate(material_remap_table_size); + if (inMaterialList != nullptr) + { + // Conservatively reserve more space if the incoming material list is bigger + if (inMaterialList->size() > mMaterials.size()) + mMaterials.reserve(inMaterialList->size()); + + // Create a remap table + uint8 *remap_entry = material_remap_table; + for (const PhysicsMaterial *material : *inMaterialList) + { + // Try to find it in the existing list + PhysicsMaterialList::const_iterator it = std::find(mMaterials.begin(), mMaterials.end(), material); + if (it != mMaterials.end()) + { + // Found it, calculate index + *remap_entry = uint8(it - mMaterials.begin()); + } + else + { + // Not found, add it + if (mMaterials.size() >= 256) + { + // We can't have more than 256 materials since we use uint8 as indices + inAllocator.Free(material_remap_table, material_remap_table_size); + return false; + } + *remap_entry = uint8(mMaterials.size()); + mMaterials.push_back(material); + } + ++remap_entry; + } + } + else + { + // No remapping + for (uint i = 0; i < material_remap_table_size; ++i) + material_remap_table[i] = uint8(i); + } + + if (mMaterials.size() == 1) + { + // Only 1 material, we don't need to store the material indices + return true; + } + + // Check if we need to resize the material indices array + uint count_min_1 = mSampleCount - 1; + uint32 new_bits_per_material_index = 32 - CountLeadingZeros((uint32)mMaterials.size() - 1); + JPH_ASSERT(mNumBitsPerMaterialIndex <= 8 && new_bits_per_material_index <= 8); + if (new_bits_per_material_index != mNumBitsPerMaterialIndex) + { + // Resize the material indices array + mMaterialIndices.resize(((Square(count_min_1) * new_bits_per_material_index + 7) >> 3) + 1); // Add 1 byte so we don't read out of bounds when reading an uint16 + + // Calculate old and new mask + uint16 old_material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1); + uint16 new_material_index_mask = uint16((1 << new_bits_per_material_index) - 1); + + // Loop through the array backwards to avoid overwriting data + int in_bit_pos = (count_min_1 * count_min_1 - 1) * mNumBitsPerMaterialIndex; + const uint8 *in_indices = mMaterialIndices.data() + (in_bit_pos >> 3); + in_bit_pos &= 0b111; + int out_bit_pos = (count_min_1 * count_min_1 - 1) * new_bits_per_material_index; + uint8 *out_indices = mMaterialIndices.data() + (out_bit_pos >> 3); + out_bit_pos &= 0b111; + + while (out_indices >= mMaterialIndices.data()) + { + // Read the material index + uint16 material_index = uint16(in_indices[0]) + uint16(uint16(in_indices[1]) << 8); + material_index >>= in_bit_pos; + material_index &= old_material_index_mask; + + // Write the material index + uint16 output_data = uint16(out_indices[0]) + uint16(uint16(out_indices[1]) << 8); + output_data &= ~(new_material_index_mask << out_bit_pos); + output_data |= material_index << out_bit_pos; + out_indices[0] = uint8(output_data); + out_indices[1] = uint8(output_data >> 8); + + // Go to the previous index + in_bit_pos -= int(mNumBitsPerMaterialIndex); + in_indices += in_bit_pos >> 3; + in_bit_pos &= 0b111; + out_bit_pos -= int(new_bits_per_material_index); + out_indices += out_bit_pos >> 3; + out_bit_pos &= 0b111; + } + + // Accept the new bits per material index + mNumBitsPerMaterialIndex = new_bits_per_material_index; + } + + uint16 material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1); + for (uint y = 0; y < inSizeY; ++y) + { + // Calculate input position + const uint8 *in_indices = inMaterials + y * inMaterialsStride; + + // Calculate output position + uint bit_pos = (inX + (inY + y) * count_min_1) * mNumBitsPerMaterialIndex; + uint8 *out_indices = mMaterialIndices.data() + (bit_pos >> 3); + bit_pos &= 0b111; + + for (uint x = 0; x < inSizeX; ++x) + { + // Update material + uint16 output_data = uint16(out_indices[0]) + uint16(uint16(out_indices[1]) << 8); + output_data &= ~(material_index_mask << bit_pos); + output_data |= material_remap_table[*in_indices] << bit_pos; + out_indices[0] = uint8(output_data); + out_indices[1] = uint8(output_data >> 8); + + // Go to the next index + in_indices++; + bit_pos += mNumBitsPerMaterialIndex; + out_indices += bit_pos >> 3; + bit_pos &= 0b111; + } + } + + // Free the remapping table + inAllocator.Free(material_remap_table, material_remap_table_size); + return true; +} + MassProperties HeightFieldShape::GetMassProperties() const { // Object should always be static, return default mass properties @@ -1796,7 +1984,7 @@ class HeightFieldShape::DecodingContext }; template -JPH_INLINE void HeightFieldShape::WalkHeightField(Visitor &ioVisitor) const +void HeightFieldShape::WalkHeightField(Visitor &ioVisitor) const { DecodingContext ctx(this); ctx.WalkHeightField(ioVisitor); @@ -1942,9 +2130,50 @@ void HeightFieldShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &in // A height field doesn't have volume, so we can't test insideness } -void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const +void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const { - sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, inScale, ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex); + JPH_PROFILE_FUNCTION(); + + struct Visitor : public CollideSoftBodyVerticesVsTriangles + { + using CollideSoftBodyVerticesVsTriangles::CollideSoftBodyVerticesVsTriangles; + + JPH_INLINE bool ShouldAbort() const + { + return false; + } + + JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const + { + return mDistanceStack[inStackTop] < mClosestDistanceSq; + } + + JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop) + { + // Get distance to vertex + Vec4 dist_sq = AABox4DistanceSqToPoint(mLocalPosition, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ); + + // Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom) + return SortReverseAndStore(dist_sq, mClosestDistanceSq, ioProperties, &mDistanceStack[inStackTop]); + } + + JPH_INLINE void VisitTriangle([[maybe_unused]] uint inX, [[maybe_unused]] uint inY, [[maybe_unused]] uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2) + { + ProcessTriangle(inV0, inV1, inV2); + } + + float mDistanceStack[cStackSize]; + }; + + Visitor visitor(inCenterOfMassTransform, inScale); + + for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v) + if (v->mInvMass > 0.0f) + { + visitor.StartVertex(*v); + WalkHeightField(visitor); + visitor.FinishVertex(*v, inCollidingShapeIndex); + } } void HeightFieldShape::sCastConvexVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector) diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.h index 122c66a3..e3a855ed 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/HeightFieldShape.h @@ -56,8 +56,8 @@ class JPH_EXPORT HeightFieldShapeSettings final : public ShapeSettings virtual ShapeResult Create() const override; /// Determine the minimal and maximal value of mHeightSamples (will ignore cNoCollisionValue) - /// @param outMinValue The minimal value fo mHeightSamples or FLT_MAX if no samples have collision - /// @param outMaxValue The maximal value fo mHeightSamples or -FLT_MAX if no samples have collision + /// @param outMinValue The minimal value of mHeightSamples or FLT_MAX if no samples have collision + /// @param outMaxValue The maximal value of mHeightSamples or -FLT_MAX if no samples have collision /// @param outQuantizationScale (value - outMinValue) * outQuantizationScale quantizes a height sample to 16 bits void DetermineMinAndMaxSample(float &outMinValue, float &outMaxValue, float &outQuantizationScale) const; @@ -72,10 +72,10 @@ class JPH_EXPORT HeightFieldShapeSettings final : public ShapeSettings Vec3 mScale = Vec3::sReplicate(1.0f); uint32 mSampleCount = 0; - /// Artifical minimal value of mHeightSamples, used for compression and can be used to update the terrain after creating with lower height values. If there are any lower values in mHeightSamples, this value will be ignored. + /// Artificial minimal value of mHeightSamples, used for compression and can be used to update the terrain after creating with lower height values. If there are any lower values in mHeightSamples, this value will be ignored. float mMinHeightValue = FLT_MAX; - /// Artifical maximum value of mHeightSamples, used for compression and can be used to update the terrain after creating with higher height values. If there are any higher values in mHeightSamples, this value will be ignored. + /// Artificial maximum value of mHeightSamples, used for compression and can be used to update the terrain after creating with higher height values. If there are any higher values in mHeightSamples, this value will be ignored. float mMaxHeightValue = -FLT_MAX; /// The heightfield is divided in blocks of mBlockSize * mBlockSize * 2 triangles and the acceleration structure culls blocks only, @@ -161,7 +161,7 @@ class JPH_EXPORT HeightFieldShape final : public Shape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::GetTrianglesStart @@ -203,6 +203,30 @@ class JPH_EXPORT HeightFieldShape final : public Shape /// @param inActiveEdgeCosThresholdAngle Cosine of the threshold angle (if the angle between the two triangles is bigger than this, the edge is active, note that a concave edge is always inactive). void SetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY, const float *inHeights, uint inHeightsStride, TempAllocator &inAllocator, float inActiveEdgeCosThresholdAngle = 0.996195f); + /// Get the current list of materials, the indices returned by GetMaterials() will index into this list. + const PhysicsMaterialList & GetMaterialList() const { return mMaterials; } + + /// Get the material indices of a block of data. + /// @param inX Start X position, must in the range [0, mSampleCount - 1] + /// @param inY Start Y position, must in the range [0, mSampleCount - 1] + /// @param inSizeX Number of samples in X direction + /// @param inSizeY Number of samples in Y direction + /// @param outMaterials Returned material indices, must be at least inSizeX * inSizeY uint8s. Values are returned in x-major order. + /// @param inMaterialsStride Stride in uint8s between two consecutive rows of outMaterials. + void GetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, uint8 *outMaterials, uint inMaterialsStride) const; + + /// Set the material indices of a block of data. + /// @param inX Start X position, must in the range [0, mSampleCount - 1] + /// @param inY Start Y position, must in the range [0, mSampleCount - 1] + /// @param inSizeX Number of samples in X direction + /// @param inSizeY Number of samples in Y direction + /// @param inMaterials The new material indices, must be at least inSizeX * inSizeY uint8s. Values are returned in x-major order. + /// @param inMaterialsStride Stride in uint8s between two consecutive rows of inMaterials. + /// @param inMaterialList The material list to use for the new material indices or nullptr if the material list should not be updated + /// @param inAllocator Allocator to use for temporary memory + /// @return True if the material indices were set, false if the total number of materials exceeded 256 + bool SetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, const uint8 *inMaterials, uint inMaterialsStride, const PhysicsMaterialList *inMaterialList, TempAllocator &inAllocator); + // See Shape virtual void SaveBinaryState(StreamOut &inStream) const override; virtual void SaveMaterialState(PhysicsMaterialList &outMaterials) const override; @@ -277,8 +301,9 @@ class JPH_EXPORT HeightFieldShape final : public Shape static void sCastSphereVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector); /// Visit the entire height field using a visitor pattern + /// Note: Used to be inlined but this triggers a bug in MSVC where it will not free the memory allocated by alloca which causes a stack overflow when WalkHeightField is called in a loop (clang does it correct) template - JPH_INLINE void WalkHeightField(Visitor &ioVisitor) const; + void WalkHeightField(Visitor &ioVisitor) const; /// A block of 2x2 ranges used to form a hierarchical grid, ordered left top, right top, left bottom, right bottom struct alignas(16) RangeBlock @@ -287,7 +312,7 @@ class JPH_EXPORT HeightFieldShape final : public Shape uint16 mMax[4]; }; - /// For block (inBlockX, inBlockY) get get the range block and the entry in the range block + /// For block (inBlockX, inBlockY) get the range block and the entry in the range block inline void GetRangeBlock(uint inBlockX, uint inBlockY, uint inRangeBlockOffset, uint inRangeBlockStride, RangeBlock *&outBlock, uint &outIndexInBlock); /// Offset of first RangedBlock in grid per level diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.cpp index 09a70dc4..cf5e8640 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -92,11 +93,13 @@ void MeshShapeSettings::Sanitize() // Remove degenerate and duplicate triangles UnorderedSet triangles; triangles.reserve(mIndexedTriangles.size()); + TriangleCodec::ValidationContext validation_ctx(mIndexedTriangles, mTriangleVertices); for (int t = (int)mIndexedTriangles.size() - 1; t >= 0; --t) { const IndexedTriangle &tri = mIndexedTriangles[t]; if (tri.IsDegenerate(mTriangleVertices) // Degenerate triangle + || validation_ctx.IsDegenerate(tri) // Triangle is degenerate in the quantized space || !triangles.insert(tri.GetLowestIndexFirst()).second) // Duplicate triangle { // The order of triangles doesn't matter (gets reordered while building the tree), so we can just swap the last triangle into this slot @@ -124,10 +127,12 @@ MeshShape::MeshShape(const MeshShapeSettings &inSettings, ShapeResult &outResult } // Check triangles + TriangleCodec::ValidationContext validation_ctx(inSettings.mIndexedTriangles, inSettings.mTriangleVertices); for (int t = (int)inSettings.mIndexedTriangles.size() - 1; t >= 0; --t) { const IndexedTriangle &triangle = inSettings.mIndexedTriangles[t]; - if (triangle.IsDegenerate(inSettings.mTriangleVertices)) + if (triangle.IsDegenerate(inSettings.mTriangleVertices) + || validation_ctx.IsDegenerate(triangle)) { outResult.SetError(StringFormat("Triangle %d is degenerate!", t)); return; @@ -234,7 +239,6 @@ void MeshShape::sFindActiveEdges(const MeshShapeSettings &inSettings, IndexedTri } JPH_ASSERT(false); - JPH_CRASH; return ~uint(0); } @@ -780,9 +784,50 @@ void MeshShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShap sCollidePointUsingRayCast(*this, inPoint, inSubShapeIDCreator, ioCollector, inShapeFilter); } -void MeshShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const +void MeshShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const { - sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, inScale, ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex); + JPH_PROFILE_FUNCTION(); + + struct Visitor : public CollideSoftBodyVerticesVsTriangles + { + using CollideSoftBodyVerticesVsTriangles::CollideSoftBodyVerticesVsTriangles; + + JPH_INLINE bool ShouldAbort() const + { + return false; + } + + JPH_INLINE bool ShouldVisitNode([[maybe_unused]] int inStackTop) const + { + return mDistanceStack[inStackTop] < mClosestDistanceSq; + } + + JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop) + { + // Get distance to vertex + Vec4 dist_sq = AABox4DistanceSqToPoint(mLocalPosition, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ); + + // Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom) + return SortReverseAndStore(dist_sq, mClosestDistanceSq, ioProperties, &mDistanceStack[inStackTop]); + } + + JPH_INLINE void VisitTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, [[maybe_unused]] uint8 inActiveEdges, [[maybe_unused]] SubShapeID inSubShapeID2) + { + ProcessTriangle(inV0, inV1, inV2); + } + + float mDistanceStack[NodeCodec::StackSize]; + }; + + Visitor visitor(inCenterOfMassTransform, inScale); + + for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v) + if (v->mInvMass > 0.0f) + { + visitor.StartVertex(*v); + WalkTreePerTriangle(SubShapeIDCreator(), visitor); + visitor.FinishVertex(*v, inCollidingShapeIndex); + } } void MeshShape::sCastConvexVsMesh(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector) diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.h index 84a4ceba..04832523 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/MeshShape.h @@ -117,7 +117,7 @@ class JPH_EXPORT MeshShape final : public Shape /// Insideness is tested by counting the amount of triangles encountered when casting an infinite ray from inPoint. If the number of hits is odd we're inside, if it's even we're outside. virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::GetTrianglesStart diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h index 21bea289..3e828181 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h @@ -96,7 +96,7 @@ class JPH_EXPORT OffsetCenterOfMassShape final : public DecoratedShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::CollectTransformedShapes diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp index 85adda64..7d6e06a0 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp @@ -202,6 +202,20 @@ void RotatedTranslatedShape::sCollideShapeVsRotatedTranslated(const Shape *inSha CollisionDispatch::sCollideShapeVsShape(inShape1, shape2->mInnerShape, inScale1, shape2->TransformScale(inScale2), inCenterOfMassTransform1, transform2, inSubShapeIDCreator1, inSubShapeIDCreator2, inCollideShapeSettings, ioCollector, inShapeFilter); } +void RotatedTranslatedShape::sCollideRotatedTranslatedVsRotatedTranslated(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) +{ + JPH_ASSERT(inShape1->GetSubType() == EShapeSubType::RotatedTranslated); + const RotatedTranslatedShape *shape1 = static_cast(inShape1); + JPH_ASSERT(inShape2->GetSubType() == EShapeSubType::RotatedTranslated); + const RotatedTranslatedShape *shape2 = static_cast(inShape2); + + // Get world transform of 1 and 2 + Mat44 transform1 = inCenterOfMassTransform1 * Mat44::sRotation(shape1->mRotation); + Mat44 transform2 = inCenterOfMassTransform2 * Mat44::sRotation(shape2->mRotation); + + CollisionDispatch::sCollideShapeVsShape(shape1->mInnerShape, shape2->mInnerShape, shape1->TransformScale(inScale1), shape2->TransformScale(inScale2), transform1, transform2, inSubShapeIDCreator1, inSubShapeIDCreator2, inCollideShapeSettings, ioCollector, inShapeFilter); +} + void RotatedTranslatedShape::sCastRotatedTranslatedVsShape(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector) { // Fetch rotated translated shape from cast shape @@ -230,6 +244,25 @@ void RotatedTranslatedShape::sCastShapeVsRotatedTranslated(const ShapeCast &inSh CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, inShapeCastSettings, shape->mInnerShape, shape->TransformScale(inScale), inShapeFilter, inCenterOfMassTransform2 * local_transform, inSubShapeIDCreator1, inSubShapeIDCreator2, ioCollector); } +void RotatedTranslatedShape::sCastRotatedTranslatedVsRotatedTranslated(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector) +{ + JPH_ASSERT(inShapeCast.mShape->GetSubType() == EShapeSubType::RotatedTranslated); + const RotatedTranslatedShape *shape1 = static_cast(inShapeCast.mShape); + JPH_ASSERT(inShape->GetSubType() == EShapeSubType::RotatedTranslated); + const RotatedTranslatedShape *shape2 = static_cast(inShape); + + // Determine the local transform of shape 2 + Mat44 local_transform2 = Mat44::sRotation(shape2->mRotation); + Mat44 local_transform2_transposed = local_transform2.Transposed3x3(); + + // Transform the shape cast and update the shape + Mat44 transform = (local_transform2_transposed * inShapeCast.mCenterOfMassStart) * Mat44::sRotation(shape1->mRotation); + Vec3 scale = shape1->TransformScale(inShapeCast.mScale); + ShapeCast shape_cast(shape1->mInnerShape, scale, transform, local_transform2_transposed.Multiply3x3(inShapeCast.mDirection)); + + CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, inShapeCastSettings, shape2->mInnerShape, shape2->TransformScale(inScale), inShapeFilter, inCenterOfMassTransform2 * local_transform2, inSubShapeIDCreator1, inSubShapeIDCreator2, ioCollector); +} + void RotatedTranslatedShape::SaveBinaryState(StreamOut &inStream) const { DecoratedShape::SaveBinaryState(inStream); @@ -274,6 +307,9 @@ void RotatedTranslatedShape::sRegister() CollisionDispatch::sRegisterCastShape(EShapeSubType::RotatedTranslated, s, sCastRotatedTranslatedVsShape); CollisionDispatch::sRegisterCastShape(s, EShapeSubType::RotatedTranslated, sCastShapeVsRotatedTranslated); } + + CollisionDispatch::sRegisterCollideShape(EShapeSubType::RotatedTranslated, EShapeSubType::RotatedTranslated, sCollideRotatedTranslatedVsRotatedTranslated); + CollisionDispatch::sRegisterCastShape(EShapeSubType::RotatedTranslated, EShapeSubType::RotatedTranslated, sCastRotatedTranslatedVsRotatedTranslated); } JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h index bf56687b..c37dbca8 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h @@ -97,7 +97,7 @@ class JPH_EXPORT RotatedTranslatedShape final : public DecoratedShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::CollectTransformedShapes @@ -135,8 +135,10 @@ class JPH_EXPORT RotatedTranslatedShape final : public DecoratedShape // Helper functions called by CollisionDispatch static void sCollideRotatedTranslatedVsShape(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter); static void sCollideShapeVsRotatedTranslated(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter); + static void sCollideRotatedTranslatedVsRotatedTranslated(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter); static void sCastRotatedTranslatedVsShape(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector); static void sCastShapeVsRotatedTranslated(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector); + static void sCastRotatedTranslatedVsRotatedTranslated(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector); /// Transform the scale to the local space of the child shape inline Vec3 TransformScale(Vec3Arg inScale) const diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ScaledShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ScaledShape.h index d58fb65b..ea9b3435 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ScaledShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/ScaledShape.h @@ -93,7 +93,7 @@ class JPH_EXPORT ScaledShape final : public DecoratedShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::CollectTransformedShapes diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.cpp index d9c4c558..8412156f 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -59,7 +58,7 @@ void Shape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCol { Vec3 scale; Mat44 transform = inCenterOfMassTransform.Decompose(scale); - TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetRotation().GetQuaternion(), this, BodyID(), SubShapeIDCreator()); + TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator()); ts.SetShapeScale(scale); ioCollector.AddHit(ts); } @@ -271,42 +270,6 @@ Shape::ShapeResult Shape::ScaleShape(Vec3Arg inScale) const return compound.Create(); } -void Shape::sCollideSoftBodyVerticesUsingRayCast(const Shape &inShape, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) -{ - Mat44 inverse_transform = Mat44::sScale(inScale.Reciprocal()) * inCenterOfMassTransform.InversedRotationTranslation(); - Mat44 direction_preserving_transform = inverse_transform.Transposed3x3(); // To transform normals: transpose of the inverse - - for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v) - if (v->mInvMass > 0.0f) - { - // Calculate the distance we will move this frame - Vec3 movement = v->mVelocity * inDeltaTime + inDisplacementDueToGravity; - - RayCastResult hit; - hit.mFraction = 2.0f; // Add a little extra distance in case the particle speeds up - - RayCast ray(v->mPosition - 0.5f * movement, movement); // Start a little early in case we penetrated before - - if (inShape.CastRay(ray.Transformed(inverse_transform), SubShapeIDCreator(), hit)) - { - // Calculate penetration - float penetration = (0.5f - hit.mFraction) * movement.Length(); - if (penetration > v->mLargestPenetration) - { - v->mLargestPenetration = penetration; - - // Calculate contact point and normal - Vec3 point = ray.GetPointOnRay(hit.mFraction); - Vec3 normal = direction_preserving_transform.Multiply3x3(inShape.GetSurfaceNormal(hit.mSubShapeID2, inverse_transform * point)).Normalized(); - - // Store collision - v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal); - v->mCollidingShapeIndex = inCollidingShapeIndex; - } - } - } -} - void Shape::sCollidePointUsingRayCast(const Shape &inShape, Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) { // First test if we're inside our bounding box @@ -334,7 +297,7 @@ void Shape::sCollidePointUsingRayCast(const Shape &inShape, Vec3Arg inPoint, con RayCastSettings settings; settings.mBackFaceMode = EBackFaceMode::CollideWithBackFaces; - // Cast a ray that's 10% longer than the heigth of our bounding box + // Cast a ray that's 10% longer than the height of our bounding box inShape.CastRay(RayCast { inPoint, 1.1f * bounds.GetSize().GetY() * Vec3::sAxisY() }, settings, inSubShapeIDCreator, collector, inShapeFilter); // Odd amount of hits means inside diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.h index 9ae1e6a1..f7abc1e6 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/Shape.h @@ -145,6 +145,10 @@ class JPH_EXPORT ShapeSettings : public SerializableObject, public RefTarget, public NonCopyable /// @param ioCollector The transformed shapes will be passed to this collector virtual void TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const; - /// Scale this shape. Note that not all shapes support all scales, this will return a shape that matches the scale as accurately as possible. - /// @param inScale The scale to use for this shape (note: this scale is applied to the entire shape in the space it was created, most function apply the scale in the space of the leaf shapes and from the center of mass!) + /// Scale this shape. Note that not all shapes support all scales, this will return a shape that matches the scale as accurately as possible. See Shape::IsValidScale for more information. + /// @param inScale The scale to use for this shape (note: this scale is applied to the entire shape in the space it was created, most other functions apply the scale in the space of the leaf shapes and from the center of mass!) ShapeResult ScaleShape(Vec3Arg inScale) const; /// An opaque buffer that holds shape specific information during GetTrianglesStart/Next. @@ -409,7 +413,17 @@ class JPH_EXPORT Shape : public RefTarget, public NonCopyable virtual float GetVolume() const = 0; /// Test if inScale is a valid scale for this shape. Some shapes can only be scaled uniformly, compound shapes cannot handle shapes - /// being rotated and scaled (this would cause shearing). In this case this function will return false. + /// being rotated and scaled (this would cause shearing), scale can never be zero. When the scale is invalid, the function will return false. + /// + /// Here's a list of supported scales: + /// * SphereShape: Scale must be uniform (signs of scale are ignored). + /// * BoxShape: Any scale supported (signs of scale are ignored). + /// * TriangleShape: Any scale supported when convex radius is zero, otherwise only uniform scale supported. + /// * CapsuleShape: Scale must be uniform (signs of scale are ignored). + /// * TaperedCapsuleShape: Scale must be uniform (sign of Y scale can be used to flip the capsule). + /// * CylinderShape: Scale must be uniform in XZ plane, Y can scale independently (signs of scale are ignored). + /// * RotatedTranslatedShape: Scale must not cause shear in the child shape. + /// * CompoundShape: Scale must not cause shear in any of the child shapes. virtual bool IsValidScale(Vec3Arg inScale) const { return !inScale.IsNearZero(); } #ifdef JPH_DEBUG_RENDERER @@ -424,9 +438,6 @@ class JPH_EXPORT Shape : public RefTarget, public NonCopyable /// A fallback version of CollidePoint that uses a ray cast and counts the number of hits to determine if the point is inside the shape. Odd number of hits means inside, even number of hits means outside. static void sCollidePointUsingRayCast(const Shape &inShape, Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter); - /// A fallback version of CollideSoftBodyVertices that uses a raycast to collide the vertices with the shape. - static void sCollideSoftBodyVerticesUsingRayCast(const Shape &inShape, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex); - private: uint64 mUserData = 0; EShapeType mShapeType; diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.cpp index b8a496d5..28577f7e 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.cpp @@ -132,6 +132,7 @@ const ConvexShape::Support *SphereShape::GetSupportFunction(ESupportMode inMode, return new (&inBuffer) SphereWithConvex(scaled_radius); case ESupportMode::ExcludeConvexRadius: + case ESupportMode::Default: return new (&inBuffer) SphereNoConvex(scaled_radius); } @@ -306,7 +307,7 @@ void SphereShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedSh { Vec3 scale; Mat44 transform = inCenterOfMassTransform.Decompose(scale); - TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetRotation().GetQuaternion(), this, BodyID(), SubShapeIDCreator()); + TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator()); ts.SetShapeScale(ScaleHelpers::MakeUniformScale(scale.Abs())); ioCollector.AddHit(ts); } diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.h index 27bc4d28..9d976948 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/SphereShape.h @@ -80,7 +80,7 @@ class JPH_EXPORT SphereShape final : public ConvexShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::TransformShape diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp index 51c46e48..98386418 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp @@ -196,6 +196,7 @@ const ConvexShape::Support *TaperedCapsuleShape::GetSupportFunction(ESupportMode return new (&inBuffer) TaperedCapsule(scaled_top_center, scaled_bottom_center, scaled_top_radius, scaled_bottom_radius, 0.0f); case ESupportMode::ExcludeConvexRadius: + case ESupportMode::Default: { // Get radii reduced by convex radius float tr = scaled_top_radius - scaled_convex_radius; @@ -311,8 +312,8 @@ void TaperedCapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransfo float scale_y = abs_scale.GetY(); float scale_xz = abs_scale.GetX(); Vec3 scale_y_flip(1, Sign(inScale.GetY()), 1); - float scaled_top_center = scale_y * mTopCenter; - float scaled_bottom_center = scale_y * mBottomCenter; + Vec3 scaled_top_center(0, scale_y * mTopCenter, 0); + Vec3 scaled_bottom_center(0, scale_y * mBottomCenter, 0); float scaled_top_radius = scale_xz * mTopRadius; float scaled_bottom_radius = scale_xz * mBottomRadius; @@ -321,29 +322,39 @@ void TaperedCapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransfo { Vec3 local_pos = scale_y_flip * (inverse_transform * v->mPosition); - // See comments at TaperedCapsuleShape::GetSurfaceNormal for rationale behind the math Vec3 position, normal; - if (local_pos.GetY() > scaled_top_center + mSinAlpha * scaled_top_radius) + + // If the vertex is inside the cone starting at the top center pointing along the y-axis with angle PI/2 - alpha then the closest point is on the top sphere + // This corresponds to: Dot(y-axis, (local_pos - top_center) / |local_pos - top_center|) >= cos(PI/2 - alpha) + // <=> (local_pos - top_center).y >= sin(alpha) * |local_pos - top_center| + Vec3 top_center_to_local_pos = local_pos - scaled_top_center; + float top_center_to_local_pos_len = top_center_to_local_pos.Length(); + if (top_center_to_local_pos.GetY() >= mSinAlpha * top_center_to_local_pos_len) { // Top sphere - Vec3 top = Vec3(0, scaled_top_center, 0); - normal = (local_pos - top).NormalizedOr(Vec3::sAxisY()); - position = top + scaled_top_radius * normal; - } - else if (local_pos.GetY() < scaled_bottom_center + mSinAlpha * scaled_bottom_radius) - { - // Bottom sphere - Vec3 bottom(0, scaled_bottom_center, 0); - normal = (local_pos - bottom).NormalizedOr(-Vec3::sAxisY()); - position = bottom + scaled_bottom_radius * normal; + normal = top_center_to_local_pos_len != 0.0f? top_center_to_local_pos / top_center_to_local_pos_len : Vec3::sAxisY(); + position = scaled_top_center + scaled_top_radius * normal; } else { - // Tapered cylinder - normal = Vec3(local_pos.GetX(), 0, local_pos.GetZ()).NormalizedOr(Vec3::sAxisX()); - normal.SetY(mTanAlpha); - normal = normal.NormalizedOr(Vec3::sAxisX()); - position = Vec3(0, scaled_bottom_center, 0) + scaled_bottom_radius * normal; + // If the vertex is outside the cone starting at the bottom center pointing along the y-axis with angle PI/2 - alpha then the closest point is on the bottom sphere + // This corresponds to: Dot(y-axis, (local_pos - bottom_center) / |local_pos - bottom_center|) <= cos(PI/2 - alpha) + // <=> (local_pos - bottom_center).y <= sin(alpha) * |local_pos - bottom_center| + Vec3 bottom_center_to_local_pos = local_pos - scaled_bottom_center; + float bottom_center_to_local_pos_len = bottom_center_to_local_pos.Length(); + if (bottom_center_to_local_pos.GetY() <= mSinAlpha * bottom_center_to_local_pos_len) + { + // Bottom sphere + normal = bottom_center_to_local_pos_len != 0.0f? bottom_center_to_local_pos / bottom_center_to_local_pos_len : -Vec3::sAxisY(); + } + else + { + // Tapered cylinder + normal = Vec3(local_pos.GetX(), 0, local_pos.GetZ()).NormalizedOr(Vec3::sAxisX()); + normal.SetY(mTanAlpha); + normal = normal.NormalizedOr(Vec3::sAxisX()); + } + position = scaled_bottom_center + scaled_bottom_radius * normal; } Plane plane = Plane::sFromPointAndNormal(position, normal); @@ -399,7 +410,7 @@ void TaperedCapsuleShape::TransformShape(Mat44Arg inCenterOfMassTransform, Trans { Vec3 scale; Mat44 transform = inCenterOfMassTransform.Decompose(scale); - TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetRotation().GetQuaternion(), this, BodyID(), SubShapeIDCreator()); + TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator()); ts.SetShapeScale(scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs())); ioCollector.AddHit(ts); } diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h index 07c0cb0b..6dad3c2e 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h @@ -71,7 +71,7 @@ class JPH_EXPORT TaperedCapsuleShape final : public ConvexShape // See ConvexShape::GetSupportFunction virtual const Support * GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; #ifdef JPH_DEBUG_RENDERER diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.cpp b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.cpp index 3582f85d..b7017964 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -146,6 +146,7 @@ const ConvexShape::Support *TriangleShape::GetSupportFunction(ESupportMode inMod switch (inMode) { case ESupportMode::IncludeConvexRadius: + case ESupportMode::Default: if (mConvexRadius > 0.0f) return new (&inBuffer) TriangleWithConvex(inScale * mV1, inScale * mV2, inScale * mV3, mConvexRadius); [[fallthrough]]; @@ -259,57 +260,16 @@ void TriangleShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSub // Can't be inside a triangle } -void TriangleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const +void TriangleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const { - Vec3 v1 = inCenterOfMassTransform * (inScale * mV1); - Vec3 v2 = inCenterOfMassTransform * (inScale * mV2); - Vec3 v3 = inCenterOfMassTransform * (inScale * mV3); - - if (ScaleHelpers::IsInsideOut(inScale)) - swap(v1, v2); - - Vec3 triangle_normal = (v2 - v1).Cross(v3 - v1).NormalizedOr(Vec3::sAxisY()); + CollideSoftBodyVerticesVsTriangles collider(inCenterOfMassTransform, inScale); for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v) if (v->mInvMass > 0.0f) { - // Get the closest point from the vertex to the triangle - uint32 set; - Vec3 v1_minus_position = v1 - v->mPosition; - Vec3 closest_point = ClosestPoint::GetClosestPointOnTriangle(v1_minus_position, v2 - v->mPosition, v3 - v->mPosition, set); - - if (set == 0b111) - { - // Closest is interior to the triangle, use plane as collision plane but don't allow more than 10cm penetration - // because otherwise a triangle half a level a way will have a huge penetration if it is back facing - float penetration = min(triangle_normal.Dot(v1_minus_position), 0.1f); - if (penetration > v->mLargestPenetration) - { - v->mLargestPenetration = penetration; - - // Store collision - v->mCollisionPlane = Plane::sFromPointAndNormal(v1, triangle_normal); - v->mCollidingShapeIndex = inCollidingShapeIndex; - } - } - else if (closest_point.Dot(triangle_normal) < 0.0f) // Ignore back facing edges - { - // Closest point is on an edge or vertex, use closest point as collision plane - float closest_point_length = closest_point.Length(); - float penetration = -closest_point_length; - if (penetration > v->mLargestPenetration) - { - v->mLargestPenetration = penetration; - - // Calculate contact point and normal - Vec3 point = v->mPosition + closest_point; - Vec3 normal = -closest_point / closest_point_length; - - // Store collision - v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal); - v->mCollidingShapeIndex = inCollidingShapeIndex; - } - } + collider.StartVertex(*v); + collider.ProcessTriangle(mV1, mV2, mV3); + collider.FinishVertex(*v, inCollidingShapeIndex); } } @@ -357,7 +317,7 @@ void TriangleShape::TransformShape(Mat44Arg inCenterOfMassTransform, Transformed { Vec3 scale; Mat44 transform = inCenterOfMassTransform.Decompose(scale); - TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetRotation().GetQuaternion(), this, BodyID(), SubShapeIDCreator()); + TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator()); ts.SetShapeScale(mConvexRadius == 0.0f? scale : scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs())); ioCollector.AddHit(ts); } diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.h index 2b8f2457..990e6e05 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/Shape/TriangleShape.h @@ -84,7 +84,7 @@ class JPH_EXPORT TriangleShape final : public ConvexShape // See: Shape::CollidePoint virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override; - // See: Shape::ColideSoftBodyVertices + // See: Shape::CollideSoftBodyVertices virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override; // See Shape::TransformShape diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/SortReverseAndStore.h b/Dependencies/Jolt/Jolt/Physics/Collision/SortReverseAndStore.h index 190d09a8..4a073096 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/SortReverseAndStore.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/SortReverseAndStore.h @@ -15,18 +15,19 @@ JPH_NAMESPACE_BEGIN JPH_INLINE int SortReverseAndStore(Vec4Arg inValues, float inMaxValue, UVec4 &ioIdentifiers, float *outValues) { // Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom) - Vec4::sSort4Reverse(inValues, ioIdentifiers); + Vec4 values = inValues; + Vec4::sSort4Reverse(values, ioIdentifiers); // Count how many results are less than the max value - UVec4 closer = Vec4::sLess(inValues, Vec4::sReplicate(inMaxValue)); + UVec4 closer = Vec4::sLess(values, Vec4::sReplicate(inMaxValue)); int num_results = closer.CountTrues(); // Shift the values so that only the ones that are less than max are kept - inValues = inValues.ReinterpretAsInt().ShiftComponents4Minus(num_results).ReinterpretAsFloat(); + values = values.ReinterpretAsInt().ShiftComponents4Minus(num_results).ReinterpretAsFloat(); ioIdentifiers = ioIdentifiers.ShiftComponents4Minus(num_results); // Store the values - inValues.StoreFloat4(reinterpret_cast(outValues)); + values.StoreFloat4(reinterpret_cast(outValues)); return num_results; } diff --git a/Dependencies/Jolt/Jolt/Physics/Collision/TransformedShape.h b/Dependencies/Jolt/Jolt/Physics/Collision/TransformedShape.h index 3110de81..887a8ee4 100644 --- a/Dependencies/Jolt/Jolt/Physics/Collision/TransformedShape.h +++ b/Dependencies/Jolt/Jolt/Physics/Collision/TransformedShape.h @@ -108,7 +108,7 @@ class JPH_EXPORT TransformedShape { Vec3 scale; RMat44 rot_trans = inTransform.Decompose(scale); - SetWorldTransform(rot_trans.GetTranslation(), rot_trans.GetRotation().GetQuaternion(), scale); + SetWorldTransform(rot_trans.GetTranslation(), rot_trans.GetQuaternion(), scale); } /// Calculates the world transform including scale of this shape (not from the center of mass but in the space the shape was created) @@ -188,7 +188,7 @@ class JPH_EXPORT TransformedShape SubShapeIDCreator mSubShapeIDCreator; ///< Optional sub shape ID creator for the shape (can be used when expanding compound shapes into multiple transformed shapes) }; -static_assert(sizeof(TransformedShape) == JPH_IF_SINGLE_PRECISION_ELSE(64, 96), "Not properly packed"); +static_assert(JPH_CPU_ADDRESS_BITS != 64 || sizeof(TransformedShape) == JPH_IF_SINGLE_PRECISION_ELSE(64, 96), "Not properly packed"); static_assert(alignof(TransformedShape) == JPH_RVECTOR_ALIGNMENT, "Not properly aligned"); JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/CalculateSolverSteps.h b/Dependencies/Jolt/Jolt/Physics/Constraints/CalculateSolverSteps.h new file mode 100644 index 00000000..857eddfb --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/CalculateSolverSteps.h @@ -0,0 +1,66 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// Class used to calculate the total number of velocity and position steps +class CalculateSolverSteps +{ +public: + /// Constructor + JPH_INLINE explicit CalculateSolverSteps(const PhysicsSettings &inSettings) : mSettings(inSettings) { } + + /// Combine the number of velocity and position steps for this body/constraint with the current values + template + JPH_INLINE void operator () (const Type *inObject) + { + uint num_velocity_steps = inObject->GetNumVelocityStepsOverride(); + mNumVelocitySteps = max(mNumVelocitySteps, num_velocity_steps); + mApplyDefaultVelocity |= num_velocity_steps == 0; + + uint num_position_steps = inObject->GetNumPositionStepsOverride(); + mNumPositionSteps = max(mNumPositionSteps, num_position_steps); + mApplyDefaultPosition |= num_position_steps == 0; + } + + /// Must be called after all bodies/constraints have been processed + JPH_INLINE void Finalize() + { + // If we have a default velocity/position step count, take the max of the default and the overrides + if (mApplyDefaultVelocity) + mNumVelocitySteps = max(mNumVelocitySteps, mSettings.mNumVelocitySteps); + if (mApplyDefaultPosition) + mNumPositionSteps = max(mNumPositionSteps, mSettings.mNumPositionSteps); + } + + /// Get the results of the calculation + JPH_INLINE uint GetNumPositionSteps() const { return mNumPositionSteps; } + JPH_INLINE uint GetNumVelocitySteps() const { return mNumVelocitySteps; } + +private: + const PhysicsSettings & mSettings; + + uint mNumVelocitySteps = 0; + uint mNumPositionSteps = 0; + + bool mApplyDefaultVelocity = false; + bool mApplyDefaultPosition = false; +}; + +/// Dummy class to replace the steps calculator when we don't need the result +class DummyCalculateSolverSteps +{ +public: + template + JPH_INLINE void operator () (const Type *) const + { + /* Nothing to do */ + } +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.cpp index 3fc2e9ed..9889fa64 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.cpp @@ -129,6 +129,12 @@ void ConeConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateRotationConstraintProperties(rotation1, rotation2); } +void ConeConstraint::ResetWarmStart() +{ + mPointConstraintPart.Deactivate(); + mAngleConstraintPart.Deactivate(); +} + void ConeConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.h index e6be6267..630d089d 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConeConstraint.h @@ -19,7 +19,7 @@ class JPH_EXPORT ConeConstraintSettings final : public TwoBodyConstraintSettings // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, all properties below should be in the specified space @@ -78,6 +78,7 @@ class JPH_EXPORT ConeConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Cone; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/Constraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/Constraint.h index 1c75c92a..f8827cf6 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/Constraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/Constraint.h @@ -81,11 +81,11 @@ class JPH_EXPORT ConstraintSettings : public SerializableObject, public RefTarge /// Note that if you want a deterministic simulation and you cannot guarantee the order in which constraints are added/removed, you can make the priority for all constraints unique to get a deterministic ordering. uint32 mConstraintPriority = 0; - /// Override for the number of solver velocity iterations to run, the total amount of iterations is the max of PhysicsSettings::mNumVelocitySteps and this for all constraints in the island. - int mNumVelocityStepsOverride = 0; + /// Used only when the constraint is active. Override for the number of solver velocity iterations to run, 0 means use the default in PhysicsSettings::mNumVelocitySteps. The number of iterations to use is the max of all contacts and constraints in the island. + uint mNumVelocityStepsOverride = 0; - /// Override for the number of position velocity iterations to run, the total amount of iterations is the max of PhysicsSettings::mNumPositionSteps and this for all constraints in the island. - int mNumPositionStepsOverride = 0; + /// Used only when the constraint is active. Override for the number of solver position iterations to run, 0 means use the default in PhysicsSettings::mNumPositionSteps. The number of iterations to use is the max of all contacts and constraints in the island. + uint mNumPositionStepsOverride = 0; /// Size of constraint when drawing it through the debug renderer float mDrawConstraintSize = 1.0f; @@ -110,11 +110,13 @@ class JPH_EXPORT Constraint : public RefTarget, public NonCopyable mDrawConstraintSize(inSettings.mDrawConstraintSize), #endif // JPH_DEBUG_RENDERER mConstraintPriority(inSettings.mConstraintPriority), - mNumVelocityStepsOverride(inSettings.mNumVelocityStepsOverride), - mNumPositionStepsOverride(inSettings.mNumPositionStepsOverride), + mNumVelocityStepsOverride(uint8(inSettings.mNumVelocityStepsOverride)), + mNumPositionStepsOverride(uint8(inSettings.mNumPositionStepsOverride)), mEnabled(inSettings.mEnabled), mUserData(inSettings.mUserData) { + JPH_ASSERT(inSettings.mNumVelocityStepsOverride < 256); + JPH_ASSERT(inSettings.mNumPositionStepsOverride < 256); } /// Virtual destructor @@ -131,13 +133,13 @@ class JPH_EXPORT Constraint : public RefTarget, public NonCopyable uint32 GetConstraintPriority() const { return mConstraintPriority; } void SetConstraintPriority(uint32 inPriority) { mConstraintPriority = inPriority; } - /// Override for the number of solver velocity iterations to run, the total amount of iterations is the max of PhysicsSettings::mNumVelocitySteps and this for all constraints in the island. - void SetNumVelocityStepsOverride(int inN) { mNumVelocityStepsOverride = inN; } - int GetNumVelocityStepsOverride() const { return mNumVelocityStepsOverride; } + /// Used only when the constraint is active. Override for the number of solver velocity iterations to run, 0 means use the default in PhysicsSettings::mNumVelocitySteps. The number of iterations to use is the max of all contacts and constraints in the island. + void SetNumVelocityStepsOverride(uint inN) { JPH_ASSERT(inN < 256); mNumVelocityStepsOverride = uint8(inN); } + uint GetNumVelocityStepsOverride() const { return mNumVelocityStepsOverride; } - /// Override for the number of position velocity iterations to run, the total amount of iterations is the max of PhysicsSettings::mNumPositionSteps and this for all constraints in the island. - void SetNumPositionStepsOverride(int inN) { mNumPositionStepsOverride = inN; } - int GetNumPositionStepsOverride() const { return mNumPositionStepsOverride; } + /// Used only when the constraint is active. Override for the number of solver position iterations to run, 0 means use the default in PhysicsSettings::mNumPositionSteps. The number of iterations to use is the max of all contacts and constraints in the island. + void SetNumPositionStepsOverride(uint inN) { JPH_ASSERT(inN < 256); mNumPositionStepsOverride = uint8(inN); } + uint GetNumPositionStepsOverride() const { return mNumPositionStepsOverride; } /// Enable / disable this constraint. This can e.g. be used to implement a breakable constraint by detecting that the constraint impulse /// (see e.g. PointConstraint::GetTotalLambdaPosition) went over a certain limit and then disabling the constraint. @@ -158,6 +160,12 @@ class JPH_EXPORT Constraint : public RefTarget, public NonCopyable /// @param inDeltaCOM The delta of the center of mass of the body (shape->GetCenterOfMass() - shape_before_change->GetCenterOfMass()) virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) = 0; + /// Notify the system that the configuration of the bodies and/or constraint has changed enough so that the warm start impulses should not be applied the next frame. + /// You can use this function for example when repositioning a ragdoll through Ragdoll::SetPose in such a way that the orientation of the bodies completely changes so that + /// the previous frame impulses are no longer a good approximation of what the impulses will be in the next frame. Calling this function when there are no big changes + /// will result in the constraints being much 'softer' than usual so they are more easily violated (e.g. a long chain of bodies might sag a bit if you call this every frame). + virtual void ResetWarmStart() = 0; + ///@name Solver interface ///@{ virtual bool IsActive() const { return mEnabled; } @@ -214,11 +222,11 @@ class JPH_EXPORT Constraint : public RefTarget, public NonCopyable /// Priority of the constraint when solving. Higher numbers have are more likely to be solved correctly. uint32 mConstraintPriority = 0; - /// Override for the number of solver velocity iterations to run, the total amount of iterations is the max of PhysicsSettings::mNumVelocitySteps and this for all constraints in the island. - int mNumVelocityStepsOverride = 0; + /// Used only when the constraint is active. Override for the number of solver velocity iterations to run, 0 means use the default in PhysicsSettings::mNumVelocitySteps. The number of iterations to use is the max of all contacts and constraints in the island. + uint8 mNumVelocityStepsOverride = 0; - /// Override for the number of position velocity iterations to run, the total amount of iterations is the max of PhysicsSettings::mNumPositionSteps and this for all constraints in the island. - int mNumPositionStepsOverride = 0; + /// Used only when the constraint is active. Override for the number of solver position iterations to run, 0 means use the default in PhysicsSettings::mNumPositionSteps. The number of iterations to use is the max of all contacts and constraints in the island. + uint8 mNumPositionStepsOverride = 0; /// If this constraint is currently enabled bool mEnabled = true; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.cpp index fcde8c0a..509bddbd 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -122,28 +123,22 @@ void ConstraintManager::sSetupVelocityConstraints(Constraint **inActiveConstrain (*c)->SetupVelocityConstraint(inDeltaTime); } -void ConstraintManager::sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio) +template +void ConstraintManager::sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, ConstraintCallback &ioCallback) { JPH_PROFILE_FUNCTION(); for (const uint32 *constraint_idx = inConstraintIdxBegin; constraint_idx < inConstraintIdxEnd; ++constraint_idx) { Constraint *c = inActiveConstraints[*constraint_idx]; + ioCallback(c); c->WarmStartVelocityConstraint(inWarmStartImpulseRatio); } } -void ConstraintManager::sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, int &ioNumVelocitySteps) -{ - JPH_PROFILE_FUNCTION(); - - for (const uint32 *constraint_idx = inConstraintIdxBegin; constraint_idx < inConstraintIdxEnd; ++constraint_idx) - { - Constraint *c = inActiveConstraints[*constraint_idx]; - ioNumVelocitySteps = max(ioNumVelocitySteps, c->GetNumVelocityStepsOverride()); - c->WarmStartVelocityConstraint(inWarmStartImpulseRatio); - } -} +// Specialize for the two constraint callback types +template void ConstraintManager::sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, CalculateSolverSteps &ioCallback); +template void ConstraintManager::sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, DummyCalculateSolverSteps &ioCallback); bool ConstraintManager::sSolveVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime) { @@ -175,22 +170,6 @@ bool ConstraintManager::sSolvePositionConstraints(Constraint **inActiveConstrain return any_impulse_applied; } -bool ConstraintManager::sSolvePositionConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime, float inBaumgarte, int &ioNumPositionSteps) -{ - JPH_PROFILE_FUNCTION(); - - bool any_impulse_applied = false; - - for (const uint32 *constraint_idx = inConstraintIdxBegin; constraint_idx < inConstraintIdxEnd; ++constraint_idx) - { - Constraint *c = inActiveConstraints[*constraint_idx]; - ioNumPositionSteps = max(ioNumPositionSteps, c->GetNumPositionStepsOverride()); - any_impulse_applied |= c->SolvePositionConstraint(inDeltaTime, inBaumgarte); - } - - return any_impulse_applied; -} - #ifdef JPH_DEBUG_RENDERER void ConstraintManager::DrawConstraints(DebugRenderer *inRenderer) const { diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.h index a5c2ec6b..7be8b069 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintManager.h @@ -58,10 +58,8 @@ class JPH_EXPORT ConstraintManager : public NonCopyable static void sSetupVelocityConstraints(Constraint **inActiveConstraints, uint32 inNumActiveConstraints, float inDeltaTime); /// Apply last frame's impulses, must be called prior to SolveVelocityConstraints - static void sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio); - - /// Same as above but also calculates the number of velocity steps - static void sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, int &ioNumVelocitySteps); + template + static void sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, ConstraintCallback &ioCallback); /// This function is called multiple times to iteratively come to a solution that meets all velocity constraints static bool sSolveVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime); @@ -69,9 +67,6 @@ class JPH_EXPORT ConstraintManager : public NonCopyable /// This function is called multiple times to iteratively come to a solution that meets all position constraints static bool sSolvePositionConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime, float inBaumgarte); - /// Same as above but also calculates the number of position steps - static bool sSolvePositionConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime, float inBaumgarte, int &ioNumPositionSteps); - #ifdef JPH_DEBUG_RENDERER /// Draw all constraints void DrawConstraints(DebugRenderer *inRenderer) const; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h index e95b5d98..541050b9 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h @@ -226,9 +226,15 @@ class AxisConstraintPart template JPH_INLINE void TemplatedCalculateConstraintProperties(float inInvMass1, Mat44Arg inInvI1, Vec3Arg inR1PlusU, float inInvMass2, Mat44Arg inInvI2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis, float inBias = 0.0f) { - mEffectiveMass = 1.0f / TemplatedCalculateInverseEffectiveMass(inInvMass1, inInvI1, inR1PlusU, inInvMass2, inInvI2, inR2, inWorldSpaceAxis); + float inv_effective_mass = TemplatedCalculateInverseEffectiveMass(inInvMass1, inInvI1, inR1PlusU, inInvMass2, inInvI2, inR2, inWorldSpaceAxis); - mSpringPart.CalculateSpringPropertiesWithBias(inBias); + if (inv_effective_mass == 0.0f) + Deactivate(); + else + { + mEffectiveMass = 1.0f / inv_effective_mass; + mSpringPart.CalculateSpringPropertiesWithBias(inBias); + } JPH_DET_LOG("TemplatedCalculateConstraintProperties: invM1: " << inInvMass1 << " invI1: " << inInvI1 << " r1PlusU: " << inR1PlusU << " invM2: " << inInvMass2 << " invI2: " << inInvI2 << " r2: " << inR2 << " bias: " << inBias << " r1PlusUxAxis: " << mR1PlusUxAxis << " r2xAxis: " << mR2xAxis << " invI1_R1PlusUxAxis: " << mInvI1_R1PlusUxAxis << " invI2_R2xAxis: " << mInvI2_R2xAxis << " effectiveMass: " << mEffectiveMass << " totalLambda: " << mTotalLambda); } diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h index 20d0b02f..c0ebe5ac 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h @@ -13,22 +13,22 @@ JPH_NAMESPACE_BEGIN /** Constrains movement on 2 axis - + @see "Constraints Derivation for Rigid Body Simulation in 3D" - Daniel Chappuis, section 2.3.1 - + Constraint equation (eq 51): - + \f[C = \begin{bmatrix} (p_2 - p_1) \cdot n_1 \\ (p_2 - p_1) \cdot n_2\end{bmatrix}\f] - + Jacobian (transposed) (eq 55): - + \f[J^T = \begin{bmatrix} -n_1 & -n_2 \\ -(r_1 + u) \times n_1 & -(r_1 + u) \times n_2 \\ n_1 & n_2 \\ r_2 \times n_1 & r_2 \times n_2 \end{bmatrix}\f] - + Used terms (here and below, everything in world space):\n n1, n2 = constraint axis (normalized).\n p1, p2 = constraint points.\n diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h index 76cd14bb..34319e98 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h @@ -22,7 +22,7 @@ JPH_NAMESPACE_BEGIN /// \f[J = \begin{bmatrix}0 & -E & 0 & E\end{bmatrix}\f] /// /// Used terms (here and below, everything in world space):\n -/// delta_theta_* = difference in rotation between initial rotation of bodyies 1 and 2.\n +/// delta_theta_* = difference in rotation between initial rotation of bodies 1 and 2.\n /// x1, x2 = center of mass for the bodies.\n /// v = [v1, w1, v2, w2].\n /// v1, v2 = linear velocity of body 1 and 2.\n @@ -70,7 +70,7 @@ class RotationEulerConstraintPart // // q20 = initial orientation of body 2 // q10 = initial orientation of body 1 - // r0 = initial rotation rotation from body 1 to body 2 + // r0 = initial rotation from body 1 to body 2 return inBody2.GetRotation().Conjugated() * inBody1.GetRotation(); } @@ -90,7 +90,7 @@ class RotationEulerConstraintPart // where: // // q10, q20 = world space initial orientation of body 1 and 2 - // r0 = initial rotation rotation from body 1 to body 2 in local space of body 1 + // r0 = initial rotation from body 1 to body 2 in local space of body 1 // // We can also write this in terms of the constraint matrices: // diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h index 3a52aba4..e7fb9672 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h @@ -121,7 +121,7 @@ class RotationQuatConstraintPart // // q20 = initial orientation of body 2 // q10 = initial orientation of body 1 - // r0 = initial rotation rotation from body 1 to body 2 + // r0 = initial rotation from body 1 to body 2 return inBody2.GetRotation().Conjugated() * inBody1.GetRotation(); } diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SpringPart.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SpringPart.h index bc34166f..0a8a4a97 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SpringPart.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SpringPart.h @@ -15,7 +15,7 @@ class SpringPart private: JPH_INLINE void CalculateSpringPropertiesHelper(float inDeltaTime, float inInvEffectiveMass, float inBias, float inC, float inStiffness, float inDamping, float &outEffectiveMass) { - // Soft constraints as per: Soft Contraints: Reinventing The Spring - Erin Catto - GDC 2011 + // Soft constraints as per: Soft Constraints: Reinventing The Spring - Erin Catto - GDC 2011 // Note that the calculation of beta and gamma below are based on the solution of an implicit Euler integration scheme // This scheme is unconditionally stable but has built in damping, so even when you set the damping ratio to 0 there will still diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h index c367fea2..c2a47547 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h @@ -10,6 +10,13 @@ JPH_NAMESPACE_BEGIN +/// How the swing limit behaves +enum class ESwingType : uint8 +{ + Cone, ///< Swing is limited by a cone shape, note that this cone starts to deform for larger swing angles. Cone limits only support limits that are symmetric around 0. + Pyramid, ///< Swing is limited by a pyramid shape, note that this pyramid starts to deform for larger swing angles. +}; + /// Quaternion based constraint that decomposes the rotation in constraint space in swing and twist: q = q_swing * q_twist /// where q_swing.x = 0 and where q_twist.y = q_twist.z = 0 /// @@ -25,23 +32,46 @@ JPH_NAMESPACE_BEGIN class SwingTwistConstraintPart { public: + /// Override the swing type + void SetSwingType(ESwingType inSwingType) + { + mSwingType = inSwingType; + } + + /// Get the swing type for this part + ESwingType GetSwingType() const + { + return mSwingType; + } + /// Set limits for this constraint (see description above for parameters) - void SetLimits(float inTwistMinAngle, float inTwistMaxAngle, float inSwingYHalfAngle, float inSwingZHalfAngle) + void SetLimits(float inTwistMinAngle, float inTwistMaxAngle, float inSwingYMinAngle, float inSwingYMaxAngle, float inSwingZMinAngle, float inSwingZMaxAngle) { constexpr float cLockedAngle = DegreesToRadians(0.5f); constexpr float cFreeAngle = DegreesToRadians(179.5f); // Assume sane input - JPH_ASSERT(inTwistMinAngle <= 0.0f && inTwistMinAngle >= -JPH_PI); - JPH_ASSERT(inTwistMaxAngle >= 0.0f && inTwistMaxAngle <= JPH_PI); - JPH_ASSERT(inSwingYHalfAngle >= 0.0f && inSwingYHalfAngle <= JPH_PI); - JPH_ASSERT(inSwingZHalfAngle >= 0.0f && inSwingZHalfAngle <= JPH_PI); + JPH_ASSERT(inTwistMinAngle <= inTwistMaxAngle); + JPH_ASSERT(inSwingYMinAngle <= inSwingYMaxAngle); + JPH_ASSERT(inSwingZMinAngle <= inSwingZMaxAngle); + JPH_ASSERT(inSwingYMinAngle >= -JPH_PI && inSwingYMaxAngle <= JPH_PI); + JPH_ASSERT(inSwingZMinAngle >= -JPH_PI && inSwingZMaxAngle <= JPH_PI); // Calculate the sine and cosine of the half angles - Vec4 s, c; - (0.5f * Vec4(inTwistMinAngle, inTwistMaxAngle, inSwingYHalfAngle, inSwingZHalfAngle)).SinCos(s, c); - - // Store axis flags which are used at runtime to quickly decided which contraints to apply + Vec4 half_twist = 0.5f * Vec4(inTwistMinAngle, inTwistMaxAngle, 0, 0); + Vec4 twist_s, twist_c; + half_twist.SinCos(twist_s, twist_c); + Vec4 half_swing = 0.5f * Vec4(inSwingYMinAngle, inSwingYMaxAngle, inSwingZMinAngle, inSwingZMaxAngle); + Vec4 swing_s, swing_c; + half_swing.SinCos(swing_s, swing_c); + + // Store half angles for pyramid limit + mSwingYHalfMinAngle = half_swing.GetX(); + mSwingYHalfMaxAngle = half_swing.GetY(); + mSwingZHalfMinAngle = half_swing.GetZ(); + mSwingZHalfMaxAngle = half_swing.GetW(); + + // Store axis flags which are used at runtime to quickly decided which constraints to apply mRotationFlags = 0; if (inTwistMinAngle > -cLockedAngle && inTwistMaxAngle < cLockedAngle) { @@ -61,50 +91,91 @@ class SwingTwistConstraintPart } else { - mSinTwistHalfMinAngle = s.GetX(); - mSinTwistHalfMaxAngle = s.GetY(); - mCosTwistHalfMinAngle = c.GetX(); - mCosTwistHalfMaxAngle = c.GetY(); + mSinTwistHalfMinAngle = twist_s.GetX(); + mSinTwistHalfMaxAngle = twist_s.GetY(); + mCosTwistHalfMinAngle = twist_c.GetX(); + mCosTwistHalfMaxAngle = twist_c.GetY(); } - if (inSwingYHalfAngle < cLockedAngle) + if (inSwingYMinAngle > -cLockedAngle && inSwingYMaxAngle < cLockedAngle) { mRotationFlags |= SwingYLocked; - mSinSwingYQuarterAngle = 0.0f; + mSinSwingYHalfMinAngle = 0.0f; + mSinSwingYHalfMaxAngle = 0.0f; + mCosSwingYHalfMinAngle = 1.0f; + mCosSwingYHalfMaxAngle = 1.0f; } - else if (inSwingYHalfAngle > cFreeAngle) + else if (inSwingYMinAngle < -cFreeAngle && inSwingYMaxAngle > cFreeAngle) { mRotationFlags |= SwingYFree; - mSinSwingYQuarterAngle = 1.0f; + mSinSwingYHalfMinAngle = -1.0f; + mSinSwingYHalfMaxAngle = 1.0f; + mCosSwingYHalfMinAngle = 0.0f; + mCosSwingYHalfMaxAngle = 0.0f; } else { - mSinSwingYQuarterAngle = s.GetZ(); + mSinSwingYHalfMinAngle = swing_s.GetX(); + mSinSwingYHalfMaxAngle = swing_s.GetY(); + mCosSwingYHalfMinAngle = swing_c.GetX(); + mCosSwingYHalfMaxAngle = swing_c.GetY(); + JPH_ASSERT(mSinSwingYHalfMinAngle <= mSinSwingYHalfMaxAngle); } - if (inSwingZHalfAngle < cLockedAngle) + if (inSwingZMinAngle > -cLockedAngle && inSwingZMaxAngle < cLockedAngle) { mRotationFlags |= SwingZLocked; - mSinSwingZQuarterAngle = 0.0f; + mSinSwingZHalfMinAngle = 0.0f; + mSinSwingZHalfMaxAngle = 0.0f; + mCosSwingZHalfMinAngle = 1.0f; + mCosSwingZHalfMaxAngle = 1.0f; } - else if (inSwingZHalfAngle > cFreeAngle) + else if (inSwingZMinAngle < -cFreeAngle && inSwingZMaxAngle > cFreeAngle) { mRotationFlags |= SwingZFree; - mSinSwingZQuarterAngle = 1.0f; + mSinSwingZHalfMinAngle = -1.0f; + mSinSwingZHalfMaxAngle = 1.0f; + mCosSwingZHalfMinAngle = 0.0f; + mCosSwingZHalfMaxAngle = 0.0f; } else { - mSinSwingZQuarterAngle = s.GetW(); + mSinSwingZHalfMinAngle = swing_s.GetZ(); + mSinSwingZHalfMaxAngle = swing_s.GetW(); + mCosSwingZHalfMinAngle = swing_c.GetZ(); + mCosSwingZHalfMaxAngle = swing_c.GetW(); + JPH_ASSERT(mSinSwingZHalfMinAngle <= mSinSwingZHalfMaxAngle); } } + /// Flags to indicate which axis got clamped by ClampSwingTwist + static constexpr uint cClampedTwistMin = 1 << 0; + static constexpr uint cClampedTwistMax = 1 << 1; + static constexpr uint cClampedSwingYMin = 1 << 2; + static constexpr uint cClampedSwingYMax = 1 << 3; + static constexpr uint cClampedSwingZMin = 1 << 4; + static constexpr uint cClampedSwingZMax = 1 << 5; + + /// Helper function to determine if we're clamped against the min or max limit + static JPH_INLINE bool sDistanceToMinShorter(float inDeltaMin, float inDeltaMax) + { + // We're outside of the limits, get actual delta to min/max range + // Note that a swing/twist of -1 and 1 represent the same angle, so if the difference is bigger than 1, the shortest angle is the other way around (2 - difference) + // We should actually be working with angles rather than sin(angle / 2). When the difference is small the approximation is accurate, but + // when working with extreme values the calculation is off and e.g. when the limit is between 0 and 180 a value of approx -60 will clamp + // to 180 rather than 0 (you'd expect anything > -90 to go to 0). + inDeltaMin = abs(inDeltaMin); + if (inDeltaMin > 1.0f) inDeltaMin = 2.0f - inDeltaMin; + inDeltaMax = abs(inDeltaMax); + if (inDeltaMax > 1.0f) inDeltaMax = 2.0f - inDeltaMax; + return inDeltaMin < inDeltaMax; + } + /// Clamp twist and swing against the constraint limits, returns which parts were clamped (everything assumed in constraint space) - inline void ClampSwingTwist(Quat &ioSwing, bool &outSwingYClamped, bool &outSwingZClamped, Quat &ioTwist, bool &outTwistClamped) const + inline void ClampSwingTwist(Quat &ioSwing, Quat &ioTwist, uint &outClampedAxis) const { // Start with not clamped - outTwistClamped = false; - outSwingYClamped = false; - outSwingZClamped = false; + outClampedAxis = 0; // Check that swing and twist quaternions don't contain rotations around the wrong axis JPH_ASSERT(ioSwing.GetX() == 0.0f); @@ -122,11 +193,8 @@ class SwingTwistConstraintPart if (mRotationFlags & TwistXLocked) { // Twist axis is locked, clamp whenever twist is not identity - if (ioTwist.GetX() != 0.0f) - { - ioTwist = Quat::sIdentity(); - outTwistClamped = true; - } + outClampedAxis |= ioTwist.GetX() != 0.0f? (cClampedTwistMin | cClampedTwistMax) : 0; + ioTwist = Quat::sIdentity(); } else if ((mRotationFlags & TwistXFree) == 0) { @@ -135,22 +203,17 @@ class SwingTwistConstraintPart float delta_max = ioTwist.GetX() - mSinTwistHalfMaxAngle; if (delta_min > 0.0f || delta_max > 0.0f) { - // We're outside of the limits, get actual delta to min/max range - // Note that a twist of -1 and 1 represent the same angle, so if the difference is bigger than 1, the shortest angle is the other way around (2 - difference) - // We should actually be working with angles rather than sin(angle / 2). When the difference is small the approximation is accurate, but - // when working with extreme values the calculation is off and e.g. when the limit is between 0 and 180 a value of approx -60 will clamp - // to 180 rather than 0 (you'd expect anything > -90 to go to 0). - delta_min = abs(delta_min); - if (delta_min > 1.0f) delta_min = 2.0f - delta_min; - delta_max = abs(delta_max); - if (delta_max > 1.0f) delta_max = 2.0f - delta_max; - // Pick the twist that corresponds to the smallest delta - if (delta_min < delta_max) + if (sDistanceToMinShorter(delta_min, delta_max)) + { ioTwist = Quat(mSinTwistHalfMinAngle, 0, 0, mCosTwistHalfMinAngle); + outClampedAxis |= cClampedTwistMin; + } else + { ioTwist = Quat(mSinTwistHalfMaxAngle, 0, 0, mCosTwistHalfMaxAngle); - outTwistClamped = true; + outClampedAxis |= cClampedTwistMax; + } } } @@ -160,41 +223,99 @@ class SwingTwistConstraintPart if (mRotationFlags & SwingZLocked) { // Both swing Y and Z are disabled, no degrees of freedom in swing - outSwingYClamped = ioSwing.GetY() != 0.0f; - outSwingZClamped = ioSwing.GetZ() != 0.0f; - if (outSwingYClamped || outSwingZClamped) - ioSwing = Quat::sIdentity(); + outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0; + outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0; + ioSwing = Quat::sIdentity(); } else { // Swing Y angle disabled, only 1 degree of freedom in swing - float z = Clamp(ioSwing.GetZ(), -mSinSwingZQuarterAngle, mSinSwingZQuarterAngle); - outSwingYClamped = ioSwing.GetY() != 0.0f; - outSwingZClamped = z != ioSwing.GetZ(); - if (outSwingYClamped || outSwingZClamped) + outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0; + float delta_min = mSinSwingZHalfMinAngle - ioSwing.GetZ(); + float delta_max = ioSwing.GetZ() - mSinSwingZHalfMaxAngle; + if (delta_min > 0.0f || delta_max > 0.0f) + { + // Pick the swing that corresponds to the smallest delta + if (sDistanceToMinShorter(delta_min, delta_max)) + { + ioSwing = Quat(0, 0, mSinSwingZHalfMinAngle, mCosSwingZHalfMinAngle); + outClampedAxis |= cClampedSwingZMin; + } + else + { + ioSwing = Quat(0, 0, mSinSwingZHalfMaxAngle, mCosSwingZHalfMaxAngle); + outClampedAxis |= cClampedSwingZMax; + } + } + else if ((outClampedAxis & cClampedSwingYMin) != 0) + { + float z = ioSwing.GetZ(); ioSwing = Quat(0, 0, z, sqrt(1.0f - Square(z))); + } } } else if (mRotationFlags & SwingZLocked) { // Swing Z angle disabled, only 1 degree of freedom in swing - float y = Clamp(ioSwing.GetY(), -mSinSwingYQuarterAngle, mSinSwingYQuarterAngle); - outSwingYClamped = y != ioSwing.GetY(); - outSwingZClamped = ioSwing.GetZ() != 0.0f; - if (outSwingYClamped || outSwingZClamped) + outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0; + float delta_min = mSinSwingYHalfMinAngle - ioSwing.GetY(); + float delta_max = ioSwing.GetY() - mSinSwingYHalfMaxAngle; + if (delta_min > 0.0f || delta_max > 0.0f) + { + // Pick the swing that corresponds to the smallest delta + if (sDistanceToMinShorter(delta_min, delta_max)) + { + ioSwing = Quat(0, mSinSwingYHalfMinAngle, 0, mCosSwingYHalfMinAngle); + outClampedAxis |= cClampedSwingYMin; + } + else + { + ioSwing = Quat(0, mSinSwingYHalfMaxAngle, 0, mCosSwingYHalfMaxAngle); + outClampedAxis |= cClampedSwingYMax; + } + } + else if ((outClampedAxis & cClampedSwingZMin) != 0) + { + float y = ioSwing.GetY(); ioSwing = Quat(0, y, 0, sqrt(1.0f - Square(y))); + } } else { - // Two degrees of freedom, use ellipse to solve limits - Ellipse ellipse(mSinSwingYQuarterAngle, mSinSwingZQuarterAngle); - Float2 point(ioSwing.GetY(), ioSwing.GetZ()); - if (!ellipse.IsInside(point)) + // Two degrees of freedom + if (mSwingType == ESwingType::Cone) + { + // Use ellipse to solve limits + Ellipse ellipse(mSinSwingYHalfMaxAngle, mSinSwingZHalfMaxAngle); + Float2 point(ioSwing.GetY(), ioSwing.GetZ()); + if (!ellipse.IsInside(point)) + { + Float2 closest = ellipse.GetClosestPoint(point); + ioSwing = Quat(0, closest.x, closest.y, sqrt(max(0.0f, 1.0f - Square(closest.x) - Square(closest.y)))); + outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here + } + } + else { - Float2 closest = ellipse.GetClosestPoint(point); - ioSwing = Quat(0, closest.x, closest.y, sqrt(max(0.0f, 1.0f - Square(closest.x) - Square(closest.y)))); - outSwingYClamped = true; - outSwingZClamped = true; + // Use pyramid to solve limits + // The quaternion rotating by angle y around the Y axis then rotating by angle z around the Z axis is: + // q = Quat::sRotation(Vec3::sAxisZ(), z) * Quat::sRotation(Vec3::sAxisY(), y) + // [q.x, q.y, q.z, q.w] = [-sin(y / 2) * sin(z / 2), sin(y / 2) * cos(z / 2), cos(y / 2) * sin(z / 2), cos(y / 2) * cos(z / 2)] + // So we can calculate y / 2 = atan2(q.y, q.w) and z / 2 = atan2(q.z, q.w) + Vec4 half_angle = Vec4::sATan2(ioSwing.GetXYZW().Swizzle(), ioSwing.GetXYZW().SplatW()); + Vec4 min_half_angle(mSwingYHalfMinAngle, mSwingYHalfMinAngle, mSwingZHalfMinAngle, mSwingZHalfMinAngle); + Vec4 max_half_angle(mSwingYHalfMaxAngle, mSwingYHalfMaxAngle, mSwingZHalfMaxAngle, mSwingZHalfMaxAngle); + Vec4 clamped_half_angle = Vec4::sMin(Vec4::sMax(half_angle, min_half_angle), max_half_angle); + UVec4 unclamped = Vec4::sEquals(half_angle, clamped_half_angle); + if (!unclamped.TestAllTrue()) + { + // We now calculate the quaternion again using the formula for q above, + // but we leave out the x component in order to not introduce twist + Vec4 s, c; + clamped_half_angle.SinCos(s, c); + ioSwing = Quat(0, s.GetY() * c.GetZ(), c.GetY() * s.GetZ(), c.GetY() * c.GetZ()).Normalized(); + outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here + } } } @@ -221,8 +342,8 @@ class SwingTwistConstraintPart // Clamp against joint limits Quat q_clamped_swing = q_swing, q_clamped_twist = q_twist; - bool swing_y_clamped, swing_z_clamped, twist_clamped; - ClampSwingTwist(q_clamped_swing, swing_y_clamped, swing_z_clamped, q_clamped_twist, twist_clamped); + uint clamped_axis; + ClampSwingTwist(q_clamped_swing, q_clamped_twist, clamped_axis); if (mRotationFlags & SwingYLocked) { @@ -240,10 +361,10 @@ class SwingTwistConstraintPart { // Swing only locked around Y mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis); - if (swing_z_clamped) + if ((clamped_axis & (cClampedSwingZMin | cClampedSwingZMax)) != 0) { - if (Sign(q_swing.GetW()) * q_swing.GetZ() < 0.0f) - mWorldSpaceSwingLimitZRotationAxis = -mWorldSpaceSwingLimitZRotationAxis; // Flip axis if angle is negative because the impulse limit is going to be between [-FLT_MAX, 0] + if ((clamped_axis & cClampedSwingZMin) != 0) + mWorldSpaceSwingLimitZRotationAxis = -mWorldSpaceSwingLimitZRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0] mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis); } else @@ -257,10 +378,10 @@ class SwingTwistConstraintPart mWorldSpaceSwingLimitYRotationAxis = twist_to_world.RotateAxisY(); mWorldSpaceSwingLimitZRotationAxis = twist_to_world.RotateAxisZ(); - if (swing_y_clamped) + if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax)) != 0) { - if (Sign(q_swing.GetW()) * q_swing.GetY() < 0.0f) - mWorldSpaceSwingLimitYRotationAxis = -mWorldSpaceSwingLimitYRotationAxis; // Flip axis if angle is negative because the impulse limit is going to be between [-FLT_MAX, 0] + if ((clamped_axis & cClampedSwingYMin) != 0) + mWorldSpaceSwingLimitYRotationAxis = -mWorldSpaceSwingLimitYRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0] mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis); } else @@ -270,7 +391,7 @@ class SwingTwistConstraintPart else if ((mRotationFlags & SwingYZFree) != SwingYZFree) { // Swing has limits around Y and Z - if (swing_y_clamped || swing_z_clamped) + if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax)) != 0) { // Calculate axis of rotation from clamped swing to swing Vec3 current = (inConstraintToWorld * q_swing).RotateAxisX(); @@ -305,11 +426,11 @@ class SwingTwistConstraintPart else if ((mRotationFlags & TwistXFree) == 0) { // Twist has limits - if (twist_clamped) + if ((clamped_axis & (cClampedTwistMin | cClampedTwistMax)) != 0) { mWorldSpaceTwistLimitRotationAxis = (inConstraintToWorld * q_swing).RotateAxisX(); - if (Sign(q_twist.GetW()) * q_twist.GetX() < 0.0f) - mWorldSpaceTwistLimitRotationAxis = -mWorldSpaceTwistLimitRotationAxis; // Flip axis if angle is negative because the impulse limit is going to be between [-FLT_MAX, 0] + if ((clamped_axis & cClampedTwistMin) != 0) + mWorldSpaceTwistLimitRotationAxis = -mWorldSpaceTwistLimitRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0] mTwistLimitConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceTwistLimitRotationAxis); } else @@ -351,14 +472,14 @@ class SwingTwistConstraintPart // Solve swing constraint if (mSwingLimitYConstraintPart.IsActive()) - impulse |= mSwingLimitYConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitYRotationAxis, -FLT_MAX, (mRotationFlags & SwingYLocked)? FLT_MAX : 0.0f); + impulse |= mSwingLimitYConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitYRotationAxis, -FLT_MAX, mSinSwingYHalfMinAngle == mSinSwingYHalfMaxAngle? FLT_MAX : 0.0f); if (mSwingLimitZConstraintPart.IsActive()) - impulse |= mSwingLimitZConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitZRotationAxis, -FLT_MAX, (mRotationFlags & SwingZLocked)? FLT_MAX : 0.0f); + impulse |= mSwingLimitZConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitZRotationAxis, -FLT_MAX, mSinSwingZHalfMinAngle == mSinSwingZHalfMaxAngle? FLT_MAX : 0.0f); // Solve twist constraint if (mTwistLimitConstraintPart.IsActive()) - impulse |= mTwistLimitConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceTwistLimitRotationAxis, -FLT_MAX, (mRotationFlags & TwistXLocked)? FLT_MAX : 0.0f); + impulse |= mTwistLimitConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceTwistLimitRotationAxis, -FLT_MAX, mSinTwistHalfMinAngle == mSinTwistHalfMaxAngle? FLT_MAX : 0.0f); return impulse; } @@ -374,11 +495,11 @@ class SwingTwistConstraintPart Quat q_swing, q_twist; inConstraintRotation.GetSwingTwist(q_swing, q_twist); - bool swing_y_clamped, swing_z_clamped, twist_clamped; - ClampSwingTwist(q_swing, swing_y_clamped, swing_z_clamped, q_twist, twist_clamped); + uint clamped_axis; + ClampSwingTwist(q_swing, q_twist, clamped_axis); // Solve rotation violations - if (swing_y_clamped || swing_z_clamped || twist_clamped) + if (clamped_axis != 0) { RotationEulerConstraintPart part; Quat inv_initial_orientation = inConstraintToBody2 * (inConstraintToBody1 * q_swing * q_twist).Conjugated(); @@ -442,12 +563,23 @@ class SwingTwistConstraintPart uint8 mRotationFlags; // Constants + ESwingType mSwingType = ESwingType::Cone; float mSinTwistHalfMinAngle; float mSinTwistHalfMaxAngle; float mCosTwistHalfMinAngle; float mCosTwistHalfMaxAngle; - float mSinSwingYQuarterAngle; - float mSinSwingZQuarterAngle; + float mSwingYHalfMinAngle; + float mSwingYHalfMaxAngle; + float mSwingZHalfMinAngle; + float mSwingZHalfMaxAngle; + float mSinSwingYHalfMinAngle; + float mSinSwingYHalfMaxAngle; + float mSinSwingZHalfMinAngle; + float mSinSwingZHalfMaxAngle; + float mCosSwingYHalfMinAngle; + float mCosSwingYHalfMaxAngle; + float mCosSwingZHalfMinAngle; + float mCosSwingZHalfMaxAngle; // RUN TIME PROPERTIES FOLLOW diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.cpp index d5db4b0d..f3f96888 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -1038,28 +1039,9 @@ bool ContactConstraintManager::TemplatedAddContactConstraint(ContactAllocator &i // If one of the bodies is a sensor, don't actually create the constraint JPH_ASSERT(settings.mIsSensor || !(inBody1.IsSensor() || inBody2.IsSensor()), "Sensors cannot be converted into regular bodies by a contact callback!"); - if (settings.mIsSensor) - { - // Store the contact manifold in the cache - for (int i = 0; i < num_contact_points; ++i) - { - // Convert to local space to the body - Vec3 p1 = Vec3(inverse_transform_body1 * (inManifold.mBaseOffset + inManifold.mRelativeContactPointsOn1[i])); - Vec3 p2 = Vec3(inverse_transform_body2 * (inManifold.mBaseOffset + inManifold.mRelativeContactPointsOn2[i])); - - // Create new contact point - CachedContactPoint &cp = new_manifold->mContactPoints[i]; - p1.StoreFloat3(&cp.mPosition1); - p2.StoreFloat3(&cp.mPosition2); - - // We don't use this, but reset them anyway for determinism check - cp.mNonPenetrationLambda = 0.0f; - cp.mFrictionLambda[0] = 0.0f; - cp.mFrictionLambda[1] = 0.0f; - } - } - else if ((inBody1.IsDynamic() && settings.mInvMassScale1 != 0.0f) // One of the bodies must have mass to be able to create a contact constraint - || (inBody2.IsDynamic() && settings.mInvMassScale2 != 0.0f)) + if (!settings.mIsSensor + && ((inBody1.IsDynamic() && settings.mInvMassScale1 != 0.0f) // One of the bodies must have mass to be able to create a contact constraint + || (inBody2.IsDynamic() && settings.mInvMassScale2 != 0.0f))) { // Add contact constraint uint32 constraint_idx = mNumConstraints++; @@ -1177,6 +1159,26 @@ bool ContactConstraintManager::TemplatedAddContactConstraint(ContactAllocator &i constraint.Draw(DebugRenderer::sInstance, Color::sOrange); #endif // JPH_DEBUG_RENDERER } + else + { + // Store the contact manifold in the cache + for (int i = 0; i < num_contact_points; ++i) + { + // Convert to local space to the body + Vec3 p1 = Vec3(inverse_transform_body1 * (inManifold.mBaseOffset + inManifold.mRelativeContactPointsOn1[i])); + Vec3 p2 = Vec3(inverse_transform_body2 * (inManifold.mBaseOffset + inManifold.mRelativeContactPointsOn2[i])); + + // Create new contact point + CachedContactPoint &cp = new_manifold->mContactPoints[i]; + p1.StoreFloat3(&cp.mPosition1); + p2.StoreFloat3(&cp.mPosition2); + + // Reset contact impulses, we haven't applied any + cp.mNonPenetrationLambda = 0.0f; + cp.mFrictionLambda[0] = 0.0f; + cp.mFrictionLambda[1] = 0.0f; + } + } // Store cached contact point in body pair cache CachedBodyPair *cbp = reinterpret_cast(inBodyPairHandle); @@ -1447,9 +1449,8 @@ JPH_INLINE void ContactConstraintManager::sWarmStartConstraint(ContactConstraint for (WorldContactPoint &wcp : ioConstraint.mContactPoints) { // Warm starting: Apply impulse from last frame - if (wcp.mFrictionConstraint1.IsActive()) + if (wcp.mFrictionConstraint1.IsActive() || wcp.mFrictionConstraint2.IsActive()) { - JPH_ASSERT(wcp.mFrictionConstraint2.IsActive()); wcp.mFrictionConstraint1.TemplatedWarmStart(ioMotionProperties1, ioConstraint.mInvMass1, ioMotionProperties2, ioConstraint.mInvMass2, t1, inWarmStartImpulseRatio); wcp.mFrictionConstraint2.TemplatedWarmStart(ioMotionProperties1, ioConstraint.mInvMass1, ioMotionProperties2, ioConstraint.mInvMass2, t2, inWarmStartImpulseRatio); } @@ -1457,7 +1458,8 @@ JPH_INLINE void ContactConstraintManager::sWarmStartConstraint(ContactConstraint } } -void ContactConstraintManager::WarmStartVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio) +template +void ContactConstraintManager::WarmStartVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, MotionPropertiesCallback &ioCallback) { JPH_PROFILE_FUNCTION(); @@ -1479,18 +1481,31 @@ void ContactConstraintManager::WarmStartVelocityConstraints(const uint32 *inCons if (motion_type1 == EMotionType::Dynamic) { if (motion_type2 == EMotionType::Dynamic) + { sWarmStartConstraint(constraint, motion_properties1, motion_properties2, inWarmStartImpulseRatio); + + ioCallback(motion_properties2); + } else sWarmStartConstraint(constraint, motion_properties1, motion_properties2, inWarmStartImpulseRatio); + + ioCallback(motion_properties1); } else { JPH_ASSERT(motion_type2 == EMotionType::Dynamic); + sWarmStartConstraint(constraint, motion_properties1, motion_properties2, inWarmStartImpulseRatio); + + ioCallback(motion_properties2); } } } +// Specialize for the two body callback types +template void ContactConstraintManager::WarmStartVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, CalculateSolverSteps &ioCallback); +template void ContactConstraintManager::WarmStartVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, DummyCalculateSolverSteps &ioCallback); + template JPH_INLINE bool ContactConstraintManager::sSolveVelocityConstraint(ContactConstraint &ioConstraint, MotionProperties *ioMotionProperties1, MotionProperties *ioMotionProperties2) { @@ -1504,10 +1519,8 @@ JPH_INLINE bool ContactConstraintManager::sSolveVelocityConstraint(ContactConstr for (WorldContactPoint &wcp : ioConstraint.mContactPoints) { // Check if friction is enabled - if (wcp.mFrictionConstraint1.IsActive()) + if (wcp.mFrictionConstraint1.IsActive() || wcp.mFrictionConstraint2.IsActive()) { - JPH_ASSERT(wcp.mFrictionConstraint2.IsActive()); - // Calculate impulse to stop motion in tangential direction float lambda1 = wcp.mFrictionConstraint1.TemplatedSolveVelocityConstraintGetTotalLambda(ioMotionProperties1, ioMotionProperties2, t1); float lambda2 = wcp.mFrictionConstraint2.TemplatedSolveVelocityConstraintGetTotalLambda(ioMotionProperties1, ioMotionProperties2, t2); diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.h b/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.h index 7af22d1e..cb9d7d80 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/ContactConstraintManager.h @@ -169,7 +169,8 @@ class JPH_EXPORT ContactConstraintManager : public NonCopyable } /// Apply last frame's impulses as an initial guess for this frame's impulses - void WarmStartVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio); + template + void WarmStartVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio, MotionPropertiesCallback &ioCallback); /// Solve velocity constraints, when almost nothing changes this should only apply very small impulses /// since we're warm starting with the total impulse applied in the last frame above. diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.cpp index 88a0f0f4..e70bfb24 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.cpp @@ -84,7 +84,17 @@ DistanceConstraint::DistanceConstraint(Body &inBody1, Body &inBody2, const Dista // Store distance we want to keep between the world space points float distance = Vec3(mWorldSpacePosition2 - mWorldSpacePosition1).Length(); - SetDistance(mMinDistance < 0.0f? distance : mMinDistance, mMaxDistance < 0.0f? distance : mMaxDistance); + float min_distance, max_distance; + if (mMinDistance < 0.0f && mMaxDistance < 0.0f) + { + min_distance = max_distance = distance; + } + else + { + min_distance = mMinDistance < 0.0f? min(distance, mMaxDistance) : mMinDistance; + max_distance = mMaxDistance < 0.0f? max(distance, mMinDistance) : mMaxDistance; + } + SetDistance(min_distance, max_distance); // Most likely gravity is going to tear us apart (this is only used when the distance between the points = 0) mWorldSpaceNormal = Vec3::sAxisY(); @@ -151,6 +161,11 @@ void DistanceConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateConstraintProperties(inDeltaTime); } +void DistanceConstraint::ResetWarmStart() +{ + mAxisConstraint.Deactivate(); +} + void DistanceConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { mAxisConstraint.WarmStart(*mBody1, *mBody2, mWorldSpaceNormal, inWarmStartImpulseRatio); diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.h index af204a47..fc9d3526 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/DistanceConstraint.h @@ -18,7 +18,7 @@ class JPH_EXPORT DistanceConstraintSettings final : public TwoBodyConstraintSett // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, all properties below should be in the specified space @@ -57,6 +57,7 @@ class JPH_EXPORT DistanceConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Distance; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.cpp index b1c41711..b0639851 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.cpp @@ -130,6 +130,12 @@ void FixedConstraint::SetupVelocityConstraint(float inDeltaTime) mPointConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, mLocalSpacePosition1, *mBody2, rotation2, mLocalSpacePosition2); } +void FixedConstraint::ResetWarmStart() +{ + mRotationConstraintPart.Deactivate(); + mPointConstraintPart.Deactivate(); +} + void FixedConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.h index 13994940..16d54d3c 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/FixedConstraint.h @@ -19,7 +19,7 @@ class JPH_EXPORT FixedConstraintSettings final : public TwoBodyConstraintSetting // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, all properties below should be in the specified space @@ -44,7 +44,7 @@ class JPH_EXPORT FixedConstraintSettings final : public TwoBodyConstraintSetting }; /// A fixed constraint welds two bodies together removing all degrees of freedom between them. -/// This variant uses euler angles for the rotation constraint. +/// This variant uses Euler angles for the rotation constraint. class JPH_EXPORT FixedConstraint final : public TwoBodyConstraint { public: @@ -57,6 +57,7 @@ class JPH_EXPORT FixedConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Fixed; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.cpp index a606318f..676bee65 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.cpp @@ -82,6 +82,11 @@ void GearConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateConstraintProperties(rotation1, rotation2); } +void GearConstraint::ResetWarmStart() +{ + mGearConstraintPart.Deactivate(); +} + void GearConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.h index ac3ee34c..8b6233b1 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/GearConstraint.h @@ -18,7 +18,7 @@ class JPH_EXPORT GearConstraintSettings final : public TwoBodyConstraintSettings // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint. + /// Create an instance of this constraint. virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// Defines the ratio between the rotation of both gears @@ -61,6 +61,7 @@ class JPH_EXPORT GearConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Gear; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override { /* Do nothing */ } virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.cpp index 6bf64c90..82465736 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.cpp @@ -219,6 +219,14 @@ void HingeConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateMotorConstraintProperties(inDeltaTime); } +void HingeConstraint::ResetWarmStart() +{ + mMotorConstraintPart.Deactivate(); + mPointConstraintPart.Deactivate(); + mRotationConstraintPart.Deactivate(); + mRotationLimitsConstraintPart.Deactivate(); +} + void HingeConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse @@ -235,6 +243,13 @@ float HingeConstraint::GetSmallestAngleToLimit() const return abs(dist_to_min) < abs(dist_to_max)? dist_to_min : dist_to_max; } +bool HingeConstraint::IsMinLimitClosest() const +{ + float dist_to_min = CenterAngleAroundZero(mTheta - mLimitsMin); + float dist_to_max = CenterAngleAroundZero(mTheta - mLimitsMax); + return abs(dist_to_min) < abs(dist_to_max); +} + bool HingeConstraint::SolveVelocityConstraint(float inDeltaTime) { // Solve motor @@ -273,7 +288,7 @@ bool HingeConstraint::SolveVelocityConstraint(float inDeltaTime) min_lambda = -FLT_MAX; max_lambda = FLT_MAX; } - else if (GetSmallestAngleToLimit() < 0.0f) + else if (IsMinLimitClosest()) { min_lambda = 0.0f; max_lambda = FLT_MAX; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.h index 587f0cca..edf48d0e 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/HingeConstraint.h @@ -21,7 +21,7 @@ class JPH_EXPORT HingeConstraintSettings final : public TwoBodyConstraintSetting // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, all properties below should be in the specified space @@ -70,6 +70,7 @@ class JPH_EXPORT HingeConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Hinge; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; @@ -127,6 +128,7 @@ class JPH_EXPORT HingeConstraint final : public TwoBodyConstraint void CalculateRotationLimitsConstraintProperties(float inDeltaTime); void CalculateMotorConstraintProperties(float inDeltaTime); inline float GetSmallestAngleToLimit() const; + inline bool IsMinLimitClosest() const; // CONFIGURATION PROPERTIES FOLLOW diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.cpp index f463ad75..e5b6eb7d 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.cpp @@ -119,7 +119,7 @@ void PathConstraint::CalculateConstraintProperties(float inDeltaTime) // Calculate new closest point on path RVec3 position2 = path_to_world_2.GetTranslation(); Vec3 position2_local_to_path = Vec3(path_to_world_1.InversedRotationTranslation() * position2); - mPathFraction = mPath->GetClosestPoint(position2_local_to_path); + mPathFraction = mPath->GetClosestPoint(position2_local_to_path, mPathFraction); // Get the point on the path for this fraction Vec3 path_point, path_tangent, path_normal, path_binormal; @@ -168,7 +168,7 @@ void PathConstraint::CalculateConstraintProperties(float inDeltaTime) mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathBinormal, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisY()); break; - case EPathRotationConstraintType::ConstaintToPath: + case EPathRotationConstraintType::ConstrainToPath: // We need to calculate the inverse of the rotation from body 1 to body 2 for the current path position (see: RotationEulerConstraintPart::sGetInvInitialOrientation) // RotationBody2 = RotationBody1 * InitialOrientation <=> InitialOrientation^-1 = RotationBody2^-1 * RotationBody1 // We can express RotationBody2 in terms of RotationBody1: RotationBody2 = RotationBody1 * PathToBody1 * RotationClosestPointOnPath * PathToBody2^-1 @@ -225,6 +225,15 @@ void PathConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateConstraintProperties(inDeltaTime); } +void PathConstraint::ResetWarmStart() +{ + mPositionMotorConstraintPart.Deactivate(); + mPositionConstraintPart.Deactivate(); + mPositionLimitsConstraintPart.Deactivate(); + mHingeConstraintPart.Deactivate(); + mRotationConstraintPart.Deactivate(); +} + void PathConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse @@ -244,7 +253,7 @@ void PathConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) mHingeConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio); break; - case EPathRotationConstraintType::ConstaintToPath: + case EPathRotationConstraintType::ConstrainToPath: case EPathRotationConstraintType::FullyConstrained: mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio); break; @@ -306,7 +315,7 @@ bool PathConstraint::SolveVelocityConstraint(float inDeltaTime) rot = mHingeConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2); break; - case EPathRotationConstraintType::ConstaintToPath: + case EPathRotationConstraintType::ConstrainToPath: case EPathRotationConstraintType::FullyConstrained: rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2); break; @@ -350,7 +359,7 @@ bool PathConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgart rot = mHingeConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte); break; - case EPathRotationConstraintType::ConstaintToPath: + case EPathRotationConstraintType::ConstrainToPath: case EPathRotationConstraintType::FullyConstrained: rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mInvInitialOrientation, inBaumgarte); break; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.h index b094e4af..6301b852 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraint.h @@ -21,11 +21,18 @@ enum class EPathRotationConstraintType ConstrainAroundTangent, ///< Only allow rotation around the tangent vector (following the path) ConstrainAroundNormal, ///< Only allow rotation around the normal vector (perpendicular to the path) ConstrainAroundBinormal, ///< Only allow rotation around the binormal vector (perpendicular to the path) - ConstaintToPath, ///< Fully constrain the rotation of body 2 to the path (follwing the tangent and normal of the path) + ConstrainToPath, ///< Fully constrain the rotation of body 2 to the path (following the tangent and normal of the path) FullyConstrained, ///< Fully constrain the rotation of the body 2 to the rotation of body 1 }; /// Path constraint settings, used to constrain the degrees of freedom between two bodies to a path +/// +/// The requirements of the path are that: +/// * Tangent, normal and bi-normal form an orthonormal basis with: tangent cross bi-normal = normal +/// * The path points along the tangent vector +/// * The path is continuous so doesn't contain any sharp corners +/// +/// The reason for all this is that the constraint acts like a slider constraint with the sliding axis being the tangent vector (the assumption here is that delta time will be small enough so that the path is linear for that delta time). class JPH_EXPORT PathConstraintSettings final : public TwoBodyConstraintSettings { public: @@ -34,7 +41,7 @@ class JPH_EXPORT PathConstraintSettings final : public TwoBodyConstraintSettings // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// The path that constrains the two bodies @@ -76,6 +83,7 @@ class JPH_EXPORT PathConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Path; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPath.h b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPath.h index bd0853fc..06f10408 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPath.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPath.h @@ -32,8 +32,9 @@ class JPH_EXPORT PathConstraintPath : public SerializableObject, public RefTarge /// Get the globally closest point on the curve (Could be slow!) /// @param inPosition Position to find closest point for + /// @param inFractionHint Last known fraction along the path (can be used to speed up the search) /// @return Fraction of closest point along the path - virtual float GetClosestPoint(Vec3Arg inPosition) const = 0; + virtual float GetClosestPoint(Vec3Arg inPosition, float inFractionHint) const = 0; /// Given the fraction along the path, get the point, tangent and normal. /// @param inFraction Fraction along the path [0, GetPathMaxFraction()]. @@ -43,7 +44,7 @@ class JPH_EXPORT PathConstraintPath : public SerializableObject, public RefTarge /// @param outPathBinormal Returns the binormal to the path at outPathPosition (a vector so that normal cross tangent = binormal) virtual void GetPointOnPath(float inFraction, Vec3 &outPathPosition, Vec3 &outPathTangent, Vec3 &outPathNormal, Vec3 &outPathBinormal) const = 0; - /// If the path is looping or not. If a path is looping, the first and last point are automatically connected to eachother. They should not be the same points. + /// If the path is looping or not. If a path is looping, the first and last point are automatically connected to each other. They should not be the same points. void SetIsLooping(bool inIsLooping) { mIsLooping = inIsLooping; } bool IsLooping() const { return mIsLooping; } @@ -63,7 +64,7 @@ class JPH_EXPORT PathConstraintPath : public SerializableObject, public RefTarge virtual void RestoreBinaryState(StreamIn &inStream); private: - /// If the path is looping or not. If a path is looping, the first and last point are automatically connected to eachother. They should not be the same points. + /// If the path is looping or not. If a path is looping, the first and last point are automatically connected to each other. They should not be the same points. bool mIsLooping = false; }; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp index e0cd2551..55c1f33a 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp @@ -203,7 +203,7 @@ void PathConstraintPathHermite::GetIndexAndT(float inFraction, int &outIndex, fl outT = t; } -float PathConstraintPathHermite::GetClosestPoint(Vec3Arg inPosition) const +float PathConstraintPathHermite::GetClosestPoint(Vec3Arg inPosition, float inFractionHint) const { JPH_PROFILE_FUNCTION(); diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.h b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.h index 842054ef..aaf2ed8f 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PathConstraintPathHermite.h @@ -18,7 +18,7 @@ class JPH_EXPORT PathConstraintPathHermite final : public PathConstraintPath virtual float GetPathMaxFraction() const override { return float(IsLooping()? mPoints.size() : mPoints.size() - 1); } // See PathConstraintPath::GetClosestPoint - virtual float GetClosestPoint(Vec3Arg inPosition) const override; + virtual float GetClosestPoint(Vec3Arg inPosition, float inFractionHint) const override; // See PathConstraintPath::GetPointOnPath virtual void GetPointOnPath(float inFraction, Vec3 &outPathPosition, Vec3 &outPathTangent, Vec3 &outPathNormal, Vec3 &outPathBinormal) const override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.cpp index ecdef663..74d0ecd7 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.cpp @@ -97,6 +97,11 @@ void PointConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateConstraintProperties(); } +void PointConstraint::ResetWarmStart() +{ + mPointConstraintPart.Deactivate(); +} + void PointConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.h index 5ae7a972..f8875693 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PointConstraint.h @@ -18,7 +18,7 @@ class JPH_EXPORT PointConstraintSettings final : public TwoBodyConstraintSetting // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, all properties below should be in the specified space @@ -49,6 +49,7 @@ class JPH_EXPORT PointConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Point; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.cpp index 4342a156..9d15c1d4 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.cpp @@ -156,6 +156,11 @@ void PulleyConstraint::SetupVelocityConstraint(float inDeltaTime) mIndependentAxisConstraintPart.Deactivate(); } +void PulleyConstraint::ResetWarmStart() +{ + mIndependentAxisConstraintPart.Deactivate(); +} + void PulleyConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { mIndependentAxisConstraintPart.WarmStart(*mBody1, *mBody2, mWorldSpaceNormal1, mWorldSpaceNormal2, mRatio, inWarmStartImpulseRatio); diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.h index dba03457..5f2523da 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/PulleyConstraint.h @@ -23,7 +23,7 @@ class JPH_EXPORT PulleyConstraintSettings final : public TwoBodyConstraintSettin // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, specified properties below should be in the specified space @@ -68,6 +68,7 @@ class JPH_EXPORT PulleyConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Pulley; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.cpp index 8cc5cf03..8c54d48f 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.cpp @@ -83,6 +83,11 @@ void RackAndPinionConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateConstraintProperties(rotation1, rotation2); } +void RackAndPinionConstraint::ResetWarmStart() +{ + mRackAndPinionConstraintPart.Deactivate(); +} + void RackAndPinionConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.h index b7e6a454..1a0e43ef 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/RackAndPinionConstraint.h @@ -18,7 +18,7 @@ class JPH_EXPORT RackAndPinionConstraintSettings final : public TwoBodyConstrain // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint. + /// Create an instance of this constraint. /// Body1 should be the pinion (gear) and body 2 the rack (slider). virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; @@ -63,6 +63,7 @@ class JPH_EXPORT RackAndPinionConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::RackAndPinion; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override { /* Nothing */ } virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.cpp index bfdcd76b..070a45e2 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.cpp @@ -28,6 +28,7 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(SixDOFConstraintSettings) JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisX2) JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisY2) JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mMaxFriction) + JPH_ADD_ENUM_ATTRIBUTE(SixDOFConstraintSettings, mSwingType) JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitMin) JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitMax) JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitsSpringSettings) @@ -46,6 +47,7 @@ void SixDOFConstraintSettings::SaveBinaryState(StreamOut &inStream) const inStream.Write(mAxisX2); inStream.Write(mAxisY2); inStream.Write(mMaxFriction); + inStream.Write(mSwingType); inStream.Write(mLimitMin); inStream.Write(mLimitMax); for (const SpringSettings &s : mLimitsSpringSettings) @@ -66,6 +68,7 @@ void SixDOFConstraintSettings::RestoreBinaryState(StreamIn &inStream) inStream.Read(mAxisX2); inStream.Read(mAxisY2); inStream.Read(mMaxFriction); + inStream.Read(mSwingType); inStream.Read(mLimitMin); inStream.Read(mLimitMax); for (SpringSettings &s : mLimitsSpringSettings) @@ -79,32 +82,80 @@ TwoBodyConstraint *SixDOFConstraintSettings::Create(Body &inBody1, Body &inBody2 return new SixDOFConstraint(inBody1, inBody2, *this); } -void SixDOFConstraint::UpdateRotationLimits() +void SixDOFConstraint::UpdateTranslationLimits() { - // Make values sensible - for (int i = 3; i < 6; ++i) - if (IsFixedAxis((EAxis)i)) + // Set to zero if the limits are inversed + for (int i = EAxis::TranslationX; i <= EAxis::TranslationZ; ++i) + if (mLimitMin[i] > mLimitMax[i]) mLimitMin[i] = mLimitMax[i] = 0.0f; - else - { - mLimitMin[i] = max(-JPH_PI, mLimitMin[i]); - mLimitMax[i] = min(JPH_PI, mLimitMax[i]); - } +} - // The swing twist constraint part requires symmetrical rotations around Y and Z - JPH_ASSERT(mLimitMin[EAxis::RotationY] == -mLimitMax[EAxis::RotationY]); - JPH_ASSERT(mLimitMin[EAxis::RotationZ] == -mLimitMax[EAxis::RotationZ]); +void SixDOFConstraint::UpdateRotationLimits() +{ + if (mSwingTwistConstraintPart.GetSwingType() == ESwingType::Cone) + { + // Cone swing upper limit needs to be positive + mLimitMax[EAxis::RotationY] = max(0.0f, mLimitMax[EAxis::RotationY]); + mLimitMax[EAxis::RotationZ] = max(0.0f, mLimitMax[EAxis::RotationZ]); + + // Cone swing limits only support symmetric ranges + mLimitMin[EAxis::RotationY] = -mLimitMax[EAxis::RotationY]; + mLimitMin[EAxis::RotationZ] = -mLimitMax[EAxis::RotationZ]; + } + + for (int i = EAxis::RotationX; i <= EAxis::RotationZ; ++i) + { + // Clamp to [-PI, PI] range + mLimitMin[i] = Clamp(mLimitMin[i], -JPH_PI, JPH_PI); + mLimitMax[i] = Clamp(mLimitMax[i], -JPH_PI, JPH_PI); + + // Set to zero if the limits are inversed + if (mLimitMin[i] > mLimitMax[i]) + mLimitMin[i] = mLimitMax[i] = 0.0f; + } // Pass limits on to constraint part - mSwingTwistConstraintPart.SetLimits(mLimitMin[EAxis::RotationX], mLimitMax[EAxis::RotationX], mLimitMax[EAxis::RotationY], mLimitMax[EAxis::RotationZ]); + mSwingTwistConstraintPart.SetLimits(mLimitMin[EAxis::RotationX], mLimitMax[EAxis::RotationX], mLimitMin[EAxis::RotationY], mLimitMax[EAxis::RotationY], mLimitMin[EAxis::RotationZ], mLimitMax[EAxis::RotationZ]); +} + +void SixDOFConstraint::UpdateFixedFreeAxis() +{ + uint8 old_free_axis = mFreeAxis; + uint8 old_fixed_axis = mFixedAxis; + + // Cache which axis are fixed and which ones are free + mFreeAxis = 0; + mFixedAxis = 0; + for (int a = 0; a < EAxis::Num; ++a) + { + float limit = a >= EAxis::RotationX? JPH_PI : FLT_MAX; + + if (mLimitMin[a] >= mLimitMax[a]) + mFixedAxis |= 1 << a; + else if (mLimitMin[a] <= -limit && mLimitMax[a] >= limit) + mFreeAxis |= 1 << a; + } + + // On change we deactivate all constraints to reset warm starting + if (old_free_axis != mFreeAxis || old_fixed_axis != mFixedAxis) + { + for (AxisConstraintPart &c : mTranslationConstraintPart) + c.Deactivate(); + mPointConstraintPart.Deactivate(); + mSwingTwistConstraintPart.Deactivate(); + mRotationConstraintPart.Deactivate(); + for (AxisConstraintPart &c : mMotorTranslationConstraintPart) + c.Deactivate(); + for (AngleConstraintPart &c : mMotorRotationConstraintPart) + c.Deactivate(); + } } SixDOFConstraint::SixDOFConstraint(Body &inBody1, Body &inBody2, const SixDOFConstraintSettings &inSettings) : TwoBodyConstraint(inBody1, inBody2, inSettings) { - // Assert that input adheres to the limitations of this class - JPH_ASSERT(inSettings.mLimitMin[EAxis::RotationY] == -inSettings.mLimitMax[EAxis::RotationY]); - JPH_ASSERT(inSettings.mLimitMin[EAxis::RotationZ] == -inSettings.mLimitMax[EAxis::RotationZ]); + // Override swing type + mSwingTwistConstraintPart.SetSwingType(inSettings.mSwingType); // Calculate rotation needed to go from constraint space to body1 local space Vec3 axis_z1 = inSettings.mAxisX1.Cross(inSettings.mAxisY1); @@ -131,23 +182,13 @@ SixDOFConstraint::SixDOFConstraint(Body &inBody1, Body &inBody2, const SixDOFCon mLocalSpacePosition2 = Vec3(inSettings.mPosition2); } - // Cache which axis are fixed and which ones are free - mFreeAxis = 0; - mFixedAxis = 0; - for (int a = 0; a < EAxis::Num; ++a) - { - if (inSettings.IsFixedAxis((EAxis)a)) - mFixedAxis |= 1 << a; - - if (inSettings.IsFreeAxis((EAxis)a)) - mFreeAxis |= 1 << a; - } - // Copy translation and rotation limits memcpy(mLimitMin, inSettings.mLimitMin, sizeof(mLimitMin)); memcpy(mLimitMax, inSettings.mLimitMax, sizeof(mLimitMax)); memcpy(mLimitsSpringSettings, inSettings.mLimitsSpringSettings, sizeof(mLimitsSpringSettings)); + UpdateTranslationLimits(); UpdateRotationLimits(); + UpdateFixedFreeAxis(); CacheHasSpringLimits(); // Store friction settings @@ -178,6 +219,9 @@ void SixDOFConstraint::SetTranslationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitM mLimitMax[EAxis::TranslationX] = inLimitMax.GetX(); mLimitMax[EAxis::TranslationY] = inLimitMax.GetY(); mLimitMax[EAxis::TranslationZ] = inLimitMax.GetZ(); + + UpdateTranslationLimits(); + UpdateFixedFreeAxis(); } void SixDOFConstraint::SetRotationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax) @@ -190,6 +234,7 @@ void SixDOFConstraint::SetRotationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax) mLimitMax[EAxis::RotationZ] = inLimitMax.GetZ(); UpdateRotationLimits(); + UpdateFixedFreeAxis(); } void SixDOFConstraint::SetMaxFriction(EAxis inAxis, float inFriction) @@ -294,10 +339,10 @@ void SixDOFConstraint::SetTargetOrientationCS(QuatArg inOrientation) Quat q_swing, q_twist; inOrientation.GetSwingTwist(q_swing, q_twist); - bool twist_clamped, swing_y_clamped, swing_z_clamped; - mSwingTwistConstraintPart.ClampSwingTwist(q_swing, swing_y_clamped, swing_z_clamped, q_twist, twist_clamped); + uint clamped_axis; + mSwingTwistConstraintPart.ClampSwingTwist(q_swing, q_twist, clamped_axis); - if (twist_clamped || swing_y_clamped || swing_z_clamped) + if (clamped_axis != 0) mTargetOrientation = q_swing * q_twist; else mTargetOrientation = inOrientation; @@ -345,7 +390,7 @@ void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime) if (IsFixedAxis(axis)) { // When constraint is fixed it is always active - constraint_value = d; + constraint_value = d - mLimitMin[i]; constraint_active = true; } else if (!IsFreeAxis(axis)) @@ -398,7 +443,7 @@ void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime) // Setup rotation constraints if (IsRotationFullyConstrained()) { - // All rotation locked: Setup rotation contraint + // All rotation locked: Setup rotation constraint mRotationConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), *mBody2, Mat44::sRotation(mBody2->GetRotation())); } else if (IsRotationConstrained() || mRotationMotorActive) @@ -528,6 +573,19 @@ void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime) } } +void SixDOFConstraint::ResetWarmStart() +{ + for (AxisConstraintPart &c : mMotorTranslationConstraintPart) + c.Deactivate(); + for (AngleConstraintPart &c : mMotorRotationConstraintPart) + c.Deactivate(); + mRotationConstraintPart.Deactivate(); + mSwingTwistConstraintPart.Deactivate(); + mPointConstraintPart.Deactivate(); + for (AxisConstraintPart &c : mTranslationConstraintPart) + c.Deactivate(); +} + void SixDOFConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm start translation motors @@ -649,7 +707,8 @@ bool SixDOFConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumga // Definition of initial orientation r0: q2 = q1 r0 // Initial rotation (see: GetRotationInConstraintSpace): q2 = q1 c1 c2^-1 // So: r0^-1 = (c1 c2^-1)^-1 = c2 * c1^-1 - Quat inv_initial_orientation = mConstraintToBody2 * mConstraintToBody1.Conjugated(); + Quat constraint_to_body1 = mConstraintToBody1 * Quat::sEulerAngles(GetRotationLimitsMin()); + Quat inv_initial_orientation = mConstraintToBody2 * constraint_to_body1.Conjugated(); // Solve rotation violations mRotationConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), *mBody2, Mat44::sRotation(mBody2->GetRotation())); @@ -668,7 +727,8 @@ bool SixDOFConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumga if (IsTranslationFullyConstrained()) { // Translation locked: Solve point constraint - mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), mLocalSpacePosition1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2); + Vec3 local_space_position1 = mLocalSpacePosition1 + mConstraintToBody1 * GetTranslationLimitsMin(); + mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), local_space_position1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2); impulse |= mPointConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte); } else if (IsTranslationConstrained()) @@ -697,7 +757,7 @@ bool SixDOFConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumga float error = 0.0f; EAxis axis(EAxis(EAxis::TranslationX + i)); if (IsFixedAxis(axis)) - error = u.Dot(translation_axis); + error = u.Dot(translation_axis) - mLimitMin[axis]; else if (!IsFreeAxis(axis)) { float displacement = u.Dot(translation_axis); @@ -763,7 +823,10 @@ void SixDOFConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const RMat44 constraint_body1_to_world = RMat44::sRotationTranslation(mBody1->GetRotation() * mConstraintToBody1, mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1); // Draw limits - inRenderer->DrawSwingLimits(constraint_body1_to_world, mLimitMax[EAxis::RotationY], mLimitMax[EAxis::RotationZ], mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off); + if (mSwingTwistConstraintPart.GetSwingType() == ESwingType::Pyramid) + inRenderer->DrawSwingPyramidLimits(constraint_body1_to_world, mLimitMin[EAxis::RotationY], mLimitMax[EAxis::RotationY], mLimitMin[EAxis::RotationZ], mLimitMax[EAxis::RotationZ], mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off); + else + inRenderer->DrawSwingConeLimits(constraint_body1_to_world, mLimitMax[EAxis::RotationY], mLimitMax[EAxis::RotationZ], mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off); inRenderer->DrawPie(constraint_body1_to_world.GetTranslation(), mDrawConstraintSize, constraint_body1_to_world.GetAxisX(), constraint_body1_to_world.GetAxisY(), mLimitMin[EAxis::RotationX], mLimitMax[EAxis::RotationX], Color::sPurple, DebugRenderer::ECastShadow::Off); } #endif // JPH_DEBUG_RENDERER @@ -825,6 +888,7 @@ Ref SixDOFConstraint::GetConstraintSettings() const settings->mPosition2 = RVec3(mLocalSpacePosition2); settings->mAxisX2 = mConstraintToBody2.RotateAxisX(); settings->mAxisY2 = mConstraintToBody2.RotateAxisY(); + settings->mSwingType = mSwingTwistConstraintPart.GetSwingType(); memcpy(settings->mLimitMin, mLimitMin, sizeof(mLimitMin)); memcpy(settings->mLimitMax, mLimitMax, sizeof(mLimitMax)); memcpy(settings->mMaxFriction, mMaxFriction, sizeof(mMaxFriction)); diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.h index e0f843c8..1bc03ece 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/SixDOFConstraint.h @@ -27,9 +27,9 @@ class JPH_EXPORT SixDOFConstraintSettings final : public TwoBodyConstraintSettin TranslationY, TranslationZ, - RotationX, ///< When limited: MinLimit needs to be [-PI, 0], MaxLimit needs to be [0, PI] - RotationY, ///< When limited: MaxLimit between [0, PI]. MinLimit = -MaxLimit. Forms a cone shaped limit with Z. - RotationZ, ///< When limited: MaxLimit between [0, PI]. MinLimit = -MaxLimit. Forms a cone shaped limit with Y. + RotationX, + RotationY, + RotationZ, Num, NumTranslation = TranslationZ + 1, @@ -38,7 +38,7 @@ class JPH_EXPORT SixDOFConstraintSettings final : public TwoBodyConstraintSettin // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, all properties below should be in the specified space @@ -59,6 +59,9 @@ class JPH_EXPORT SixDOFConstraintSettings final : public TwoBodyConstraintSettin /// For rotation: Max friction torque in Nm. 0 = no friction. float mMaxFriction[EAxis::Num] = { 0, 0, 0, 0, 0, 0 }; + /// The type of swing constraint that we want to use. + ESwingType mSwingType = ESwingType::Cone; + /// Limits. /// For translation: Min and max linear limits in m (0 is frame of body 1 and 2 coincide). /// For rotation: Min and max angular limits in rad (0 is frame of body 1 and 2 coincide). See comments at Axis enum for limit ranges. @@ -66,6 +69,12 @@ class JPH_EXPORT SixDOFConstraintSettings final : public TwoBodyConstraintSettin /// Remove degree of freedom by setting min = FLT_MAX and max = -FLT_MAX. The constraint will be driven to 0 for this axis. /// /// Free movement over an axis is allowed when min = -FLT_MAX and max = FLT_MAX. + /// + /// Rotation limit around X-Axis: When limited, should be \f$\in [-\pi, \pi]\f$. Can be asymmetric around zero. + /// + /// Rotation limit around Y-Z Axis: Forms a pyramid or cone shaped limit: + /// * For pyramid, should be \f$\in [-\pi, \pi]\f$ and does not need to be symmetrical around zero. + /// * For cone should be \f$\in [0, \pi]\f$ and needs to be symmetrical around zero (min limit is assumed to be -max limit). float mLimitMin[EAxis::Num] = { -FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX }; float mLimitMax[EAxis::Num] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; @@ -81,8 +90,8 @@ class JPH_EXPORT SixDOFConstraintSettings final : public TwoBodyConstraintSettin void MakeFixedAxis(EAxis inAxis) { mLimitMin[inAxis] = FLT_MAX; mLimitMax[inAxis] = -FLT_MAX; } bool IsFixedAxis(EAxis inAxis) const { return mLimitMin[inAxis] >= mLimitMax[inAxis]; } - /// Set a valid range for the constraint - void SetLimitedAxis(EAxis inAxis, float inMin, float inMax) { JPH_ASSERT(inMin < inMax); JPH_ASSERT(inMin <= 0.0f); JPH_ASSERT(inMax >= 0.0f); mLimitMin[inAxis] = inMin; mLimitMax[inAxis] = inMax; } + /// Set a valid range for the constraint (if inMax < inMin, the axis will become fixed) + void SetLimitedAxis(EAxis inAxis, float inMin, float inMax) { mLimitMin[inAxis] = inMin; mLimitMax[inAxis] = inMax; } /// Motor settings for each axis MotorSettings mMotorSettings[EAxis::Num]; @@ -108,6 +117,7 @@ class JPH_EXPORT SixDOFConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::SixDOF; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; @@ -123,16 +133,21 @@ class JPH_EXPORT SixDOFConstraint final : public TwoBodyConstraint virtual Mat44 GetConstraintToBody1Matrix() const override { return Mat44::sRotationTranslation(mConstraintToBody1, mLocalSpacePosition1); } virtual Mat44 GetConstraintToBody2Matrix() const override { return Mat44::sRotationTranslation(mConstraintToBody2, mLocalSpacePosition2); } - /// Update the translation limits for this constraint, note that this won't change if axis are free or not. + /// Update the translation limits for this constraint void SetTranslationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax); - /// Update the rotational limits for this constraint, note that this won't change if axis are free or not. + /// Update the rotational limits for this constraint void SetRotationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax); /// Get constraint Limits float GetLimitsMin(EAxis inAxis) const { return mLimitMin[inAxis]; } float GetLimitsMax(EAxis inAxis) const { return mLimitMax[inAxis]; } + Vec3 GetTranslationLimitsMin() const { return Vec3::sLoadFloat3Unsafe(*reinterpret_cast(&mLimitMin[EAxis::TranslationX])); } + Vec3 GetTranslationLimitsMax() const { return Vec3::sLoadFloat3Unsafe(*reinterpret_cast(&mLimitMax[EAxis::TranslationX])); } + Vec3 GetRotationLimitsMin() const { return Vec3::sLoadFloat3Unsafe(*reinterpret_cast(&mLimitMin[EAxis::RotationX])); } + Vec3 GetRotationLimitsMax() const { return Vec3::sLoadFloat3Unsafe(*reinterpret_cast(&mLimitMax[EAxis::RotationX])); } + /// Check which axis are fixed/free inline bool IsFixedAxis(EAxis inAxis) const { return (mFixedAxis & (1 << inAxis)) != 0; } inline bool IsFreeAxis(EAxis inAxis) const { return (mFreeAxis & (1 << inAxis)) != 0; } @@ -187,9 +202,15 @@ class JPH_EXPORT SixDOFConstraint final : public TwoBodyConstraint // Calculate properties needed for the position constraint inline void GetPositionConstraintProperties(Vec3 &outR1PlusU, Vec3 &outR2, Vec3 &outU) const; + // Sanitize the translation limits + inline void UpdateTranslationLimits(); + // Propagate the rotation limits to the constraint part inline void UpdateRotationLimits(); + // Update the cached state of which axis are free and which ones are fixed + inline void UpdateFixedFreeAxis(); + // Cache the state of mTranslationMotorActive void CacheTranslationMotorActive(); @@ -220,8 +241,8 @@ class JPH_EXPORT SixDOFConstraint final : public TwoBodyConstraint Quat mConstraintToBody2; // Limits - uint8 mFreeAxis; // Bitmask of free axis (bit 0 = TranslationX) - uint8 mFixedAxis; // Bitmask of fixed axis (bit 0 = TranslationX) + uint8 mFreeAxis = 0; // Bitmask of free axis (bit 0 = TranslationX) + uint8 mFixedAxis = 0; // Bitmask of fixed axis (bit 0 = TranslationX) bool mTranslationMotorActive = false; // If any of the translational frictions / motors are active bool mRotationMotorActive = false; // If any of the rotational frictions / motors are active uint8 mRotationPositionMotorActive = 0; // Bitmask of axis that have position motor active (bit 0 = RotationX) diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.cpp index aca0462c..75335c32 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.cpp @@ -267,6 +267,14 @@ void SliderConstraint::SetupVelocityConstraint(float inDeltaTime) CalculateMotorConstraintProperties(inDeltaTime); } +void SliderConstraint::ResetWarmStart() +{ + mMotorConstraintPart.Deactivate(); + mPositionConstraintPart.Deactivate(); + mRotationConstraintPart.Deactivate(); + mPositionLimitsConstraintPart.Deactivate(); +} + void SliderConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.h index 3496a247..2838517f 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/SliderConstraint.h @@ -21,7 +21,7 @@ class JPH_EXPORT SliderConstraintSettings final : public TwoBodyConstraintSettin // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint. + /// Create an instance of this constraint. /// Note that the rotation constraint will be solved from body 1. This means that if body 1 and body 2 have different masses / inertias (kinematic body = infinite mass / inertia), body 1 should be the heaviest body. virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; @@ -76,6 +76,7 @@ class JPH_EXPORT SliderConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Slider; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/SpringSettings.h b/Dependencies/Jolt/Jolt/Physics/Constraints/SpringSettings.h index 9b230a2a..b2f6b7e2 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/SpringSettings.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/SpringSettings.h @@ -53,13 +53,17 @@ class JPH_EXPORT SpringSettings /// Valid when mSpringMode = ESpringMode::StiffnessAndDamping. /// If mStiffness > 0 the constraint will be soft and mStiffness specifies the stiffness (k) in the spring equation F = -k * x - c * v for a linear or T = -k * theta - c * w for an angular spring. /// If mStiffness <= 0, mDamping is ignored and the constraint will have hard limits (as hard as the time step / the number of velocity / position solver steps allows). + /// + /// Note that stiffness values are large numbers. To calculate a ballpark value for the needed stiffness you can use: + /// force = stiffness * delta_spring_length = mass * gravity <=> stiffness = mass * gravity / delta_spring_length. + /// So if your object weighs 1500 kg and the spring compresses by 2 meters, you need a stiffness in the order of 1500 * 9.81 / 2 ~ 7500 N/m. float mStiffness; }; /// When mSpringMode = ESpringMode::FrequencyAndDamping mDamping is the damping ratio (0 = no damping, 1 = critical damping). /// When mSpringMode = ESpringMode::StiffnessAndDamping mDamping is the damping (c) in the spring equation F = -k * x - c * v for a linear or T = -k * theta - c * w for an angular spring. /// Note that if you set mDamping = 0, you will not get an infinite oscillation. Because we integrate physics using an explicit Euler scheme, there is always energy loss. - /// This is done to keep the simulation from exploding, because with a damping of 0 and even the slightest rounding error, the oscillation could become bigger and bigger until the simluation explodes. + /// This is done to keep the simulation from exploding, because with a damping of 0 and even the slightest rounding error, the oscillation could become bigger and bigger until the simulation explodes. float mDamping = 0.0f; }; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.cpp b/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.cpp index 065ec4bd..bcd74ff3 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.cpp @@ -26,6 +26,7 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(SwingTwistConstraintSettings) JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPosition2) JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mTwistAxis2) JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPlaneAxis2) + JPH_ADD_ENUM_ATTRIBUTE(SwingTwistConstraintSettings, mSwingType) JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mNormalHalfConeAngle) JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPlaneHalfConeAngle) JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mTwistMinAngle) @@ -46,6 +47,7 @@ void SwingTwistConstraintSettings::SaveBinaryState(StreamOut &inStream) const inStream.Write(mPosition2); inStream.Write(mTwistAxis2); inStream.Write(mPlaneAxis2); + inStream.Write(mSwingType); inStream.Write(mNormalHalfConeAngle); inStream.Write(mPlaneHalfConeAngle); inStream.Write(mTwistMinAngle); @@ -66,6 +68,7 @@ void SwingTwistConstraintSettings::RestoreBinaryState(StreamIn &inStream) inStream.Read(mPosition2); inStream.Read(mTwistAxis2); inStream.Read(mPlaneAxis2); + inStream.Read(mSwingType); inStream.Read(mNormalHalfConeAngle); inStream.Read(mPlaneHalfConeAngle); inStream.Read(mTwistMinAngle); @@ -83,7 +86,7 @@ TwoBodyConstraint *SwingTwistConstraintSettings::Create(Body &inBody1, Body &inB void SwingTwistConstraint::UpdateLimits() { // Pass limits on to swing twist constraint part - mSwingTwistConstraintPart.SetLimits(mTwistMinAngle, mTwistMaxAngle, mPlaneHalfConeAngle, mNormalHalfConeAngle); + mSwingTwistConstraintPart.SetLimits(mTwistMinAngle, mTwistMaxAngle, -mPlaneHalfConeAngle, mPlaneHalfConeAngle, -mNormalHalfConeAngle, mNormalHalfConeAngle); } SwingTwistConstraint::SwingTwistConstraint(Body &inBody1, Body &inBody2, const SwingTwistConstraintSettings &inSettings) : @@ -96,6 +99,9 @@ SwingTwistConstraint::SwingTwistConstraint(Body &inBody1, Body &inBody2, const S mSwingMotorSettings(inSettings.mSwingMotorSettings), mTwistMotorSettings(inSettings.mTwistMotorSettings) { + // Override swing type + mSwingTwistConstraintPart.SetSwingType(inSettings.mSwingType); + // Calculate rotation needed to go from constraint space to body1 local space Vec3 normal_axis1 = inSettings.mPlaneAxis1.Cross(inSettings.mTwistAxis1); Mat44 c_to_b1(Vec4(inSettings.mTwistAxis1, 0), Vec4(normal_axis1, 0), Vec4(inSettings.mPlaneAxis1, 0), Vec4(0, 0, 0, 1)); @@ -182,10 +188,10 @@ void SwingTwistConstraint::SetTargetOrientationCS(QuatArg inOrientation) Quat q_swing, q_twist; inOrientation.GetSwingTwist(q_swing, q_twist); - bool swing_y_clamped, swing_z_clamped, twist_clamped; - mSwingTwistConstraintPart.ClampSwingTwist(q_swing, swing_y_clamped, swing_z_clamped, q_twist, twist_clamped); + uint clamped_axis; + mSwingTwistConstraintPart.ClampSwingTwist(q_swing, q_twist, clamped_axis); - if (swing_y_clamped || swing_z_clamped || twist_clamped) + if (clamped_axis != 0) mTargetOrientation = q_swing * q_twist; else mTargetOrientation = inOrientation; @@ -323,6 +329,14 @@ void SwingTwistConstraint::SetupVelocityConstraint(float inDeltaTime) } } +void SwingTwistConstraint::ResetWarmStart() +{ + for (AngleConstraintPart &c : mMotorConstraintPart) + c.Deactivate(); + mSwingTwistConstraintPart.Deactivate(); + mPointConstraintPart.Deactivate(); +} + void SwingTwistConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { // Warm starting: Apply previous frame impulse @@ -447,7 +461,10 @@ void SwingTwistConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const RMat44 constraint_to_world = RMat44::sRotationTranslation(mBody1->GetRotation() * mConstraintToBody1, mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1); // Draw limits - inRenderer->DrawSwingLimits(constraint_to_world, mPlaneHalfConeAngle, mNormalHalfConeAngle, mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off); + if (mSwingTwistConstraintPart.GetSwingType() == ESwingType::Pyramid) + inRenderer->DrawSwingPyramidLimits(constraint_to_world, -mPlaneHalfConeAngle, mPlaneHalfConeAngle, -mNormalHalfConeAngle, mNormalHalfConeAngle, mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off); + else + inRenderer->DrawSwingConeLimits(constraint_to_world, mPlaneHalfConeAngle, mNormalHalfConeAngle, mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off); inRenderer->DrawPie(constraint_to_world.GetTranslation(), mDrawConstraintSize, constraint_to_world.GetAxisX(), constraint_to_world.GetAxisY(), mTwistMinAngle, mTwistMaxAngle, Color::sPurple, DebugRenderer::ECastShadow::Off); } #endif // JPH_DEBUG_RENDERER @@ -493,6 +510,7 @@ Ref SwingTwistConstraint::GetConstraintSettings() const settings->mPosition2 = RVec3(mLocalSpacePosition2); settings->mTwistAxis2 = mConstraintToBody2.RotateAxisX(); settings->mPlaneAxis2 = mConstraintToBody2.RotateAxisZ(); + settings->mSwingType = mSwingTwistConstraintPart.GetSwingType(); settings->mNormalHalfConeAngle = mNormalHalfConeAngle; settings->mPlaneHalfConeAngle = mPlaneHalfConeAngle; settings->mTwistMinAngle = mTwistMinAngle; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.h index 693d097a..d915c001 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/SwingTwistConstraint.h @@ -25,7 +25,7 @@ class JPH_EXPORT SwingTwistConstraintSettings final : public TwoBodyConstraintSe // See: ConstraintSettings::SaveBinaryState virtual void SaveBinaryState(StreamOut &inStream) const override; - /// Create an an instance of this constraint + /// Create an instance of this constraint virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const override; /// This determines in which space the constraint is setup, all properties below should be in the specified space @@ -41,13 +41,16 @@ class JPH_EXPORT SwingTwistConstraintSettings final : public TwoBodyConstraintSe Vec3 mTwistAxis2 = Vec3::sAxisX(); Vec3 mPlaneAxis2 = Vec3::sAxisY(); + /// The type of swing constraint that we want to use. + ESwingType mSwingType = ESwingType::Cone; + ///@name Swing rotation limits - float mNormalHalfConeAngle = 0.0f; ///< See image. Angle in radians. - float mPlaneHalfConeAngle = 0.0f; ///< See image. Angle in radians. + float mNormalHalfConeAngle = 0.0f; ///< See image at Detailed Description. Angle in radians. + float mPlaneHalfConeAngle = 0.0f; ///< See image at Detailed Description. Angle in radians. ///@name Twist rotation limits - float mTwistMinAngle = 0.0f; ///< See image. Angle in radians. Rotation will be limited between [mLimitsMin, mLimitsMax] where mLimitsMin \f$\in [-\pi, 0]\f$ and mLimitsMax \f$\in [0, \pi]\f$ - float mTwistMaxAngle = 0.0f; ///< See image. Angle in radians. + float mTwistMinAngle = 0.0f; ///< See image at Detailed Description. Angle in radians. Should be \f$\in [-\pi, \pi]\f$. + float mTwistMaxAngle = 0.0f; ///< See image at Detailed Description. Angle in radians. Should be \f$\in [-\pi, \pi]\f$. ///@name Friction float mMaxFrictionTorque = 0.0f; ///< Maximum amount of torque (N m) to apply as friction when the constraint is not powered by a motor @@ -76,6 +79,7 @@ class JPH_EXPORT SwingTwistConstraint final : public TwoBodyConstraint virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::SwingTwist; } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override; virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; diff --git a/Dependencies/Jolt/Jolt/Physics/Constraints/TwoBodyConstraint.h b/Dependencies/Jolt/Jolt/Physics/Constraints/TwoBodyConstraint.h index 9cdbfc0e..028c073a 100644 --- a/Dependencies/Jolt/Jolt/Physics/Constraints/TwoBodyConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Constraints/TwoBodyConstraint.h @@ -17,7 +17,7 @@ class JPH_EXPORT TwoBodyConstraintSettings : public ConstraintSettings public: JPH_DECLARE_SERIALIZABLE_ABSTRACT(JPH_EXPORT, TwoBodyConstraintSettings) - /// Create an an instance of this constraint + /// Create an instance of this constraint /// You can use Body::sFixedToWorld for inBody1 if you want to attach inBody2 to the world virtual TwoBodyConstraint * Create(Body &inBody1, Body &inBody2) const = 0; }; diff --git a/Dependencies/Jolt/Jolt/Physics/IslandBuilder.cpp b/Dependencies/Jolt/Jolt/Physics/IslandBuilder.cpp index dda2093a..ed1064df 100644 --- a/Dependencies/Jolt/Jolt/Physics/IslandBuilder.cpp +++ b/Dependencies/Jolt/Jolt/Physics/IslandBuilder.cpp @@ -382,6 +382,8 @@ void IslandBuilder::Finalize(const BodyID *inActiveBodies, uint32 inNumActiveBod BuildConstraintIslands(mConstraintLinks, mNumConstraints, mConstraintIslands, mConstraintIslandEnds, inTempAllocator); BuildConstraintIslands(mContactLinks, mNumContacts, mContactIslands, mContactIslandEnds, inTempAllocator); SortIslands(inTempAllocator); + + mNumPositionSteps = (uint8 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint8)); } void IslandBuilder::GetBodiesInIsland(uint32 inIslandIndex, BodyID *&outBodiesBegin, BodyID *&outBodiesEnd) const @@ -432,6 +434,8 @@ void IslandBuilder::ResetIslands(TempAllocator *inTempAllocator) { JPH_PROFILE_FUNCTION(); + inTempAllocator->Free(mNumPositionSteps, mNumIslands * sizeof(uint8)); + if (mIslandsSorted != nullptr) { inTempAllocator->Free(mIslandsSorted, mNumIslands * sizeof(uint32)); diff --git a/Dependencies/Jolt/Jolt/Physics/IslandBuilder.h b/Dependencies/Jolt/Jolt/Physics/IslandBuilder.h index 148484a9..4c2f097d 100644 --- a/Dependencies/Jolt/Jolt/Physics/IslandBuilder.h +++ b/Dependencies/Jolt/Jolt/Physics/IslandBuilder.h @@ -50,6 +50,10 @@ class IslandBuilder : public NonCopyable bool GetConstraintsInIsland(uint32 inIslandIndex, uint32 *&outConstraintsBegin, uint32 *&outConstraintsEnd) const; bool GetContactsInIsland(uint32 inIslandIndex, uint32 *&outContactsBegin, uint32 *&outContactsEnd) const; + /// The number of position iterations for each island + void SetNumPositionSteps(uint32 inIslandIndex, uint inNumPositionSteps) { JPH_ASSERT(inIslandIndex < mNumIslands); JPH_ASSERT(inNumPositionSteps < 256); mNumPositionSteps[inIslandIndex] = uint8(inNumPositionSteps); } + uint GetNumPositionSteps(uint32 inIslandIndex) const { JPH_ASSERT(inIslandIndex < mNumIslands); return mNumPositionSteps[inIslandIndex]; } + /// After you're done calling the three functions above, call this function to free associated data void ResetIslands(TempAllocator *inTempAllocator); @@ -95,6 +99,8 @@ class IslandBuilder : public NonCopyable uint32 * mIslandsSorted = nullptr; ///< A list of island indices in order of most constraints first + uint8 * mNumPositionSteps = nullptr; ///< Number of position steps for each island + // Counters uint32 mMaxActiveBodies; ///< Maximum size of the active bodies list (see BodyManager::mActiveBodies) uint32 mNumActiveBodies = 0; ///< Number of active bodies passed to diff --git a/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.cpp b/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.cpp index 134d9c8a..ebe6fbfe 100644 --- a/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.cpp +++ b/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -174,7 +175,7 @@ void LargeIslandSplitter::Splits::MarkBatchProcessed(uint inNumProcessed, bool & LargeIslandSplitter::~LargeIslandSplitter() { JPH_ASSERT(mSplitMasks == nullptr); - JPH_ASSERT(mContactAndConstaintsSplitIdx == nullptr); + JPH_ASSERT(mContactAndConstraintsSplitIdx == nullptr); JPH_ASSERT(mContactAndConstraintIndices == nullptr); JPH_ASSERT(mSplitIslands == nullptr); } @@ -216,7 +217,7 @@ void LargeIslandSplitter::Prepare(const IslandBuilder &inIslandBuilder, uint32 i // Allocate contact and constraint buffer uint contact_and_constraint_indices_size = mContactAndConstraintsSize * sizeof(uint32); - mContactAndConstaintsSplitIdx = (uint32 *)inTempAllocator->Allocate(contact_and_constraint_indices_size); + mContactAndConstraintsSplitIdx = (uint32 *)inTempAllocator->Allocate(contact_and_constraint_indices_size); mContactAndConstraintIndices = (uint32 *)inTempAllocator->Allocate(contact_and_constraint_indices_size); // Allocate island split buffer @@ -279,7 +280,7 @@ uint LargeIslandSplitter::AssignToNonParallelSplit(const Body *inBody) return cNonParallelSplitIdx; } -bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder &inIslandBuilder, const BodyManager &inBodyManager, const ContactConstraintManager &inContactManager, Constraint **inActiveConstraints, int inNumVelocitySteps, int inNumPositionSteps) +bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder &inIslandBuilder, const BodyManager &inBodyManager, const ContactConstraintManager &inContactManager, Constraint **inActiveConstraints, CalculateSolverSteps &ioStepsCalculator) { JPH_PROFILE_FUNCTION(); @@ -293,7 +294,7 @@ bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder inIslandBuilder.GetConstraintsInIsland(inIslandIndex, constraints_start, constraints_end); uint num_constraints_in_island = uint(constraints_end - constraints_start); - // Check if it exceeds the treshold + // Check if it exceeds the threshold uint island_size = num_contacts_in_island + num_constraints_in_island; if (island_size < cLargeIslandTreshold) return false; @@ -313,7 +314,7 @@ bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder // Get space to store split indices uint offset = mContactAndConstraintsNextFree.fetch_add(island_size, memory_order_relaxed); - uint32 *contact_split_idx = mContactAndConstaintsSplitIdx + offset; + uint32 *contact_split_idx = mContactAndConstraintsSplitIdx + offset; uint32 *constraint_split_idx = contact_split_idx + num_contacts_in_island; // Assign the contacts to a split @@ -325,6 +326,11 @@ bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder uint split = AssignSplit(body1, body2); num_contacts_in_split[split]++; *cur_contact_split_idx++ = split; + + if (body1->IsDynamic()) + ioStepsCalculator(body1->GetMotionPropertiesUnchecked()); + if (body2->IsDynamic()) + ioStepsCalculator(body2->GetMotionPropertiesUnchecked()); } // Assign the constraints to a split @@ -333,12 +339,14 @@ bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder { const Constraint *constraint = inActiveConstraints[*c]; uint split = constraint->BuildIslandSplits(*this); - inNumVelocitySteps = max(inNumVelocitySteps, constraint->GetNumVelocityStepsOverride()); - inNumPositionSteps = max(inNumPositionSteps, constraint->GetNumPositionStepsOverride()); num_constraints_in_split[split]++; *cur_constraint_split_idx++ = split; + + ioStepsCalculator(constraint); } + ioStepsCalculator.Finalize(); + // Start with 0 splits uint split_remap_table[cNumSplits]; uint new_split_idx = mNextSplitIsland.fetch_add(1, memory_order_relaxed); @@ -346,9 +354,9 @@ bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder Splits &splits = mSplitIslands[new_split_idx]; splits.mIslandIndex = inIslandIndex; splits.mNumSplits = 0; - splits.mNumIterations = inNumVelocitySteps + 1; // Iteration 0 is used for warm starting - splits.mNumVelocitySteps = inNumVelocitySteps; - splits.mNumPositionSteps = inNumPositionSteps; + splits.mNumIterations = ioStepsCalculator.GetNumVelocitySteps() + 1; // Iteration 0 is used for warm starting + splits.mNumVelocitySteps = ioStepsCalculator.GetNumVelocitySteps(); + splits.mNumPositionSteps = ioStepsCalculator.GetNumPositionSteps(); splits.mItemsProcessed.store(0, memory_order_release); // Allocate space to store the sorted constraint and contact indices per split @@ -551,8 +559,8 @@ void LargeIslandSplitter::Reset(TempAllocator *inTempAllocator) inTempAllocator->Free(mContactAndConstraintIndices, mContactAndConstraintsSize * sizeof(uint32)); mContactAndConstraintIndices = nullptr; - inTempAllocator->Free(mContactAndConstaintsSplitIdx, mContactAndConstraintsSize * sizeof(uint32)); - mContactAndConstaintsSplitIdx = nullptr; + inTempAllocator->Free(mContactAndConstraintsSplitIdx, mContactAndConstraintsSize * sizeof(uint32)); + mContactAndConstraintsSplitIdx = nullptr; mContactAndConstraintsSize = 0; mContactAndConstraintsNextFree.store(0, memory_order_relaxed); diff --git a/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.h b/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.h index a46704bc..8e61d093 100644 --- a/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.h +++ b/Dependencies/Jolt/Jolt/Physics/LargeIslandSplitter.h @@ -15,6 +15,7 @@ class TempAllocator; class Constraint; class BodyManager; class ContactConstraintManager; +class CalculateSolverSteps; /// Assigns bodies in large islands to multiple groups that can run in parallel /// @@ -142,7 +143,7 @@ class LargeIslandSplitter : public NonCopyable uint AssignToNonParallelSplit(const Body *inBody); /// Splits up an island, the created splits will be added to the list of batches and can be fetched with FetchNextBatch. Returns false if the island did not need splitting. - bool SplitIsland(uint32 inIslandIndex, const IslandBuilder &inIslandBuilder, const BodyManager &inBodyManager, const ContactConstraintManager &inContactManager, Constraint **inActiveConstraints, int inNumVelocitySteps, int inNumPositionSteps); + bool SplitIsland(uint32 inIslandIndex, const IslandBuilder &inIslandBuilder, const BodyManager &inBodyManager, const ContactConstraintManager &inContactManager, Constraint **inActiveConstraints, CalculateSolverSteps &ioStepsCalculator); /// Fetch the next batch to process, returns a handle in outSplitIslandIndex that must be provided to MarkBatchProcessed when complete EStatus FetchNextBatch(uint &outSplitIslandIndex, uint32 *&outConstraintsBegin, uint32 *&outConstraintsEnd, uint32 *&outContactsBegin, uint32 *&outContactsEnd, bool &outFirstIteration); @@ -171,7 +172,7 @@ class LargeIslandSplitter : public NonCopyable SplitMask * mSplitMasks = nullptr; ///< Bits that indicate for each body in the BodyManager::mActiveBodies list which split they already belong to - uint32 * mContactAndConstaintsSplitIdx = nullptr; ///< Buffer to store the split index per constraint or contact + uint32 * mContactAndConstraintsSplitIdx = nullptr; ///< Buffer to store the split index per constraint or contact uint32 * mContactAndConstraintIndices = nullptr; ///< Buffer to store the ordered constraint indices per split uint mContactAndConstraintsSize = 0; ///< Total size of mContactAndConstraintsSplitIdx and mContactAndConstraintIndices atomic mContactAndConstraintsNextFree { 0 }; ///< Next element that is free in both buffers diff --git a/Dependencies/Jolt/Jolt/Physics/PhysicsSettings.h b/Dependencies/Jolt/Jolt/Physics/PhysicsSettings.h index 1d18c20e..eb8ecbbe 100644 --- a/Dependencies/Jolt/Jolt/Physics/PhysicsSettings.h +++ b/Dependencies/Jolt/Jolt/Physics/PhysicsSettings.h @@ -46,7 +46,7 @@ struct PhysicsSettings /// step which may not be the actual closest points by the time the two objects hit (unit: meters) float mSpeculativeContactDistance = 0.02f; - /// How much bodies are allowed to sink into eachother (unit: meters) + /// How much bodies are allowed to sink into each other (unit: meters) float mPenetrationSlop = 0.02f; /// Fraction of its inner radius a body must move per step to enable casting for the LinearCast motion quality @@ -75,10 +75,10 @@ struct PhysicsSettings /// Number of solver velocity iterations to run /// Note that this needs to be >= 2 in order for friction to work (friction is applied using the non-penetration impulse from the previous iteration) - int mNumVelocitySteps = 10; + uint mNumVelocitySteps = 10; /// Number of solver position iterations to run - int mNumPositionSteps = 2; + uint mNumPositionSteps = 2; /// Minimal velocity needed before a collision can be elastic (unit: m) float mMinVelocityForRestitution = 1.0f; @@ -101,7 +101,7 @@ struct PhysicsSettings /// Whether or not to use the body pair cache, which removes the need for narrow phase collision detection when orientation between two bodies didn't change bool mUseBodyPairContactCache = true; - /// Whether or not to reduce manifolds with similar contact normals into one contact manifold + /// Whether or not to reduce manifolds with similar contact normals into one contact manifold (see description at Body::SetUseManifoldReduction) bool mUseManifoldReduction = true; /// If we split up large islands into smaller parallel batches of work (to improve performance) diff --git a/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.cpp b/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.cpp index 75680d86..be0281a3 100644 --- a/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.cpp +++ b/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.cpp @@ -19,10 +19,14 @@ #include #include #include +#include +#include #include #include #include +#include #include +#include #include #include #include @@ -190,6 +194,9 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep // Leave 1 thread for update broadphase prepare and 1 for apply gravity int num_determine_active_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cDetermineActiveConstraintsBatchSize - 1) / cDetermineActiveConstraintsBatchSize, max_concurrency - 2)); + // Number of setup velocity constraints jobs to run depends on number of constraints. + int num_setup_velocity_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cSetupVelocityConstraintsBatchSize - 1) / cSetupVelocityConstraintsBatchSize, max_concurrency)); + // Number of find collisions jobs to run depends on number of active bodies. // Note that when we have more than 1 thread, we always spawn at least 2 find collisions jobs so that the first job can wait for build islands from constraints // (which may activate additional bodies that need to be processed) while the second job can start processing collision work. @@ -291,12 +298,14 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep }, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners) // This job will setup velocity constraints for non-collision constraints - step.mSetupVelocityConstraints = inJobSystem->CreateJob("SetupVelocityConstraints", cColorSetupVelocityConstraints, [&context, &step]() - { - context.mPhysicsSystem->JobSetupVelocityConstraints(context.mStepDeltaTime, &step); + step.mSetupVelocityConstraints.resize(num_setup_velocity_constraints_jobs); + for (int i = 0; i < num_setup_velocity_constraints_jobs; ++i) + step.mSetupVelocityConstraints[i] = inJobSystem->CreateJob("SetupVelocityConstraints", cColorSetupVelocityConstraints, [&context, &step]() + { + context.mPhysicsSystem->JobSetupVelocityConstraints(context.mStepDeltaTime, &step); - JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints); - }, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs + JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints); + }, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs // This job will build islands from constraints step.mBuildIslandsFromConstraints = inJobSystem->CreateJob("BuildIslandsFromConstraints", cColorBuildIslandsFromConstraints, [&context, &step]() @@ -314,10 +323,10 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep { context.mPhysicsSystem->JobDetermineActiveConstraints(&step); - step.mSetupVelocityConstraints.RemoveDependency(); step.mBuildIslandsFromConstraints.RemoveDependency(); - // Kick find collisions last as they will use up all CPU cores leaving no space for the previous 2 jobs + // Kick these jobs last as they will use up all CPU cores leaving no space for the previous job, we prefer setup velocity constraints to finish first so we kick it first + JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints); JobHandle::sRemoveDependencies(step.mFindCollisions); }, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners) @@ -369,9 +378,8 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep { context.mPhysicsSystem->JobBodySetIslandIndex(); - if (step.mStartNextStep.IsValid()) - step.mStartNextStep.RemoveDependency(); - }, 1); // depends on: finalize islands + JobHandle::sRemoveDependencies(step.mSolvePositionConstraints); + }, 2); // depends on: finalize islands, finish building jobs // Job to start the next collision step if (!is_last_step) @@ -413,7 +421,7 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep // Kick the step listeners job first JobHandle::sRemoveDependencies(next_step->mStepListeners); } - }, 4); // depends on: update soft bodies, body set island index, contact removed callbacks, finish building the previous step + }, 3); // depends on: update soft bodies, contact removed callbacks, finish building the previous step } // This job will solve the velocity constraints @@ -424,10 +432,10 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep context.mPhysicsSystem->JobSolveVelocityConstraints(&context, &step); step.mPreIntegrateVelocity.RemoveDependency(); - }, 3); // depends on: finalize islands, setup velocity constraints, finish building jobs. + }, num_setup_velocity_constraints_jobs + 2); // depends on: finalize islands, setup velocity constraints, finish building jobs. - // Kick find collisions after setup velocity constraints because the former job will use up all CPU cores - step.mSetupVelocityConstraints.RemoveDependency(); + // We prefer setup velocity constraints to finish first so we kick it first + JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints); JobHandle::sRemoveDependencies(step.mFindCollisions); // Finalize islands is a dependency on find collisions so it can go last @@ -490,10 +498,11 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep // Kick the next step if (step.mSoftBodyPrepare.IsValid()) step.mSoftBodyPrepare.RemoveDependency(); - }, 2); // depends on: resolve ccd contacts, finish building jobs. + }, 3); // depends on: resolve ccd contacts, body set island index, finish building jobs. - // Unblock previous job. + // Unblock previous jobs. step.mResolveCCDContacts.RemoveDependency(); + step.mBodySetIslandIndex.RemoveDependency(); // The soft body prepare job will create other jobs if needed step.mSoftBodyPrepare = inJobSystem->CreateJob("SoftBodyPrepare", cColorSoftBodyPrepare, [&context, &step]() @@ -526,7 +535,8 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep handles.push_back(h); if (step.mUpdateBroadphaseFinalize.IsValid()) handles.push_back(step.mUpdateBroadphaseFinalize); - handles.push_back(step.mSetupVelocityConstraints); + for (const JobHandle &h : step.mSetupVelocityConstraints) + handles.push_back(h); handles.push_back(step.mBuildIslandsFromConstraints); handles.push_back(step.mFinalizeIslands); handles.push_back(step.mBodySetIslandIndex); @@ -599,7 +609,7 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep // Return any errors EPhysicsUpdateError errors = static_cast(context.mErrors.load(memory_order_acquire)); - JPH_ASSERT(errors == EPhysicsUpdateError::None, "An error occured during the physics update, see EPhysicsUpdateError for more information"); + JPH_ASSERT(errors == EPhysicsUpdateError::None, "An error occurred during the physics update, see EPhysicsUpdateError for more information"); return errors; } @@ -642,7 +652,7 @@ void PhysicsSystem::JobDetermineActiveConstraints(PhysicsUpdateContext::Step *io for (;;) { // Atomically fetch a batch of constraints - uint32 constraint_idx = ioStep->mConstraintReadIdx.fetch_add(cDetermineActiveConstraintsBatchSize); + uint32 constraint_idx = ioStep->mDetermineActiveConstraintReadIdx.fetch_add(cDetermineActiveConstraintsBatchSize); if (constraint_idx >= num_constraints) break; @@ -694,7 +704,15 @@ void PhysicsSystem::JobApplyGravity(const PhysicsUpdateContext *ioContext, Physi { Body &body = mBodyManager.GetBody(active_bodies[active_body_idx]); if (body.IsDynamic()) - body.GetMotionProperties()->ApplyForceTorqueAndDragInternal(body.GetRotation(), mGravity, delta_time); + { + MotionProperties *mp = body.GetMotionProperties(); + Quat rotation = body.GetRotation(); + + if (body.GetApplyGyroscopicForce()) + mp->ApplyGyroscopicForceInternal(rotation, delta_time); + + mp->ApplyForceTorqueAndDragInternal(rotation, mGravity, delta_time); + } active_body_idx++; } } @@ -707,7 +725,17 @@ void PhysicsSystem::JobSetupVelocityConstraints(float inDeltaTime, PhysicsUpdate BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read); #endif - ConstraintManager::sSetupVelocityConstraints(ioStep->mContext->mActiveConstraints, ioStep->mNumActiveConstraints, inDeltaTime); + uint32 num_constraints = ioStep->mNumActiveConstraints; + + for (;;) + { + // Atomically fetch a batch of constraints + uint32 constraint_idx = ioStep->mSetupVelocityConstraintsReadIdx.fetch_add(cSetupVelocityConstraintsBatchSize); + if (constraint_idx >= num_constraints) + break; + + ConstraintManager::sSetupVelocityConstraints(ioStep->mContext->mActiveConstraints + constraint_idx, min(cSetupVelocityConstraintsBatchSize, num_constraints - constraint_idx), inDeltaTime); + } } void PhysicsSystem::JobBuildIslandsFromConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) @@ -949,13 +977,12 @@ void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const return; } - // Ensure that body1 is dynamic, this ensures that we do the collision detection in the space of a moving body, which avoids accuracy problems when testing a very large static object against a small dynamic object - // Ensure that body1 id < body2 id for dynamic vs dynamic - // Keep body order unchanged when colliding with a sensor - if ((!body1->IsDynamic() || (body2->IsDynamic() && inBodyPair.mBodyB < inBodyPair.mBodyA)) - && !body2->IsSensor()) + // Ensure that body1 has the higher motion type (i.e. dynamic trumps kinematic), this ensures that we do the collision detection in the space of a moving body, + // which avoids accuracy problems when testing a very large static object against a small dynamic object + // Ensure that body1 id < body2 id when motion types are the same. + if (body1->GetMotionType() < body2->GetMotionType() + || (body1->GetMotionType() == body2->GetMotionType() && inBodyPair.mBodyB < inBodyPair.mBodyA)) swap(body1, body2); - JPH_ASSERT(body1->IsDynamic() || body2->IsSensor()); // Check if the contact points from the previous frame are reusable and if so copy them bool pair_handled = false, constraint_created = false; @@ -975,7 +1002,7 @@ void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const CollideShapeSettings settings; settings.mCollectFacesMode = ECollectFacesMode::CollectFaces; settings.mActiveEdgeMode = mPhysicsSettings.mCheckActiveEdges? EActiveEdgeMode::CollideOnlyWithActive : EActiveEdgeMode::CollideWithAll; - settings.mMaxSeparationDistance = mPhysicsSettings.mSpeculativeContactDistance; + settings.mMaxSeparationDistance = body1->IsSensor() || body2->IsSensor()? 0.0f : mPhysicsSettings.mSpeculativeContactDistance; settings.mActiveEdgeMovementDirection = body1->GetLinearVelocity() - body2->GetLinearVelocity(); // Get transforms relative to body1 @@ -1010,10 +1037,8 @@ void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const virtual void AddHit(const CollideShapeResult &inResult) override { - // One of the following should be true: - // - Body 1 is dynamic and body 2 may be dynamic, static or kinematic - // - Body 1 is not dynamic in which case body 2 should be a sensor - JPH_ASSERT(mBody1->IsDynamic() || mBody2->IsSensor()); + // The first body should be the one with the highest motion type + JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType()); JPH_ASSERT(!ShouldEarlyOut()); // Test if we want to accept this hit @@ -1100,7 +1125,8 @@ void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const // Perform collision detection between the two shapes SubShapeIDCreator part1, part2; - CollisionDispatch::sCollideShapeVsShape(body1->GetShape(), body2->GetShape(), Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), transform1, transform2, part1, part2, settings, collector); + auto f = body1->GetEnhancedInternalEdgeRemovalWithBody(*body2)? InternalEdgeRemovingCollector::sCollideShapeVsShape : CollisionDispatch::sCollideShapeVsShape; + f(body1->GetShape(), body2->GetShape(), Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), transform1, transform2, part1, part2, settings, collector, { }); // Add the contacts for (ContactManifold &manifold : collector.mManifolds) @@ -1135,10 +1161,8 @@ void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const virtual void AddHit(const CollideShapeResult &inResult) override { - // One of the following should be true: - // - Body 1 is dynamic and body 2 may be dynamic, static or kinematic - // - Body 1 is not dynamic in which case body 2 should be a sensor - JPH_ASSERT(mBody1->IsDynamic() || mBody2->IsSensor()); + // The first body should be the one with the highest motion type + JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType()); JPH_ASSERT(!ShouldEarlyOut()); // Test if we want to accept this hit @@ -1202,7 +1226,8 @@ void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const // Perform collision detection between the two shapes SubShapeIDCreator part1, part2; - CollisionDispatch::sCollideShapeVsShape(body1->GetShape(), body2->GetShape(), Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), transform1, transform2, part1, part2, settings, collector); + auto f = body1->GetEnhancedInternalEdgeRemovalWithBody(*body2)? InternalEdgeRemovingCollector::sCollideShapeVsShape : CollisionDispatch::sCollideShapeVsShape; + f(body1->GetShape(), body2->GetShape(), Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), transform1, transform2, part1, part2, settings, collector, { }); constraint_created = collector.mConstraintCreated; } @@ -1258,6 +1283,9 @@ void PhysicsSystem::JobBodySetIslandIndex() } } +JPH_SUPPRESS_WARNING_PUSH +JPH_CLANG_SUPPRESS_WARNING("-Wundefined-func-template") // ConstraintManager::sWarmStartVelocityConstraints / ContactConstraintManager::WarmStartVelocityConstraints is instantiated in the cpp file + void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) { #ifdef JPH_ENABLE_ASSERTS @@ -1287,8 +1315,9 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, if (first_iteration) { // Iteration 0 is used to warm start the batch (we added 1 to the number of iterations in LargeIslandSplitter::SplitIsland) - ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio); - mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio); + DummyCalculateSolverSteps dummy; + ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, dummy); + mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, dummy); } else { @@ -1363,17 +1392,21 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, } // Split up large islands - int num_velocity_steps = mPhysicsSettings.mNumVelocitySteps; + CalculateSolverSteps steps_calculator(mPhysicsSettings); if (mPhysicsSettings.mUseLargeIslandSplitter - && mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, num_velocity_steps, mPhysicsSettings.mNumPositionSteps)) + && mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, steps_calculator)) continue; // Loop again to try to fetch the newly split island // We didn't create a split, just run the solver now for this entire island. Begin by warm starting. - ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, num_velocity_steps); - mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio); + ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, steps_calculator); + mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, steps_calculator); + steps_calculator.Finalize(); + + // Store the number of position steps for later + mIslandBuilder.SetNumPositionSteps(island_idx, steps_calculator.GetNumPositionSteps()); // Solve velocity constraints - for (int velocity_step = 0; velocity_step < num_velocity_steps; ++velocity_step) + for (uint velocity_step = 0; velocity_step < steps_calculator.GetNumVelocitySteps(); ++velocity_step) { bool applied_impulse = ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time); applied_impulse |= mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end); @@ -1394,6 +1427,8 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, while (check_islands || check_split_islands); } +JPH_SUPPRESS_WARNING_POP + void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) { // Reserve enough space for all bodies that may need a cast @@ -1462,12 +1497,12 @@ void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, // For motion type discrete we need to do this anyway, for motion type linear cast we have multiple choices // 1. Rotate the body first and then sweep // 2. First sweep and then rotate the body at the end - // 3. Pick some inbetween rotation (e.g. half way), then sweep and finally rotate the remainder + // 3. Pick some in between rotation (e.g. half way), then sweep and finally rotate the remainder // (1) has some clear advantages as when a long thin body hits a surface away from the center of mass, this will result in a large angular velocity and a limited reduction in linear velocity. // When simulation the rotation first before doing the translation, the body will be able to rotate away from the contact point allowing the center of mass to approach the surface. When using // approach (2) in this case what will happen is that we will immediately detect the same collision again (the body has not rotated and the body was already colliding at the end of the previous // time step) resulting in a lot of stolen time and the body appearing to be frozen in an unnatural pose (like it is glued at an angle to the surface). (2) obviously has some negative side effects - // too as simulating the rotation first may cause it to tunnel through a small object that the linear cast might have otherwise dectected. In any case a linear cast is not good for detecting + // too as simulating the rotation first may cause it to tunnel through a small object that the linear cast might have otherwise detected. In any case a linear cast is not good for detecting // tunneling due to angular rotation, so we don't care about that too much (you'd need a full cast to take angular effects into account). body.AddRotationStep(body.GetAngularVelocity() * delta_time); @@ -1491,12 +1526,13 @@ void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, float inner_radius = body.GetShape()->GetInnerRadius(); JPH_ASSERT(inner_radius > 0.0f, "The shape has no inner radius, this makes the shape unsuitable for the linear cast motion quality as we cannot move it without risking tunneling."); - // Measure translation in this step and check if it above the treshold to perform a linear cast + // Measure translation in this step and check if it above the threshold to perform a linear cast float linear_cast_threshold_sq = Square(mPhysicsSettings.mLinearCastThreshold * inner_radius); if (delta_pos.LengthSq() > linear_cast_threshold_sq) { // This body needs a cast uint32 ccd_body_idx = ioStep->mNumCCDBodies++; + JPH_ASSERT(active_body_idx < ioStep->mNumActiveBodyToCCDBody); ioStep->mActiveBodyToCCDBody[active_body_idx] = ccd_body_idx; new (&ioStep->mCCDBodies[ccd_body_idx]) CCDBody(body_id, delta_pos, linear_cast_threshold_sq, min(mPhysicsSettings.mPenetrationSlop, mPhysicsSettings.mLinearCastMaxPenetration * inner_radius)); @@ -1547,12 +1583,12 @@ void PhysicsSystem::JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, Ph if (ioStep->mNumCCDBodies == 0) { - // No continous collision detection jobs -> kick the next job ourselves + // No continuous collision detection jobs -> kick the next job ourselves ioStep->mContactRemovedCallbacks.RemoveDependency(); } else { - // Run the continous collision detection jobs + // Run the continuous collision detection jobs int num_continuous_collision_jobs = min(int(ioStep->mNumCCDBodies + cNumCCDBodiesPerJob - 1) / cNumCCDBodiesPerJob, ioContext->GetMaxConcurrency()); ioStep->mResolveCCDContacts.AddDependency(num_continuous_collision_jobs); ioStep->mContactRemovedCallbacks.AddDependency(num_continuous_collision_jobs - 1); // Already had 1 dependency @@ -1584,6 +1620,10 @@ inline static Vec3 sCalculateBodyMotion(const Body &inBody, float inDeltaTime) // Helper function that finds the CCD body corresponding to a body (if it exists) inline static PhysicsUpdateContext::Step::CCDBody *sGetCCDBody(const Body &inBody, PhysicsUpdateContext::Step *inStep) { + // Only rigid bodies can have a CCD body + if (!inBody.IsRigidBody()) + return nullptr; + // If the body has no motion properties it cannot have a CCD body const MotionProperties *motion_properties = inBody.GetMotionPropertiesUnchecked(); if (motion_properties == nullptr) @@ -1716,6 +1756,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph // This is the earliest hit so far, store it mCCDBody.mContactNormal = normal; mCCDBody.mBodyID2 = inResult.mBodyID2; + mCCDBody.mSubShapeID2 = inResult.mSubShapeID2; mCCDBody.mFraction = fraction; mCCDBody.mFractionPlusSlop = fraction_plus_slop; mResult = inResult; @@ -1960,7 +2001,7 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi // Check if the other body found a hit that is further away if (ccd_body2->mFraction > ccd_body->mFraction) { - // Reset the colliding body of the other CCD body. The other body will shorten its distance travelled and will not do any collision response (we'll do that). + // Reset the colliding body of the other CCD body. The other body will shorten its distance traveled and will not do any collision response (we'll do that). // This means that at this point we have triggered a contact point add/persist for our further hit by accident for the other body. // We accept this as calling the contact point callbacks here would require persisting the manifolds up to this point and doing the callbacks single threaded. ccd_body2->mBodyID2 = BodyID(); @@ -1972,73 +2013,132 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi // We'll just move to the collision position anyway (as that's the last position we know is good), but we won't do any collision response. if (ccd_body2 == nullptr || ccd_body2->mFraction >= ccd_body->mFraction) { - // Calculate contact points relative to center of mass of both bodies - Vec3 r1_plus_u = Vec3(ccd_body->mContactPointOn2 - (body1.GetCenterOfMassPosition() + ccd_body->mFraction * ccd_body->mDeltaPosition)); - Vec3 r2 = Vec3(ccd_body->mContactPointOn2 - body2.GetCenterOfMassPosition()); + const ContactSettings &contact_settings = ccd_body->mContactSettings; - // Calculate velocity of collision points + // Calculate contact point velocity for body 1 + Vec3 r1_plus_u = Vec3(ccd_body->mContactPointOn2 - (body1.GetCenterOfMassPosition() + ccd_body->mFraction * ccd_body->mDeltaPosition)); Vec3 v1 = body1.GetPointVelocityCOM(r1_plus_u); - Vec3 v2 = body2.GetPointVelocityCOM(r2); - Vec3 relative_velocity = v2 - v1; - float normal_velocity = relative_velocity.Dot(ccd_body->mContactNormal); - - // Calculate velocity bias due to restitution - float normal_velocity_bias; - const ContactSettings &contact_settings = ccd_body->mContactSettings; - if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution) - normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity; - else - normal_velocity_bias = 0.0f; - // Get inverse masses + // Calculate inverse mass for body 1 float inv_m1 = contact_settings.mInvMassScale1 * body_mp->GetInverseMass(); - float inv_m2 = body2.GetMotionPropertiesUnchecked() != nullptr? contact_settings.mInvMassScale2 * body2.GetMotionPropertiesUnchecked()->GetInverseMassUnchecked() : 0.0f; - // Solve contact constraint - AxisConstraintPart contact_constraint; - contact_constraint.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, ccd_body->mContactNormal, normal_velocity_bias); - contact_constraint.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, ccd_body->mContactNormal, -FLT_MAX, FLT_MAX); - - // Apply friction - if (contact_settings.mCombinedFriction > 0.0f) + if (body2.IsRigidBody()) { - // Calculate friction direction by removing normal velocity from the relative velocity - Vec3 friction_direction = relative_velocity - normal_velocity * ccd_body->mContactNormal; - float friction_direction_len_sq = friction_direction.LengthSq(); - if (friction_direction_len_sq > 1.0e-12f) + // Calculate contact point velocity for body 2 + Vec3 r2 = Vec3(ccd_body->mContactPointOn2 - body2.GetCenterOfMassPosition()); + Vec3 v2 = body2.GetPointVelocityCOM(r2); + + // Calculate relative contact velocity + Vec3 relative_velocity = v2 - v1; + float normal_velocity = relative_velocity.Dot(ccd_body->mContactNormal); + + // Calculate velocity bias due to restitution + float normal_velocity_bias; + if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution) + normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity; + else + normal_velocity_bias = 0.0f; + + // Get inverse mass of body 2 + float inv_m2 = body2.GetMotionPropertiesUnchecked() != nullptr? contact_settings.mInvMassScale2 * body2.GetMotionPropertiesUnchecked()->GetInverseMassUnchecked() : 0.0f; + + // Solve contact constraint + AxisConstraintPart contact_constraint; + contact_constraint.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, ccd_body->mContactNormal, normal_velocity_bias); + contact_constraint.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, ccd_body->mContactNormal, -FLT_MAX, FLT_MAX); + + // Apply friction + if (contact_settings.mCombinedFriction > 0.0f) { - // Normalize friction direction - friction_direction /= sqrt(friction_direction_len_sq); + // Calculate friction direction by removing normal velocity from the relative velocity + Vec3 friction_direction = relative_velocity - normal_velocity * ccd_body->mContactNormal; + float friction_direction_len_sq = friction_direction.LengthSq(); + if (friction_direction_len_sq > 1.0e-12f) + { + // Normalize friction direction + friction_direction /= sqrt(friction_direction_len_sq); - // Calculate max friction impulse - float max_lambda_f = contact_settings.mCombinedFriction * contact_constraint.GetTotalLambda(); + // Calculate max friction impulse + float max_lambda_f = contact_settings.mCombinedFriction * contact_constraint.GetTotalLambda(); - AxisConstraintPart friction; - friction.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, friction_direction); - friction.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, friction_direction, -max_lambda_f, max_lambda_f); + AxisConstraintPart friction; + friction.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, friction_direction); + friction.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, friction_direction, -max_lambda_f, max_lambda_f); + } + } + + // Clamp velocity of body 2 + if (body2.IsDynamic()) + { + MotionProperties *body2_mp = body2.GetMotionProperties(); + body2_mp->ClampLinearVelocity(); + body2_mp->ClampAngularVelocity(); } } + else + { + SoftBodyMotionProperties *soft_mp = static_cast(body2.GetMotionProperties()); + const SoftBodyShape *soft_shape = static_cast(body2.GetShape()); + + // Convert the sub shape ID of the soft body to a face + uint32 face_idx = soft_shape->GetFaceIndex(ccd_body->mSubShapeID2); + const SoftBodyMotionProperties::Face &face = soft_mp->GetFace(face_idx); + + // Get vertices of the face + SoftBodyMotionProperties::Vertex &vtx0 = soft_mp->GetVertex(face.mVertex[0]); + SoftBodyMotionProperties::Vertex &vtx1 = soft_mp->GetVertex(face.mVertex[1]); + SoftBodyMotionProperties::Vertex &vtx2 = soft_mp->GetVertex(face.mVertex[2]); + + // Inverse mass of the face + float vtx0_mass = vtx0.mInvMass > 0.0f? 1.0f / vtx0.mInvMass : 1.0e10f; + float vtx1_mass = vtx1.mInvMass > 0.0f? 1.0f / vtx1.mInvMass : 1.0e10f; + float vtx2_mass = vtx2.mInvMass > 0.0f? 1.0f / vtx2.mInvMass : 1.0e10f; + float inv_m2 = 1.0f / (vtx0_mass + vtx1_mass + vtx2_mass); + + // Calculate barycentric coordinates of the contact point on the soft body's face + float u, v, w; + RMat44 inv_body2_transform = body2.GetInverseCenterOfMassTransform(); + Vec3 local_contact = Vec3(inv_body2_transform * ccd_body->mContactPointOn2); + ClosestPoint::GetBaryCentricCoordinates(vtx0.mPosition - local_contact, vtx1.mPosition - local_contact, vtx2.mPosition - local_contact, u, v, w); + + // Calculate contact point velocity for the face + Vec3 v2 = inv_body2_transform.Multiply3x3Transposed(u * vtx0.mVelocity + v * vtx1.mVelocity + w * vtx2.mVelocity); + float normal_velocity = (v2 - v1).Dot(ccd_body->mContactNormal); + + // Calculate velocity bias due to restitution + float normal_velocity_bias; + if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution) + normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity; + else + normal_velocity_bias = 0.0f; + + // Calculate resulting velocity change (the math here is similar to AxisConstraintPart but without an inertia term for body 2 as we treat it as a point mass) + Vec3 r1_plus_u_x_n = r1_plus_u.Cross(ccd_body->mContactNormal); + Vec3 invi1_r1_plus_u_x_n = contact_settings.mInvInertiaScale1 * body1.GetInverseInertia().Multiply3x3(r1_plus_u_x_n); + float jv = r1_plus_u_x_n.Dot(body_mp->GetAngularVelocity()) - normal_velocity - normal_velocity_bias; + float inv_effective_mass = inv_m1 + inv_m2 + invi1_r1_plus_u_x_n.Dot(r1_plus_u_x_n); + float lambda = jv / inv_effective_mass; + body_mp->SubLinearVelocityStep((lambda * inv_m1) * ccd_body->mContactNormal); + body_mp->SubAngularVelocityStep(lambda * invi1_r1_plus_u_x_n); + Vec3 delta_v2 = inv_body2_transform.Multiply3x3(lambda * ccd_body->mContactNormal); + vtx0.mVelocity += delta_v2 * vtx0.mInvMass; + vtx1.mVelocity += delta_v2 * vtx1.mInvMass; + vtx2.mVelocity += delta_v2 * vtx2.mInvMass; + } - // Clamp velocities + // Clamp velocity of body 1 body_mp->ClampLinearVelocity(); body_mp->ClampAngularVelocity(); - if (body2.IsDynamic()) + // Activate the 2nd body if it is not already active + if (body2.IsDynamic() && !body2.IsActive()) { - MotionProperties *body2_mp = body2.GetMotionProperties(); - body2_mp->ClampLinearVelocity(); - body2_mp->ClampAngularVelocity(); - - // Activate the body if it is not already active - if (!body2.IsActive()) + bodies_to_activate[num_bodies_to_activate++] = ccd_body->mBodyID2; + if (num_bodies_to_activate == cBodiesBatch) { - bodies_to_activate[num_bodies_to_activate++] = ccd_body->mBodyID2; - if (num_bodies_to_activate == cBodiesBatch) - { - // Batch is full, activate now - mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate); - num_bodies_to_activate = 0; - } + // Batch is full, activate now + mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate); + num_bodies_to_activate = 0; } } @@ -2301,29 +2401,9 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, // Check if this island needs solving if (num_items > 0) { - // First iteration - int num_position_steps = mPhysicsSettings.mNumPositionSteps; - if (num_position_steps > 0) - { - // In the first iteration also calculate the number of position steps (this way we avoid pulling all constraints into the cache twice) - bool applied_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte, num_position_steps); - applied_impulse |= mContactManager.SolvePositionConstraints(contacts_begin, contacts_end); - - // If no impulses were applied we can stop, otherwise we already did 1 iteration - if (!applied_impulse) - num_position_steps = 0; - else - --num_position_steps; - } - else - { - // Iterate the constraints to see if they override the number of position steps - for (const uint32 *c = constraints_begin; c < constraints_end; ++c) - num_position_steps = max(num_position_steps, active_constraints[*c]->GetNumPositionStepsOverride()); - } - - // Further iterations - for (int position_step = 0; position_step < num_position_steps; ++position_step) + // Iterate + uint num_position_steps = mIslandBuilder.GetNumPositionSteps(island_idx); + for (uint position_step = 0; position_step < num_position_steps; ++position_step) { bool applied_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte); applied_impulse |= mContactManager.SolvePositionConstraints(contacts_begin, contacts_end); @@ -2442,15 +2522,15 @@ void PhysicsSystem::JobSoftBodyCollide(PhysicsUpdateContext *ioContext) const // Do a broadphase check SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[sb_idx]; - sb_ctx.mMotionProperties->DetermineCollidingShapes(sb_ctx, *this); + sb_ctx.mMotionProperties->DetermineCollidingShapes(sb_ctx, *this, GetBodyLockInterfaceNoLock()); } } void PhysicsSystem::JobSoftBodySimulate(PhysicsUpdateContext *ioContext, uint inThreadIndex) const { #ifdef JPH_ENABLE_ASSERTS - // Updating velocities of soft bodies - BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::None); + // Updating velocities of soft bodies, allow the contact listener to read the soft body state + BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read); #endif // Calculate at which body we start to distribute the workload across the threads @@ -2505,7 +2585,7 @@ void PhysicsSystem::JobSoftBodyFinalize(PhysicsUpdateContext *ioContext) for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx) { // Apply the rigid body velocity deltas - sb_ctx->mMotionProperties->UpdateRigidBodyVelocities(*sb_ctx, *this); + sb_ctx->mMotionProperties->UpdateRigidBodyVelocities(*sb_ctx, GetBodyInterfaceNoLock()); // Update the position sb_ctx->mBody->SetPositionAndRotationInternal(sb_ctx->mBody->GetPosition() + sb_ctx->mDeltaPosition, sb_ctx->mBody->GetRotation(), false); diff --git a/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.h b/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.h index 66076276..3234e5b5 100644 --- a/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.h +++ b/Dependencies/Jolt/Jolt/Physics/PhysicsSystem.h @@ -20,6 +20,7 @@ class JobSystem; class StateRecorder; class TempAllocator; class PhysicsStepListener; +class SoftBodyContactListener; /// The main class for the physics system. It contains all rigid bodies and simulates them. /// @@ -51,6 +52,10 @@ class JPH_EXPORT PhysicsSystem : public NonCopyable void SetContactListener(ContactListener *inListener) { mContactManager.SetContactListener(inListener); } ContactListener * GetContactListener() const { return mContactManager.GetContactListener(); } + /// Listener that is notified whenever a contact point between a soft body and another body + void SetSoftBodyContactListener(SoftBodyContactListener *inListener) { mSoftBodyContactListener = inListener; } + SoftBodyContactListener * GetSoftBodyContactListener() const { return mSoftBodyContactListener; } + /// Set the function that combines the friction of two bodies and returns it /// Default method is the geometric mean: sqrt(friction1 * friction2). void SetCombineFriction(ContactConstraintManager::CombineFunction inCombineFriction) { mContactManager.SetCombineFriction(inCombineFriction); } @@ -188,6 +193,9 @@ class JPH_EXPORT PhysicsSystem : public NonCopyable /// - During the ContactListener::OnContactRemoved callback this function can be used to determine if this is the last contact pair between the bodies (function returns false) or if there are other contacts still present (function returns true). bool WereBodiesInContact(const BodyID &inBody1ID, const BodyID &inBody2ID) const { return mContactManager.WereBodiesInContact(inBody1ID, inBody2ID); } + /// Get the bounding box of all bodies in the physics system + AABox GetBounds() const { return mBroadPhase->GetBounds(); } + #ifdef JPH_TRACK_BROADPHASE_STATS /// Trace the accumulated broadphase stats to the TTY void ReportBroadphaseStats() { mBroadPhase->ReportStats(); } @@ -235,6 +243,9 @@ class JPH_EXPORT PhysicsSystem : public NonCopyable /// Number of constraints to process at once in JobDetermineActiveConstraints static constexpr int cDetermineActiveConstraintsBatchSize = 64; + /// Number of constraints to process at once in JobSetupVelocityConstraints, we want a low number of threads working on this so we take fairly large batches + static constexpr int cSetupVelocityConstraintsBatchSize = 256; + /// Number of bodies to process at once in JobApplyGravity static constexpr int cApplyGravityBatchSize = 64; @@ -274,6 +285,9 @@ class JPH_EXPORT PhysicsSystem : public NonCopyable /// The broadphase does quick collision detection between body pairs BroadPhase * mBroadPhase = nullptr; + /// The soft body contact listener + SoftBodyContactListener * mSoftBodyContactListener = nullptr; + /// Simulation settings PhysicsSettings mPhysicsSettings; diff --git a/Dependencies/Jolt/Jolt/Physics/PhysicsUpdateContext.h b/Dependencies/Jolt/Jolt/Physics/PhysicsUpdateContext.h index e563141f..fbcdb261 100644 --- a/Dependencies/Jolt/Jolt/Physics/PhysicsUpdateContext.h +++ b/Dependencies/Jolt/Jolt/Physics/PhysicsUpdateContext.h @@ -62,21 +62,24 @@ class PhysicsUpdateContext : public NonCopyable uint32 mNumActiveBodiesAtStepStart; ///< Number of bodies that were active at the start of the physics update step. Only these bodies will receive gravity (they are the first N in the active body list). - atomic mConstraintReadIdx { 0 }; ///< Next constraint for determine active constraints + atomic mDetermineActiveConstraintReadIdx { 0 }; ///< Next constraint for determine active constraints uint8 mPadding1[JPH_CACHE_LINE_SIZE - sizeof(atomic)];///< Padding to avoid sharing cache line with the next atomic atomic mNumActiveConstraints { 0 }; ///< Number of constraints in the mActiveConstraints array uint8 mPadding2[JPH_CACHE_LINE_SIZE - sizeof(atomic)];///< Padding to avoid sharing cache line with the next atomic - atomic mStepListenerReadIdx { 0 }; ///< Next step listener to call + atomic mSetupVelocityConstraintsReadIdx { 0 }; ///< Next constraint for setting up velocity constraints uint8 mPadding3[JPH_CACHE_LINE_SIZE - sizeof(atomic)];///< Padding to avoid sharing cache line with the next atomic - atomic mApplyGravityReadIdx { 0 }; ///< Next body to apply gravity to + atomic mStepListenerReadIdx { 0 }; ///< Next step listener to call uint8 mPadding4[JPH_CACHE_LINE_SIZE - sizeof(atomic)];///< Padding to avoid sharing cache line with the next atomic - atomic mActiveBodyReadIdx { 0 }; ///< Index of fist active body that has not yet been processed by the broadphase + atomic mApplyGravityReadIdx { 0 }; ///< Next body to apply gravity to uint8 mPadding5[JPH_CACHE_LINE_SIZE - sizeof(atomic)];///< Padding to avoid sharing cache line with the next atomic + atomic mActiveBodyReadIdx { 0 }; ///< Index of fist active body that has not yet been processed by the broadphase + uint8 mPadding6[JPH_CACHE_LINE_SIZE - sizeof(atomic)];///< Padding to avoid sharing cache line with the next atomic + BodyPairQueues mBodyPairQueues; ///< Queues in which to put body pairs that need to be tested by the narrowphase uint32 mMaxBodyPairsPerQueue; ///< Amount of body pairs that we can queue per queue @@ -99,6 +102,7 @@ class PhysicsUpdateContext : public NonCopyable RVec3 mContactPointOn2; ///< World space contact point on body 2 of closest hit (only valid if mFractionPlusSlop < 1) BodyID mBodyID1; ///< Body 1 (the body that is performing collision detection) BodyID mBodyID2; ///< Body 2 (the body of the closest hit, only valid if mFractionPlusSlop < 1) + SubShapeID mSubShapeID2; ///< Sub shape of body 2 that was hit (only valid if mFractionPlusSlop < 1) float mFraction = 1.0f; ///< Fraction at which the hit occurred float mFractionPlusSlop = 1.0f; ///< Fraction at which the hit occurred + extra delta to allow body to penetrate by mMaxPenetration float mLinearCastThresholdSq; ///< Maximum allowed squared movement before doing a linear cast (determined by inner radius of shape) @@ -120,7 +124,7 @@ class PhysicsUpdateContext : public NonCopyable JobHandleArray mApplyGravity; ///< Update velocities of bodies with gravity JobHandleArray mFindCollisions; ///< Find all collisions between active bodies an the world JobHandle mUpdateBroadphaseFinalize; ///< Swap the newly built tree with the current tree - JobHandle mSetupVelocityConstraints; ///< Calculate properties for all constraints in the constraint manager + JobHandleArray mSetupVelocityConstraints; ///< Calculate properties for all constraints in the constraint manager JobHandle mBuildIslandsFromConstraints; ///< Go over all constraints and assign the bodies they're attached to to an island JobHandle mFinalizeIslands; ///< Finalize calculation simulation islands JobHandle mBodySetIslandIndex; ///< Set the current island index on each body (not used by the simulation, only for drawing purposes) diff --git a/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.cpp b/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.cpp index 23b9d01b..f66212f2 100644 --- a/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.cpp @@ -552,7 +552,7 @@ void Ragdoll::SetPose(RVec3Arg inRootOffset, const Mat44 *inJointMatrices, bool for (int i = 0; i < (int)mBodyIDs.size(); ++i) { const Mat44 &joint = inJointMatrices[i]; - bi.SetPositionAndRotation(mBodyIDs[i], inRootOffset + joint.GetTranslation(), joint.GetRotation().GetQuaternion(), EActivation::DontActivate); + bi.SetPositionAndRotation(mBodyIDs[i], inRootOffset + joint.GetTranslation(), joint.GetQuaternion(), EActivation::DontActivate); } } @@ -588,6 +588,12 @@ void Ragdoll::GetPose(RVec3 &outRootOffset, Mat44 *outJointMatrices, bool inLock } } +void Ragdoll::ResetWarmStart() +{ + for (TwoBodyConstraint *c : mConstraints) + c->ResetWarmStart(); +} + void Ragdoll::DriveToPoseUsingKinematics(const SkeletonPose &inPose, float inDeltaTime, bool inLockBodies) { JPH_ASSERT(inPose.GetSkeleton() == mRagdollSettings->mSkeleton); @@ -602,7 +608,7 @@ void Ragdoll::DriveToPoseUsingKinematics(RVec3Arg inRootOffset, const Mat44 *inJ for (int i = 0; i < (int)mBodyIDs.size(); ++i) { const Mat44 &joint = inJointMatrices[i]; - bi.MoveKinematic(mBodyIDs[i], inRootOffset + joint.GetTranslation(), joint.GetRotation().GetQuaternion(), inDeltaTime); + bi.MoveKinematic(mBodyIDs[i], inRootOffset + joint.GetTranslation(), joint.GetQuaternion(), inDeltaTime); } } @@ -610,7 +616,7 @@ void Ragdoll::DriveToPoseUsingMotors(const SkeletonPose &inPose) { JPH_ASSERT(inPose.GetSkeleton() == mRagdollSettings->mSkeleton); - // Move bodies into the correct position using kinematics + // Move bodies into the correct position using constraints for (int i = 0; i < (int)inPose.GetJointMatrices().size(); ++i) { int constraint_idx = mRagdollSettings->GetConstraintIndexForBodyIndex(i); diff --git a/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.h b/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.h index f9807fe3..c31586f2 100644 --- a/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.h +++ b/Dependencies/Jolt/Jolt/Physics/Ragdoll/Ragdoll.h @@ -169,6 +169,9 @@ class JPH_EXPORT Ragdoll : public RefTarget, public NonCopyable /// Lower level version of GetPose that directly returns the world space joint matrices void GetPose(RVec3 &outRootOffset, Mat44 *outJointMatrices, bool inLockBodies = true); + /// This function calls ResetWarmStart on all constraints. It can be used after calling SetPose to reset previous frames impulses. See: Constraint::ResetWarmStart. + void ResetWarmStart(); + /// Drive the ragdoll to a specific pose by setting velocities on each of the bodies so that it will reach inPose in inDeltaTime void DriveToPoseUsingKinematics(const SkeletonPose &inPose, float inDeltaTime, bool inLockBodies = true); diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyContactListener.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyContactListener.h new file mode 100644 index 00000000..27e18537 --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyContactListener.h @@ -0,0 +1,55 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +JPH_NAMESPACE_BEGIN + +class Body; +class SoftBodyManifold; + +/// Return value for the OnSoftBodyContactValidate callback. Determines if the contact will be processed or not. +enum class SoftBodyValidateResult +{ + AcceptContact, ///< Accept this contact + RejectContact, ///< Reject this contact +}; + +/// Contact settings for a soft body contact. +/// The values are filled in with their defaults by the system so the callback doesn't need to modify anything, but it can if it wants to. +class SoftBodyContactSettings +{ +public: + float mInvMassScale1 = 1.0f; ///< Scale factor for the inverse mass of the soft body (0 = infinite mass, 1 = use original mass, 2 = body has half the mass). For the same contact pair, you should strive to keep the value the same over time. + float mInvMassScale2 = 1.0f; ///< Scale factor for the inverse mass of the other body (0 = infinite mass, 1 = use original mass, 2 = body has half the mass). For the same contact pair, you should strive to keep the value the same over time. + float mInvInertiaScale2 = 1.0f; ///< Scale factor for the inverse inertia of the other body (usually same as mInvMassScale2) + bool mIsSensor; ///< If the contact should be treated as a sensor vs body contact (no collision response) +}; + +/// A listener class that receives collision contact events for soft bodies against rigid bodies. +/// It can be registered with the PhysicsSystem. +class SoftBodyContactListener +{ +public: + /// Ensure virtual destructor + virtual ~SoftBodyContactListener() = default; + + /// Called whenever the soft body's aabox overlaps with another body's aabox (so receiving this callback doesn't tell if any of the vertices will collide). + /// This callback can be used to change the behavior of the collision response for all vertices in the soft body or to completely reject the contact. + /// Note that this callback is called when all bodies are locked, so don't use any locking functions! + /// @param inSoftBody The soft body that collided. It is safe to access this as the soft body is only updated on the current thread. + /// @param inOtherBody The other body that collided. Note that accessing the position/orientation/velocity of inOtherBody may result in a race condition as other threads may be modifying the body at the same time. + /// @param ioSettings The settings for all contact points that are generated by this collision. + /// @return Whether the contact should be processed or not. + virtual SoftBodyValidateResult OnSoftBodyContactValidate([[maybe_unused]] const Body &inSoftBody, [[maybe_unused]] const Body &inOtherBody, [[maybe_unused]] SoftBodyContactSettings &ioSettings) { return SoftBodyValidateResult::AcceptContact; } + + /// Called after all contact points for a soft body have been handled. You only receive one callback per body pair per simulation step and can use inManifold to iterate through all contacts. + /// Note that this callback is called when all bodies are locked, so don't use any locking functions! + /// You will receive a single callback for a soft body per simulation step for performance reasons, this callback will apply to all vertices in the soft body. + /// @param inSoftBody The soft body that collided. It is safe to access this as the soft body is only updated on the current thread. + /// @param inManifold The manifold that describes the contact surface between the two bodies. Other bodies may be modified by other threads during this callback. + virtual void OnSoftBodyContactAdded([[maybe_unused]] const Body &inSoftBody, const SoftBodyManifold &inManifold) { /* Do nothing */ } +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h index ea8a7aa5..9d153290 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h @@ -63,7 +63,7 @@ class JPH_EXPORT SoftBodyCreationSettings float mMaxLinearVelocity = 500.0f; ///< Maximum linear velocity that a vertex can reach (m/s) float mRestitution = 0.0f; ///< Restitution when colliding float mFriction = 0.2f; ///< Friction coefficient when colliding - float mPressure = 0.0f; ///< n * R * T, amount of substance * ideal gass constant * absolute temperature, see https://en.wikipedia.org/wiki/Pressure + float mPressure = 0.0f; ///< n * R * T, amount of substance * ideal gas constant * absolute temperature, see https://en.wikipedia.org/wiki/Pressure float mGravityFactor = 1.0f; ///< Value to multiply gravity with for this body bool mUpdatePosition = true; ///< Update the position of the body while simulating (set to false for something that is attached to the static world) bool mMakeRotationIdentity = true; ///< Bake specified mRotation in the vertices and set the body rotation to identity (simulation is slightly more accurate if the rotation of a soft body is kept to identity) diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyManifold.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyManifold.h new file mode 100644 index 00000000..ea3a65b3 --- /dev/null +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyManifold.h @@ -0,0 +1,59 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// An interface to query which vertices of a soft body are colliding with other bodies +class SoftBodyManifold +{ +public: + /// Get the vertices of the soft body for iterating + const Array & GetVertices() const { return mVertices; } + + /// Check if a vertex has collided with something in this update + JPH_INLINE bool HasContact(const SoftBodyVertex &inVertex) const + { + return inVertex.mHasContact; + } + + /// Get the local space contact point (multiply by GetCenterOfMassTransform() of the soft body to get world space) + JPH_INLINE Vec3 GetLocalContactPoint(const SoftBodyVertex &inVertex) const + { + return inVertex.mPosition - inVertex.mCollisionPlane.SignedDistance(inVertex.mPosition) * inVertex.mCollisionPlane.GetNormal(); + } + + /// Get the contact normal for the vertex (assumes there is a contact). + JPH_INLINE Vec3 GetContactNormal(const SoftBodyVertex &inVertex) const + { + return -inVertex.mCollisionPlane.GetNormal(); + } + + /// Get the body with which the vertex has collided in this update + JPH_INLINE BodyID GetContactBodyID(const SoftBodyVertex &inVertex) const + { + return inVertex.mHasContact? mCollidingShapes[inVertex.mCollidingShapeIndex].mBodyID : BodyID(); + } + +private: + /// Allow SoftBodyMotionProperties to construct us + friend class SoftBodyMotionProperties; + + /// Constructor + explicit SoftBodyManifold(const SoftBodyMotionProperties *inMotionProperties) : + mVertices(inMotionProperties->mVertices), + mCollidingShapes(inMotionProperties->mCollidingShapes) + { + } + + using CollidingShape = SoftBodyMotionProperties::CollidingShape; + + const Array & mVertices; + const Array & mCollidingShapes; +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp index 4b1972fe..31b06845 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include #ifdef JPH_DEBUG_RENDERER #include @@ -13,6 +15,8 @@ JPH_NAMESPACE_BEGIN +using namespace JPH::literals; + void SoftBodyMotionProperties::CalculateMassAndInertia() { MassProperties mp; @@ -66,11 +70,16 @@ void SoftBodyMotionProperties::Initialize(const SoftBodyCreationSettings &inSett out_vertex.mPreviousPosition = out_vertex.mPosition = rotation * Vec3(in_vertex.mPosition); out_vertex.mVelocity = rotation.Multiply3x3(Vec3(in_vertex.mVelocity)); out_vertex.mCollidingShapeIndex = -1; + out_vertex.mHasContact = false; out_vertex.mLargestPenetration = -FLT_MAX; out_vertex.mInvMass = in_vertex.mInvMass; mLocalBounds.Encapsulate(out_vertex.mPosition); } + // Allocate space for skinned vertices + if (!inSettings.mSettings->mSkinnedConstraints.empty()) + mSkinState.resize(mVertices.size()); + // We don't know delta time yet, so we can't predict the bounds and use the local bounds as the predicted bounds mLocalPredictedBounds = mLocalBounds; @@ -90,16 +99,16 @@ float SoftBodyMotionProperties::GetVolumeTimesSix() const return six_volume; } -void SoftBodyMotionProperties::DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem) +void SoftBodyMotionProperties::DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface) { JPH_PROFILE_FUNCTION(); struct Collector : public CollideShapeBodyCollector { - Collector(Body &inSoftBody, RMat44Arg inTransform, const PhysicsSystem &inSystem, Array &ioHits) : - mSoftBody(inSoftBody), - mInverseTransform(inTransform.InversedRotationTranslation()), - mBodyLockInterface(inSystem.GetBodyLockInterfaceNoLock()), + Collector(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface, Array &ioHits) : + mContext(inContext), + mInverseTransform(inContext.mCenterOfMassTransform.InversedRotationTranslation()), + mBodyLockInterface(inBodyLockInterface), mCombineFriction(inSystem.GetCombineFriction()), mCombineRestitution(inSystem.GetCombineRestitution()), mHits(ioHits) @@ -111,33 +120,43 @@ void SoftBodyMotionProperties::DetermineCollidingShapes(const SoftBodyUpdateCont BodyLockRead lock(mBodyLockInterface, inResult); if (lock.Succeeded()) { + const Body &soft_body = *mContext.mBody; const Body &body = lock.GetBody(); if (body.IsRigidBody() // TODO: We should support soft body vs soft body - && mSoftBody.GetCollisionGroup().CanCollide(body.GetCollisionGroup())) + && soft_body.GetCollisionGroup().CanCollide(body.GetCollisionGroup())) { - CollidingShape cs; - cs.mCenterOfMassTransform = (mInverseTransform * body.GetCenterOfMassTransform()).ToMat44(); - cs.mShape = body.GetShape(); - cs.mBodyID = inResult; - cs.mMotionType = body.GetMotionType(); - cs.mUpdateVelocities = false; - cs.mFriction = mCombineFriction(mSoftBody, SubShapeID(), body, SubShapeID()); - cs.mRestitution = mCombineRestitution(mSoftBody, SubShapeID(), body, SubShapeID()); - if (cs.mMotionType == EMotionType::Dynamic) + // Call the contact listener to see if we should accept this contact + // If there is no contact listener then we can ignore the contact if the other body is a sensor + SoftBodyContactSettings settings; + settings.mIsSensor = body.IsSensor(); + if (mContext.mContactListener == nullptr? !settings.mIsSensor : mContext.mContactListener->OnSoftBodyContactValidate(soft_body, body, settings) == SoftBodyValidateResult::AcceptContact) { - const MotionProperties *mp = body.GetMotionProperties(); - cs.mInvMass = mp->GetInverseMass(); - cs.mInvInertia = mp->GetInverseInertiaForRotation(cs.mCenterOfMassTransform.GetRotation()); - cs.mOriginalLinearVelocity = cs.mLinearVelocity = mInverseTransform.Multiply3x3(mp->GetLinearVelocity()); - cs.mOriginalAngularVelocity = cs.mAngularVelocity = mInverseTransform.Multiply3x3(mp->GetAngularVelocity()); + CollidingShape cs; + cs.mCenterOfMassTransform = (mInverseTransform * body.GetCenterOfMassTransform()).ToMat44(); + cs.mShape = body.GetShape(); + cs.mBodyID = inResult; + cs.mMotionType = body.GetMotionType(); + cs.mIsSensor = settings.mIsSensor; + cs.mUpdateVelocities = false; + cs.mFriction = mCombineFriction(soft_body, SubShapeID(), body, SubShapeID()); + cs.mRestitution = mCombineRestitution(soft_body, SubShapeID(), body, SubShapeID()); + if (cs.mMotionType == EMotionType::Dynamic) + { + const MotionProperties *mp = body.GetMotionProperties(); + cs.mInvMass = settings.mInvMassScale2 * mp->GetInverseMass(); + cs.mInvInertia = settings.mInvInertiaScale2 * mp->GetInverseInertiaForRotation(cs.mCenterOfMassTransform.GetRotation()); + cs.mSoftBodyInvMassScale = settings.mInvMassScale1; + cs.mOriginalLinearVelocity = cs.mLinearVelocity = mInverseTransform.Multiply3x3(mp->GetLinearVelocity()); + cs.mOriginalAngularVelocity = cs.mAngularVelocity = mInverseTransform.Multiply3x3(mp->GetAngularVelocity()); + } + mHits.push_back(cs); } - mHits.push_back(cs); } } } private: - Body & mSoftBody; + const SoftBodyUpdateContext &mContext; RMat44 mInverseTransform; const BodyLockInterface & mBodyLockInterface; ContactConstraintManager::CombineFunction mCombineFriction; @@ -145,10 +164,11 @@ void SoftBodyMotionProperties::DetermineCollidingShapes(const SoftBodyUpdateCont Array & mHits; }; - Collector collector(*inContext.mBody, inContext.mCenterOfMassTransform, inSystem, mCollidingShapes); + Collector collector(inContext, inSystem, inBodyLockInterface, mCollidingShapes); AABox bounds = mLocalBounds; bounds.Encapsulate(mLocalPredictedBounds); bounds = bounds.Transformed(inContext.mCenterOfMassTransform); + bounds.ExpandBy(Vec3::sReplicate(mSettings->mVertexRadius)); ObjectLayer layer = inContext.mBody->GetObjectLayer(); DefaultBroadPhaseLayerFilter broadphase_layer_filter = inSystem.GetDefaultBroadPhaseLayerFilter(layer); DefaultObjectLayerFilter object_layer_filter = inSystem.GetDefaultLayerFilter(layer); @@ -228,6 +248,97 @@ void SoftBodyMotionProperties::IntegratePositions(const SoftBodyUpdateContext &i } } +void SoftBodyMotionProperties::ApplyBendConstraints(const SoftBodyUpdateContext &inContext) +{ + JPH_PROFILE_FUNCTION(); + + float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime); + + for (const DihedralBend &b : mSettings->mDihedralBendConstraints) + { + Vertex &v0 = mVertices[b.mVertex[0]]; + Vertex &v1 = mVertices[b.mVertex[1]]; + Vertex &v2 = mVertices[b.mVertex[2]]; + Vertex &v3 = mVertices[b.mVertex[3]]; + + // Get positions + Vec3 x0 = v0.mPosition; + Vec3 x1 = v1.mPosition; + Vec3 x2 = v2.mPosition; + Vec3 x3 = v3.mPosition; + + /* + x2 + e1/ \e3 + / \ + x0----x1 + \ e0 / + e2\ /e4 + x3 + */ + + // Calculate the shared edge of the triangles + Vec3 e = x1 - x0; + float e_len = e.Length(); + if (e_len < 1.0e-6f) + continue; + + // Calculate the normals of the triangles + Vec3 x1x2 = x2 - x1; + Vec3 x1x3 = x3 - x1; + Vec3 n1 = (x2 - x0).Cross(x1x2); + Vec3 n2 = x1x3.Cross(x3 - x0); + float n1_len_sq = n1.LengthSq(); + float n2_len_sq = n2.LengthSq(); + float n1_len_sq_n2_len_sq = n1_len_sq * n2_len_sq; + if (n1_len_sq_n2_len_sq < 1.0e-24f) + continue; + + // Calculate constraint equation + // As per "Strain Based Dynamics" Appendix A we need to negate the gradients when (n1 x n2) . e > 0, instead we make sure that the sign of the constraint equation is correct + float sign = Sign(n2.Cross(n1).Dot(e)); + float d = n1.Dot(n2) / sqrt(n1_len_sq_n2_len_sq); + float c = sign * ACos(d) - b.mInitialAngle; + + // Ensure the range is -PI to PI + if (c > JPH_PI) + c -= 2.0f * JPH_PI; + else if (c < -JPH_PI) + c += 2.0f * JPH_PI; + + // Calculate gradient of constraint equation + // Taken from "Strain Based Dynamics" - Matthias Muller et al. (Appendix A) + // with p1 = x2, p2 = x3, p3 = x0 and p4 = x1 + // which in turn is based on "Simulation of Clothing with Folds and Wrinkles" - R. Bridson et al. (Section 4) + n1 /= n1_len_sq; + n2 /= n2_len_sq; + Vec3 d0c = (x1x2.Dot(e) * n1 + x1x3.Dot(e) * n2) / e_len; + Vec3 d2c = e_len * n1; + Vec3 d3c = e_len * n2; + + // The sum of the gradients must be zero (see "Strain Based Dynamics" section 4) + Vec3 d1c = -d0c - d2c - d3c; + + // Get masses + float w0 = v0.mInvMass; + float w1 = v1.mInvMass; + float w2 = v2.mInvMass; + float w3 = v3.mInvMass; + + // Calculate -lambda + float denom = w0 * d0c.LengthSq() + w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + b.mCompliance * inv_dt_sq; + if (denom < 1.0e-12f) + continue; + float minus_lambda = c / denom; + + // Apply correction + v0.mPosition = x0 - minus_lambda * w0 * d0c; + v1.mPosition = x1 - minus_lambda * w1 * d1c; + v2.mPosition = x2 - minus_lambda * w2 * d2c; + v3.mPosition = x3 - minus_lambda * w3 * d3c; + } +} + void SoftBodyMotionProperties::ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext) { JPH_PROFILE_FUNCTION(); @@ -259,18 +370,78 @@ void SoftBodyMotionProperties::ApplyVolumeConstraints(const SoftBodyUpdateContex Vec3 d3c = x1x4.Cross(x1x2); Vec3 d4c = x1x2.Cross(x1x3); + // Get masses float w1 = v1.mInvMass; float w2 = v2.mInvMass; float w3 = v3.mInvMass; float w4 = v4.mInvMass; - JPH_ASSERT(w1 > 0.0f || w2 > 0.0f || w3 > 0.0f || w4 > 0.0f); + + // Calculate -lambda + float denom = w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + w4 * d4c.LengthSq() + v.mCompliance * inv_dt_sq; + if (denom < 1.0e-12f) + continue; + float minus_lambda = c / denom; // Apply correction - float lambda = -c / (w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + w4 * d4c.LengthSq() + v.mCompliance * inv_dt_sq); - v1.mPosition += lambda * w1 * d1c; - v2.mPosition += lambda * w2 * d2c; - v3.mPosition += lambda * w3 * d3c; - v4.mPosition += lambda * w4 * d4c; + v1.mPosition = x1 - minus_lambda * w1 * d1c; + v2.mPosition = x2 - minus_lambda * w2 * d2c; + v3.mPosition = x3 - minus_lambda * w3 * d3c; + v4.mPosition = x4 - minus_lambda * w4 * d4c; + } +} + +void SoftBodyMotionProperties::ApplySkinConstraints([[maybe_unused]] const SoftBodyUpdateContext &inContext) +{ + // Early out if nothing to do + if (mSettings->mSkinnedConstraints.empty() || !mEnableSkinConstraints) + return; + + JPH_ASSERT(mSkinStateTransform == inContext.mCenterOfMassTransform, "Skinning state is stale, artifacts will show!"); + + // Apply the constraints + Vertex *vertices = mVertices.data(); + const SkinState *skin_states = mSkinState.data(); + for (const Skinned &s : mSettings->mSkinnedConstraints) + { + Vertex &vertex = vertices[s.mVertex]; + const SkinState &skin_state = skin_states[s.mVertex]; + float max_distance = s.mMaxDistance * mSkinnedMaxDistanceMultiplier; + if (max_distance > 0.0f) + { + // Move vertex if it violated the back stop + if (s.mBackStopDistance < max_distance) + { + // Center of the back stop sphere + Vec3 center = skin_state.mPosition - skin_state.mNormal * (s.mBackStopDistance + s.mBackStopRadius); + + // Check if we're inside the back stop sphere + Vec3 delta = vertex.mPosition - center; + float delta_len_sq = delta.LengthSq(); + if (delta_len_sq < Square(s.mBackStopRadius)) + { + // Push the vertex to the surface of the back stop sphere + float delta_len = sqrt(delta_len_sq); + vertex.mPosition = delta_len > 0.0f? + center + delta * (s.mBackStopRadius / delta_len) + : center + skin_state.mNormal * s.mBackStopRadius; + } + } + + // Clamp vertex distance to max distance from skinned position + if (max_distance < FLT_MAX) + { + Vec3 delta = vertex.mPosition - skin_state.mPosition; + float delta_len_sq = delta.LengthSq(); + float max_distance_sq = Square(max_distance); + if (delta_len_sq > max_distance_sq) + vertex.mPosition = skin_state.mPosition + delta * sqrt(max_distance_sq / delta_len_sq); + } + } + else + { + // Kinematic: Just update the vertex position + vertex.mPosition = skin_state.mPosition; + } } } @@ -288,16 +459,42 @@ void SoftBodyMotionProperties::ApplyEdgeConstraints(const SoftBodyUpdateContext Vertex &v0 = mVertices[e.mVertex[0]]; Vertex &v1 = mVertices[e.mVertex[1]]; + // Get positions + Vec3 x0 = v0.mPosition; + Vec3 x1 = v1.mPosition; + // Calculate current length - Vec3 delta = v1.mPosition - v0.mPosition; + Vec3 delta = x1 - x0; float length = delta.Length(); - if (length > 0.0f) - { - // Apply correction - Vec3 correction = delta * (length - e.mRestLength) / (length * (v0.mInvMass + v1.mInvMass + e.mCompliance * inv_dt_sq)); - v0.mPosition += v0.mInvMass * correction; - v1.mPosition -= v1.mInvMass * correction; - } + + // Apply correction + float denom = length * (v0.mInvMass + v1.mInvMass + e.mCompliance * inv_dt_sq); + if (denom < 1.0e-12f) + continue; + Vec3 correction = delta * (length - e.mRestLength) / denom; + v0.mPosition = x0 + v0.mInvMass * correction; + v1.mPosition = x1 - v1.mInvMass * correction; + } +} + +void SoftBodyMotionProperties::ApplyLRAConstraints() +{ + JPH_PROFILE_FUNCTION(); + + // Satisfy LRA constraints + Vertex *vertices = mVertices.data(); + for (const LRA &lra : mSettings->mLRAConstraints) + { + JPH_ASSERT(lra.mVertex[0] < mVertices.size()); + JPH_ASSERT(lra.mVertex[1] < mVertices.size()); + const Vertex &vertex0 = vertices[lra.mVertex[0]]; + Vertex &vertex1 = vertices[lra.mVertex[1]]; + + Vec3 x0 = vertex0.mPosition; + Vec3 delta = vertex1.mPosition - x0; + float delta_len_sq = delta.LengthSq(); + if (delta_len_sq > Square(lra.mMaxDistance)) + vertex1.mPosition = x0 + delta * lra.mMaxDistance / sqrt(delta_len_sq); } } @@ -307,6 +504,7 @@ void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(cons float dt = inContext.mSubStepDeltaTime; float restitution_treshold = -2.0f * inContext.mGravity.Length() * dt; + float vertex_radius = mSettings->mVertexRadius; for (Vertex &v : mVertices) if (v.mInvMass > 0.0f) { @@ -320,91 +518,101 @@ void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(cons if (v.mCollidingShapeIndex >= 0) { // Check if there is a collision - float projected_distance = -v.mCollisionPlane.SignedDistance(v.mPosition); + float projected_distance = -v.mCollisionPlane.SignedDistance(v.mPosition) + vertex_radius; if (projected_distance > 0.0f) { - // Note that we already calculated the velocity, so this does not affect the velocity (next iteration starts by setting previous position to current position) - Vec3 contact_normal = v.mCollisionPlane.GetNormal(); - v.mPosition += contact_normal * projected_distance; + // Remember that there was a collision + v.mHasContact = true; + mHasContact = true; + // Sensors should not have a collision response CollidingShape &cs = mCollidingShapes[v.mCollidingShapeIndex]; - - // Apply friction as described in Detailed Rigid Body Simulation with Extended Position Based Dynamics - Matthias Muller et al. - // See section 3.6: - // Inverse mass: w1 = 1 / m1, w2 = 1 / m2 + (r2 x n)^T I^-1 (r2 x n) = 0 for a static object - // r2 are the contact point relative to the center of mass of body 2 - // Lagrange multiplier for contact: lambda = -c / (w1 + w2) - // Where c is the constraint equation (the distance to the plane, negative because penetrating) - // Contact normal force: fn = lambda / dt^2 - // Delta velocity due to friction dv = -vt / |vt| * min(dt * friction * fn * (w1 + w2), |vt|) = -vt * min(-friction * c / (|vt| * dt), 1) - // Note that I think there is an error in the paper, I added a mass term, see: https://github.com/matthias-research/pages/issues/29 - // Relative velocity: vr = v1 - v2 - omega2 x r2 - // Normal velocity: vn = vr . contact_normal - // Tangential velocity: vt = vr - contact_normal * vn - // Impulse: p = dv / (w1 + w2) - // Changes in particle velocities: - // v1 = v1 + p / m1 - // v2 = v2 - p / m2 (no change when colliding with a static body) - // w2 = w2 - I^-1 (r2 x p) (no change when colliding with a static body) - if (cs.mMotionType == EMotionType::Dynamic) + if (!cs.mIsSensor) { - // Calculate normal and tangential velocity (equation 30) - Vec3 r2 = v.mPosition - cs.mCenterOfMassTransform.GetTranslation(); - Vec3 v2 = cs.GetPointVelocity(r2); - Vec3 relative_velocity = v.mVelocity - v2; - Vec3 v_normal = contact_normal * contact_normal.Dot(relative_velocity); - Vec3 v_tangential = relative_velocity - v_normal; - float v_tangential_length = v_tangential.Length(); - - // Calculate inverse effective mass - Vec3 r2_cross_n = r2.Cross(contact_normal); - float w2 = cs.mInvMass + r2_cross_n.Dot(cs.mInvInertia * r2_cross_n); - float w1_plus_w2 = v.mInvMass + w2; - - // Calculate delta relative velocity due to friction (modified equation 31) - Vec3 dv; - if (v_tangential_length > 0.0f) - dv = v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f); + // Note that we already calculated the velocity, so this does not affect the velocity (next iteration starts by setting previous position to current position) + Vec3 contact_normal = v.mCollisionPlane.GetNormal(); + v.mPosition += contact_normal * projected_distance; + + // Apply friction as described in Detailed Rigid Body Simulation with Extended Position Based Dynamics - Matthias Muller et al. + // See section 3.6: + // Inverse mass: w1 = 1 / m1, w2 = 1 / m2 + (r2 x n)^T I^-1 (r2 x n) = 0 for a static object + // r2 are the contact point relative to the center of mass of body 2 + // Lagrange multiplier for contact: lambda = -c / (w1 + w2) + // Where c is the constraint equation (the distance to the plane, negative because penetrating) + // Contact normal force: fn = lambda / dt^2 + // Delta velocity due to friction dv = -vt / |vt| * min(dt * friction * fn * (w1 + w2), |vt|) = -vt * min(-friction * c / (|vt| * dt), 1) + // Note that I think there is an error in the paper, I added a mass term, see: https://github.com/matthias-research/pages/issues/29 + // Relative velocity: vr = v1 - v2 - omega2 x r2 + // Normal velocity: vn = vr . contact_normal + // Tangential velocity: vt = vr - contact_normal * vn + // Impulse: p = dv / (w1 + w2) + // Changes in particle velocities: + // v1 = v1 + p / m1 + // v2 = v2 - p / m2 (no change when colliding with a static body) + // w2 = w2 - I^-1 (r2 x p) (no change when colliding with a static body) + if (cs.mMotionType == EMotionType::Dynamic) + { + // Calculate normal and tangential velocity (equation 30) + Vec3 r2 = v.mPosition - cs.mCenterOfMassTransform.GetTranslation(); + Vec3 v2 = cs.GetPointVelocity(r2); + Vec3 relative_velocity = v.mVelocity - v2; + Vec3 v_normal = contact_normal * contact_normal.Dot(relative_velocity); + Vec3 v_tangential = relative_velocity - v_normal; + float v_tangential_length = v_tangential.Length(); + + // Calculate resulting inverse mass of vertex + float vertex_inv_mass = cs.mSoftBodyInvMassScale * v.mInvMass; + + // Calculate inverse effective mass + Vec3 r2_cross_n = r2.Cross(contact_normal); + float w2 = cs.mInvMass + r2_cross_n.Dot(cs.mInvInertia * r2_cross_n); + float w1_plus_w2 = vertex_inv_mass + w2; + + // Calculate delta relative velocity due to friction (modified equation 31) + Vec3 dv; + if (v_tangential_length > 0.0f) + dv = v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f); + else + dv = Vec3::sZero(); + + // Calculate delta relative velocity due to restitution (equation 35) + dv += v_normal; + float prev_v_normal = (prev_v - v2).Dot(contact_normal); + if (prev_v_normal < restitution_treshold) + dv += cs.mRestitution * prev_v_normal * contact_normal; + + // Calculate impulse + Vec3 p = dv / w1_plus_w2; + + // Apply impulse to particle + v.mVelocity -= p * vertex_inv_mass; + + // Apply impulse to rigid body + cs.mLinearVelocity += p * cs.mInvMass; + cs.mAngularVelocity += cs.mInvInertia * r2.Cross(p); + + // Mark that the velocities of the body we hit need to be updated + cs.mUpdateVelocities = true; + } else - dv = Vec3::sZero(); - - // Calculate delta relative velocity due to restitution (equation 35) - dv += v_normal; - float prev_v_normal = (prev_v - v2).Dot(contact_normal); - if (prev_v_normal < restitution_treshold) - dv += cs.mRestitution * prev_v_normal * contact_normal; - - // Calculate impulse - Vec3 p = dv / w1_plus_w2; - - // Apply impulse to particle - v.mVelocity -= p * v.mInvMass; - - // Apply impulse to rigid body - cs.mLinearVelocity += p * cs.mInvMass; - cs.mAngularVelocity += cs.mInvInertia * r2.Cross(p); - - // Mark that the velocities of the body we hit need to be updated - cs.mUpdateVelocities = true; - } - else - { - // Body is not moveable, equations are simpler - - // Calculate normal and tangential velocity (equation 30) - Vec3 v_normal = contact_normal * contact_normal.Dot(v.mVelocity); - Vec3 v_tangential = v.mVelocity - v_normal; - float v_tangential_length = v_tangential.Length(); - - // Apply friction (modified equation 31) - if (v_tangential_length > 0.0f) - v.mVelocity -= v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f); - - // Apply restitution (equation 35) - v.mVelocity -= v_normal; - float prev_v_normal = prev_v.Dot(contact_normal); - if (prev_v_normal < restitution_treshold) - v.mVelocity -= cs.mRestitution * prev_v_normal * contact_normal; + { + // Body is not movable, equations are simpler + + // Calculate normal and tangential velocity (equation 30) + Vec3 v_normal = contact_normal * contact_normal.Dot(v.mVelocity); + Vec3 v_tangential = v.mVelocity - v_normal; + float v_tangential_length = v_tangential.Length(); + + // Apply friction (modified equation 31) + if (v_tangential_length > 0.0f) + v.mVelocity -= v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f); + + // Apply restitution (equation 35) + v.mVelocity -= v_normal; + float prev_v_normal = prev_v.Dot(contact_normal); + if (prev_v_normal < restitution_treshold) + v.mVelocity -= cs.mRestitution * prev_v_normal * contact_normal; + } } } } @@ -415,12 +623,17 @@ void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioCont { JPH_PROFILE_FUNCTION(); + // Contact callback + if (mHasContact && ioContext.mContactListener != nullptr) + ioContext.mContactListener->OnSoftBodyContactAdded(*ioContext.mBody, SoftBodyManifold(this)); + // Loop through vertices once more to update the global state float dt = ioContext.mDeltaTime; float max_linear_velocity_sq = Square(GetMaxLinearVelocity()); float max_v_sq = 0.0f; Vec3 linear_velocity = Vec3::sZero(), angular_velocity = Vec3::sZero(); mLocalPredictedBounds = mLocalBounds = { }; + mHasContact = false; for (Vertex &v : mVertices) { // Calculate max square velocity @@ -443,6 +656,7 @@ void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioCont // Reset collision data for the next iteration v.mCollidingShapeIndex = -1; + v.mHasContact = false; v.mLargestPenetration = -FLT_MAX; } @@ -481,15 +695,14 @@ void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioCont ioContext.mCanSleep = ECanSleep::CannotSleep; } -void SoftBodyMotionProperties::UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, PhysicsSystem &inSystem) +void SoftBodyMotionProperties::UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, BodyInterface &inBodyInterface) { JPH_PROFILE_FUNCTION(); // Write back velocity deltas - BodyInterface &body_interface = inSystem.GetBodyInterfaceNoLock(); for (const CollidingShape &cs : mCollidingShapes) if (cs.mUpdateVelocities) - body_interface.AddLinearAndAngularVelocity(cs.mBodyID, inContext.mCenterOfMassTransform.Multiply3x3(cs.mLinearVelocity - cs.mOriginalLinearVelocity), inContext.mCenterOfMassTransform.Multiply3x3(cs.mAngularVelocity - cs.mOriginalAngularVelocity)); + inBodyInterface.AddLinearAndAngularVelocity(cs.mBodyID, inContext.mCenterOfMassTransform.Multiply3x3(cs.mLinearVelocity - cs.mOriginalLinearVelocity), inContext.mCenterOfMassTransform.Multiply3x3(cs.mAngularVelocity - cs.mOriginalAngularVelocity)); // Clear colliding shapes to avoid hanging on to references to shapes mCollidingShapes.clear(); @@ -502,6 +715,7 @@ void SoftBodyMotionProperties::InitializeUpdateContext(float inDeltaTime, Body & // Store body ioContext.mBody = &inSoftBody; ioContext.mMotionProperties = this; + ioContext.mContactListener = inSystem.GetSoftBodyContactListener(); // Convert gravity to local space ioContext.mCenterOfMassTransform = inSoftBody.GetCenterOfMassTransform(); @@ -524,6 +738,8 @@ void SoftBodyMotionProperties::StartNextIteration(const SoftBodyUpdateContext &i IntegratePositions(ioContext); + ApplyBendConstraints(ioContext); + ApplyVolumeConstraints(ioContext); } @@ -562,60 +778,68 @@ SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelApplyEdgeCon JPH_ASSERT(num_groups > 0, "SoftBodySharedSettings::Optimize should have been called!"); uint32 edge_group, edge_start_idx; SoftBodyUpdateContext::sGetEdgeGroupAndStartIdx(ioContext.mNextEdgeConstraint.load(memory_order_relaxed), edge_group, edge_start_idx); - if (edge_group < num_groups && edge_start_idx < mSettings->GetEdgeGroupSize(edge_group)) + if (edge_group < num_groups) { - // Fetch the next batch of edges to process - uint64 next_edge_batch = ioContext.mNextEdgeConstraint.fetch_add(SoftBodyUpdateContext::cEdgeConstraintBatch, memory_order_acquire); - SoftBodyUpdateContext::sGetEdgeGroupAndStartIdx(next_edge_batch, edge_group, edge_start_idx); - if (edge_group < num_groups) + uint edge_group_size = mSettings->GetEdgeGroupSize(edge_group); + if (edge_start_idx < edge_group_size || edge_group_size == 0) { - bool non_parallel_group = edge_group == num_groups - 1; // Last group is the non-parallel group and goes as a whole - uint edge_group_size = mSettings->GetEdgeGroupSize(edge_group); - if (non_parallel_group? edge_start_idx == 0 : edge_start_idx < edge_group_size) + // Fetch the next batch of edges to process + uint64 next_edge_batch = ioContext.mNextEdgeConstraint.fetch_add(SoftBodyUpdateContext::cEdgeConstraintBatch, memory_order_acquire); + SoftBodyUpdateContext::sGetEdgeGroupAndStartIdx(next_edge_batch, edge_group, edge_start_idx); + if (edge_group < num_groups) { - // Process edges - uint num_edges_to_process = non_parallel_group? edge_group_size : min(SoftBodyUpdateContext::cEdgeConstraintBatch, edge_group_size - edge_start_idx); - if (edge_group > 0) - edge_start_idx += mSettings->mEdgeGroupEndIndices[edge_group - 1]; - ApplyEdgeConstraints(ioContext, edge_start_idx, edge_start_idx + num_edges_to_process); - - // Test if we're at the end of this group - uint edge_constraints_processed = ioContext.mNumEdgeConstraintsProcessed.fetch_add(num_edges_to_process, memory_order_relaxed) + num_edges_to_process; - if (edge_constraints_processed >= edge_group_size) + bool non_parallel_group = edge_group == num_groups - 1; // Last group is the non-parallel group and goes as a whole + edge_group_size = mSettings->GetEdgeGroupSize(edge_group); + if (non_parallel_group? edge_start_idx == 0 : edge_start_idx < edge_group_size) { - // Non parallel group is the last group (which is also the only group that can be empty) - if (non_parallel_group || mSettings->GetEdgeGroupSize(edge_group + 1) == 0) + // Process edges + uint num_edges_to_process = non_parallel_group? edge_group_size : min(SoftBodyUpdateContext::cEdgeConstraintBatch, edge_group_size - edge_start_idx); + if (edge_group > 0) + edge_start_idx += mSettings->mEdgeGroupEndIndices[edge_group - 1]; + ApplyEdgeConstraints(ioContext, edge_start_idx, edge_start_idx + num_edges_to_process); + + // Test if we're at the end of this group + uint edge_constraints_processed = ioContext.mNumEdgeConstraintsProcessed.fetch_add(num_edges_to_process, memory_order_relaxed) + num_edges_to_process; + if (edge_constraints_processed >= edge_group_size) { - // Finish the iteration - ApplyCollisionConstraintsAndUpdateVelocities(ioContext); - - uint iteration = ioContext.mNextIteration.fetch_add(1, memory_order_relaxed); - if (iteration < mNumIterations) + // Non parallel group is the last group (which is also the only group that can be empty) + if (non_parallel_group || mSettings->GetEdgeGroupSize(edge_group + 1) == 0) { - // Start a new iteration - StartNextIteration(ioContext); - - // Reset next edge to process - ioContext.mNumEdgeConstraintsProcessed.store(0, memory_order_relaxed); - ioContext.mNextEdgeConstraint.store(0, memory_order_release); + // Finish the iteration + ApplyLRAConstraints(); + + ApplyCollisionConstraintsAndUpdateVelocities(ioContext); + + ApplySkinConstraints(ioContext); + + uint iteration = ioContext.mNextIteration.fetch_add(1, memory_order_relaxed); + if (iteration < mNumIterations) + { + // Start a new iteration + StartNextIteration(ioContext); + + // Reset next edge to process + ioContext.mNumEdgeConstraintsProcessed.store(0, memory_order_relaxed); + ioContext.mNextEdgeConstraint.store(0, memory_order_release); + } + else + { + // On final iteration we update the state + UpdateSoftBodyState(ioContext, inPhysicsSettings); + + ioContext.mState.store(SoftBodyUpdateContext::EState::Done, memory_order_release); + return EStatus::Done; + } } else { - // On final iteration we update the state - UpdateSoftBodyState(ioContext, inPhysicsSettings); - - ioContext.mState.store(SoftBodyUpdateContext::EState::Done, memory_order_release); - return EStatus::Done; + // Next group + ioContext.mNumEdgeConstraintsProcessed.store(0, memory_order_relaxed); + ioContext.mNextEdgeConstraint.store(SoftBodyUpdateContext::sGetEdgeGroupStart(edge_group + 1), memory_order_release); } } - else - { - // Next group - ioContext.mNumEdgeConstraintsProcessed.store(0, memory_order_relaxed); - ioContext.mNextEdgeConstraint.store(SoftBodyUpdateContext::sGetEdgeGroupStart(edge_group + 1), memory_order_release); - } + return EStatus::DidWork; } - return EStatus::DidWork; } } } @@ -641,12 +865,128 @@ SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelUpdate(SoftB } } +void SoftBodyMotionProperties::SkinVertices(RMat44Arg inCenterOfMassTransform, const Mat44 *inJointMatrices, [[maybe_unused]] uint inNumJoints, bool inHardSkinAll, TempAllocator &ioTempAllocator) +{ + // Calculate the skin matrices + uint num_skin_matrices = uint(mSettings->mInvBindMatrices.size()); + uint skin_matrices_size = num_skin_matrices * sizeof(Mat44); + Mat44 *skin_matrices = (Mat44 *)ioTempAllocator.Allocate(skin_matrices_size); + const Mat44 *skin_matrices_end = skin_matrices + num_skin_matrices; + const InvBind *inv_bind_matrix = mSettings->mInvBindMatrices.data(); + for (Mat44 *s = skin_matrices; s < skin_matrices_end; ++s, ++inv_bind_matrix) + *s = inJointMatrices[inv_bind_matrix->mJointIndex] * inv_bind_matrix->mInvBind; + + // Skin the vertices + mSkinStateTransform = inCenterOfMassTransform; + JPH_IF_ENABLE_ASSERTS(uint num_vertices = uint(mSettings->mVertices.size());) + JPH_ASSERT(mSkinState.size() == num_vertices); + const SoftBodySharedSettings::Vertex *in_vertices = mSettings->mVertices.data(); + for (const Skinned &s : mSettings->mSkinnedConstraints) + { + // Get bind pose + JPH_ASSERT(s.mVertex < num_vertices); + Vec3 bind_pos = Vec3::sLoadFloat3Unsafe(in_vertices[s.mVertex].mPosition); + + // Skin vertex + Vec3 pos = Vec3::sZero(); + for (const SkinWeight &w : s.mWeights) + { + // We assume that the first zero weight is the end of the list + if (w.mWeight == 0.0f) + break; + + JPH_ASSERT(w.mInvBindIndex < num_skin_matrices); + pos += w.mWeight * (skin_matrices[w.mInvBindIndex] * bind_pos); + } + mSkinState[s.mVertex].mPosition = pos; + } + + // Calculate the normals + for (const Skinned &s : mSettings->mSkinnedConstraints) + { + Vec3 normal = Vec3::sZero(); + uint32 num_faces = s.mNormalInfo >> 24; + if (num_faces > 0) + { + // Calculate normal + const uint32 *f = &mSettings->mSkinnedConstraintNormals[s.mNormalInfo & 0xffffff]; + const uint32 *f_end = f + num_faces; + while (f < f_end) + { + const Face &face = mSettings->mFaces[*f]; + Vec3 v0 = mSkinState[face.mVertex[0]].mPosition; + Vec3 v1 = mSkinState[face.mVertex[1]].mPosition; + Vec3 v2 = mSkinState[face.mVertex[2]].mPosition; + normal += (v1 - v0).Cross(v2 - v0).NormalizedOr(Vec3::sZero()); + ++f; + } + normal = normal.NormalizedOr(Vec3::sZero()); + } + mSkinState[s.mVertex].mNormal = normal; + } + + ioTempAllocator.Free(skin_matrices, skin_matrices_size); + + if (inHardSkinAll) + { + // Hard skin all vertices and reset their velocities + for (const Skinned &s : mSettings->mSkinnedConstraints) + { + Vertex &vertex = mVertices[s.mVertex]; + vertex.mPosition = mSkinState[s.mVertex].mPosition; + vertex.mVelocity = Vec3::sZero(); + } + } + else if (!mEnableSkinConstraints) + { + // Hard skin only the kinematic vertices as we will not solve the skin constraints later + for (const Skinned &s : mSettings->mSkinnedConstraints) + if (s.mMaxDistance == 0.0f) + { + Vertex &vertex = mVertices[s.mVertex]; + vertex.mPosition = mSkinState[s.mVertex].mPosition; + } + } +} + +void SoftBodyMotionProperties::CustomUpdate(float inDeltaTime, Body &ioSoftBody, PhysicsSystem &inSystem) +{ + JPH_PROFILE_FUNCTION(); + + // Create update context + SoftBodyUpdateContext context; + InitializeUpdateContext(inDeltaTime, ioSoftBody, inSystem, context); + + // Determine bodies we're colliding with + DetermineCollidingShapes(context, inSystem, inSystem.GetBodyLockInterface()); + + // Call the internal update until it finishes + EStatus status; + const PhysicsSettings &settings = inSystem.GetPhysicsSettings(); + while ((status = ParallelUpdate(context, settings)) == EStatus::DidWork) + continue; + JPH_ASSERT(status == EStatus::Done); + + // Update the state of the bodies we've collided with + UpdateRigidBodyVelocities(context, inSystem.GetBodyInterface()); + + // Update position of the soft body + if (mUpdatePosition) + inSystem.GetBodyInterface().SetPosition(ioSoftBody.GetID(), ioSoftBody.GetPosition() + context.mDeltaPosition, EActivation::DontActivate); +} + #ifdef JPH_DEBUG_RENDERER void SoftBodyMotionProperties::DrawVertices(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const { for (const Vertex &v : mVertices) - inRenderer->DrawMarker(inCenterOfMassTransform * v.mPosition, Color::sRed, 0.05f); + inRenderer->DrawMarker(inCenterOfMassTransform * v.mPosition, v.mInvMass > 0.0f? Color::sGreen : Color::sRed, 0.05f); +} + +void SoftBodyMotionProperties::DrawVertexVelocities(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const +{ + for (const Vertex &v : mVertices) + inRenderer->DrawArrow(inCenterOfMassTransform * v.mPosition, inCenterOfMassTransform * (v.mPosition + v.mVelocity), Color::sYellow, 0.01f); } void SoftBodyMotionProperties::DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const @@ -655,6 +995,24 @@ void SoftBodyMotionProperties::DrawEdgeConstraints(DebugRenderer *inRenderer, RM inRenderer->DrawLine(inCenterOfMassTransform * mVertices[e.mVertex[0]].mPosition, inCenterOfMassTransform * mVertices[e.mVertex[1]].mPosition, Color::sWhite); } +void SoftBodyMotionProperties::DrawBendConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const +{ + for (const DihedralBend &b : mSettings->mDihedralBendConstraints) + { + RVec3 x0 = inCenterOfMassTransform * mVertices[b.mVertex[0]].mPosition; + RVec3 x1 = inCenterOfMassTransform * mVertices[b.mVertex[1]].mPosition; + RVec3 x2 = inCenterOfMassTransform * mVertices[b.mVertex[2]].mPosition; + RVec3 x3 = inCenterOfMassTransform * mVertices[b.mVertex[3]].mPosition; + RVec3 c_edge = 0.5_r * (x0 + x1); + RVec3 c0 = (x0 + x1 + x2) / 3.0_r; + RVec3 c1 = (x0 + x1 + x3) / 3.0_r; + + inRenderer->DrawArrow(0.9_r * x0 + 0.1_r * x1, 0.1_r * x0 + 0.9_r * x1, Color::sDarkGreen, 0.01f); + inRenderer->DrawLine(c_edge, 0.1_r * c_edge + 0.9_r * c0, Color::sGreen); + inRenderer->DrawLine(c_edge, 0.1_r * c_edge + 0.9_r * c1, Color::sGreen); + } +} + void SoftBodyMotionProperties::DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const { for (const Volume &v : mSettings->mVolumeConstraints) @@ -671,6 +1029,22 @@ void SoftBodyMotionProperties::DrawVolumeConstraints(DebugRenderer *inRenderer, } } +void SoftBodyMotionProperties::DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const +{ + for (const Skinned &s : mSettings->mSkinnedConstraints) + { + const SkinState &skin_state = mSkinState[s.mVertex]; + DebugRenderer::sInstance->DrawArrow(mSkinStateTransform * skin_state.mPosition, mSkinStateTransform * (skin_state.mPosition + 0.1f * skin_state.mNormal), Color::sOrange, 0.01f); + DebugRenderer::sInstance->DrawLine(mSkinStateTransform * skin_state.mPosition, inCenterOfMassTransform * mVertices[s.mVertex].mPosition, Color::sBlue); + } +} + +void SoftBodyMotionProperties::DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const +{ + for (const LRA &l : mSettings->mLRAConstraints) + inRenderer->DrawLine(inCenterOfMassTransform * mVertices[l.mVertex[0]].mPosition, inCenterOfMassTransform * mVertices[l.mVertex[1]].mPosition, Color::sGrey); +} + void SoftBodyMotionProperties::DrawPredictedBounds(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const { inRenderer->DrawWireBox(inCenterOfMassTransform, mLocalPredictedBounds, Color::sRed); diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h index d32dcd14..0124a33b 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h @@ -14,10 +14,13 @@ JPH_NAMESPACE_BEGIN class PhysicsSystem; +class BodyInterface; +class BodyLockInterface; struct PhysicsSettings; class Body; class Shape; class SoftBodyCreationSettings; +class TempAllocator; #ifdef JPH_DEBUG_RENDERER class DebugRenderer; #endif // JPH_DEBUG_RENDERER @@ -32,7 +35,12 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties using Vertex = SoftBodyVertex; using Edge = SoftBodySharedSettings::Edge; using Face = SoftBodySharedSettings::Face; + using DihedralBend = SoftBodySharedSettings::DihedralBend; using Volume = SoftBodySharedSettings::Volume; + using InvBind = SoftBodySharedSettings::InvBind; + using SkinWeight = SoftBodySharedSettings::SkinWeight; + using Skinned = SoftBodySharedSettings::Skinned; + using LRA = SoftBodySharedSettings::LRA; /// Initialize the soft body motion properties void Initialize(const SoftBodyCreationSettings &inSettings); @@ -69,6 +77,14 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties bool GetUpdatePosition() const { return mUpdatePosition; } void SetUpdatePosition(bool inUpdatePosition) { mUpdatePosition = inUpdatePosition; } + /// Global setting to turn on/off skin constraints + bool GetEnableSkinConstraints() const { return mEnableSkinConstraints; } + void SetEnableSkinConstraints(bool inEnableSkinConstraints) { mEnableSkinConstraints = inEnableSkinConstraints; } + + /// Multiplier applied to Skinned::mMaxDistance to allow tightening or loosening of the skin constraints. 0 to hard skin all vertices. + float GetSkinnedMaxDistanceMultiplier() const { return mSkinnedMaxDistanceMultiplier; } + void SetSkinnedMaxDistanceMultiplier(float inSkinnedMaxDistanceMultiplier) { mSkinnedMaxDistanceMultiplier = inSkinnedMaxDistanceMultiplier; } + /// Get local bounding box const AABox & GetLocalBounds() const { return mLocalBounds; } @@ -81,8 +97,12 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties #ifdef JPH_DEBUG_RENDERER /// Draw the state of a soft body void DrawVertices(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; + void DrawVertexVelocities(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; void DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; + void DrawBendConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; void DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; + void DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; + void DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; void DrawPredictedBounds(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const; #endif // JPH_DEBUG_RENDERER @@ -92,11 +112,36 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties /// Restoring state for replay void RestoreState(StateRecorder &inStream); - /// Initialize the update context (used internally by the PhysicsSystem) + /// Skin vertices to supplied joints, information is used by the skinned constraints. + /// @param inCenterOfMassTransform Value of Body::GetCenterOfMassTransform(). + /// @param inJointMatrices The joint matrices must be expressed relative to inCenterOfMassTransform. + /// @param inNumJoints Indicates how large the inJointMatrices array is (used only for validating out of bounds). + /// @param inHardSkinAll Can be used to position all vertices on the skinned vertices and can be used to hard reset the soft body. + /// @param ioTempAllocator Allocator. + void SkinVertices(RMat44Arg inCenterOfMassTransform, const Mat44 *inJointMatrices, uint inNumJoints, bool inHardSkinAll, TempAllocator &ioTempAllocator); + + /// This function allows you to update the soft body immediately without going through the PhysicsSystem. + /// This is useful if the soft body is teleported and needs to 'settle' or it can be used if a the soft body + /// is not added to the PhysicsSystem and needs to be updated manually. One reason for not adding it to the + /// PhyicsSystem is that you might want to update a soft body immediately after updating an animated object + /// that has the soft body attached to it. If the soft body is added to the PhysicsSystem it will be updated + /// by it, so calling this function will effectively update it twice. Note that when you use this function, + /// only the current thread will be used, whereas if you update through the PhysicsSystem, multiple threads may + /// be used. + /// Note that this will bypass any sleep checks. Since the dynamic objects that the soft body touches + /// will not move during this call, there can be simulation artifacts if you call this function multiple times + /// without running the physics simulation step. + void CustomUpdate(float inDeltaTime, Body &ioSoftBody, PhysicsSystem &inSystem); + + //////////////////////////////////////////////////////////// + // FUNCTIONS BELOW THIS LINE ARE FOR INTERNAL USE ONLY + //////////////////////////////////////////////////////////// + + /// Initialize the update context. Not part of the public API. void InitializeUpdateContext(float inDeltaTime, Body &inSoftBody, const PhysicsSystem &inSystem, SoftBodyUpdateContext &ioContext); - /// Do a broad phase check and collect all bodies that can possibly collide with this soft body - void DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem); + /// Do a broad phase check and collect all bodies that can possibly collide with this soft body. Not part of the public API. + void DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface); /// Return code for ParallelUpdate enum class EStatus @@ -106,18 +151,21 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties Done = 1 << 2, ///< All work is done }; - /// Update the soft body, will process a batch of work. Used internally. + /// Update the soft body, will process a batch of work. Not part of the public API. EStatus ParallelUpdate(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings); - /// Update the velocities of all rigid bodies that we collided with. Used internally. - void UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, PhysicsSystem &inSystem); + /// Update the velocities of all rigid bodies that we collided with. Not part of the public API. + void UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, BodyInterface &inBodyInterface); private: + // SoftBodyManifold needs to have access to CollidingShape + friend class SoftBodyManifold; + // Collect information about the colliding bodies struct CollidingShape { /// Get the velocity of a point on this body - Vec3 GetPointVelocity(Vec3Arg inPointRelativeToCOM) const + Vec3 GetPointVelocity(Vec3Arg inPointRelativeToCOM) const { return mLinearVelocity + mAngularVelocity.Cross(inPointRelativeToCOM); } @@ -126,9 +174,11 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties RefConst mShape; ///< Shape of the body we hit BodyID mBodyID; ///< Body ID of the body we hit EMotionType mMotionType; ///< Motion type of the body we hit + bool mIsSensor; ///< If the contact should be treated as a sensor vs body contact (no collision response) float mInvMass; ///< Inverse mass of the body we hit float mFriction; ///< Combined friction of the two bodies float mRestitution; ///< Combined restitution of the two bodies + float mSoftBodyInvMassScale; ///< Scale factor for the inverse mass of the soft body vertices bool mUpdateVelocities; ///< If the linear/angular velocity changed and the body needs to be updated Mat44 mInvInertia; ///< Inverse inertia in local space to the soft body Vec3 mLinearVelocity; ///< Linear velocity of the body in local space to the soft body @@ -137,6 +187,13 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties Vec3 mOriginalAngularVelocity; ///< Angular velocity of the body in local space to the soft body at start }; + // Information about the state of all skinned vertices + struct SkinState + { + Vec3 mPosition = Vec3::sNaN(); + Vec3 mNormal = Vec3::sNaN(); + }; + /// Do a narrow phase check and determine the closest feature that we can collide with void DetermineCollisionPlanes(const SoftBodyUpdateContext &inContext, uint inVertexStart, uint inNumVertices); @@ -146,13 +203,22 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties /// Integrate the positions of all vertices by 1 sub step void IntegratePositions(const SoftBodyUpdateContext &inContext); + /// Enforce all bend constraints + void ApplyBendConstraints(const SoftBodyUpdateContext &inContext); + /// Enforce all volume constraints void ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext); + /// Enforce all skin constraints + void ApplySkinConstraints(const SoftBodyUpdateContext &inContext); + /// Enforce all edge constraints void ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex); - /// Enforce all collision constraints & update all velocities according the the XPBD algorithm + /// Enforce all LRA constraints + void ApplyLRAConstraints(); + + /// Enforce all collision constraints & update all velocities according the XPBD algorithm void ApplyCollisionConstraintsAndUpdateVelocities(const SoftBodyUpdateContext &inContext); /// Update the state of the soft body (position, velocity, bounds) @@ -170,14 +236,19 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties /// Returns 6 times the volume of the soft body float GetVolumeTimesSix() const; + RMat44 mSkinStateTransform = RMat44::sIdentity(); ///< The matrix that transforms mSkinState to world space RefConst mSettings; ///< Configuration of the particles and constraints Array mVertices; ///< Current state of all vertices in the simulation Array mCollidingShapes; ///< List of colliding shapes retrieved during the last update + Array mSkinState; ///< List of skinned positions (1-on-1 with mVertices but only those that are used by the skinning constraints are filled in) AABox mLocalBounds; ///< Bounding box of all vertices AABox mLocalPredictedBounds; ///< Predicted bounding box for all vertices using extrapolation of velocity by last step delta time uint32 mNumIterations; ///< Number of solver iterations - float mPressure; ///< n * R * T, amount of substance * ideal gass constant * absolute temperature, see https://en.wikipedia.org/wiki/Pressure + float mPressure; ///< n * R * T, amount of substance * ideal gas constant * absolute temperature, see https://en.wikipedia.org/wiki/Pressure + float mSkinnedMaxDistanceMultiplier = 1.0f; ///< Multiplier applied to Skinned::mMaxDistance to allow tightening or loosening of the skin constraints bool mUpdatePosition; ///< Update the position of the body while simulating (set to false for something that is attached to the static world) + bool mHasContact = false; ///< True if the soft body has collided with anything in the last update + bool mEnableSkinConstraints = true; ///< If skin constraints are enabled }; JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.cpp b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.cpp index 0e0a9896..391bdef1 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.cpp +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.cpp @@ -29,6 +29,14 @@ uint SoftBodyShape::GetSubShapeIDBits() const return 32 - CountLeadingZeros(n); } +uint32 SoftBodyShape::GetFaceIndex(const SubShapeID &inSubShapeID) const +{ + SubShapeID remainder; + uint32 face_index = inSubShapeID.PopID(GetSubShapeIDBits(), remainder); + JPH_ASSERT(remainder.IsEmpty()); + return face_index; +} + AABox SoftBodyShape::GetLocalBounds() const { return mSoftBodyMotionProperties->GetLocalBounds(); diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.h index 40025cec..605ec80a 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.h +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyShape.h @@ -23,6 +23,9 @@ class JPH_EXPORT SoftBodyShape final : public Shape /// Determine amount of bits needed to encode sub shape id uint GetSubShapeIDBits() const; + /// Convert a sub shape ID back to a face index + uint32 GetFaceIndex(const SubShapeID &inSubShapeID) const; + // See Shape virtual bool MustBeStatic() const override { return false; } virtual Vec3 GetCenterOfMass() const override { return Vec3::sZero(); } diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp index c1c4e8a8..91eae3fb 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp @@ -10,9 +10,17 @@ #include #include #include +#include +#include + +JPH_SUPPRESS_WARNINGS_STD_BEGIN +#include +JPH_SUPPRESS_WARNINGS_STD_END JPH_NAMESPACE_BEGIN +template, class Compare = std::less> using PriorityQueue = std::priority_queue; + JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Vertex) { JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Vertex, mPosition) @@ -33,6 +41,13 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Edge) JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Edge, mCompliance) } +JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::DihedralBend) +{ + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mVertex) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mCompliance) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mInitialAngle) +} + JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Volume) { JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mVertex) @@ -40,14 +55,289 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Volume) JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mCompliance) } +JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::InvBind) +{ + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::InvBind, mJointIndex) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::InvBind, mInvBind) +} + +JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::SkinWeight) +{ + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::SkinWeight, mInvBindIndex) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::SkinWeight, mWeight) +} + +JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Skinned) +{ + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mVertex) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mWeights) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mMaxDistance) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mBackStopDistance) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mBackStopRadius) +} + +JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::LRA) +{ + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::LRA, mVertex) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::LRA, mMaxDistance) +} + JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings) { JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVertices) JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mFaces) JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mEdgeConstraints) JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mEdgeGroupEndIndices) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mDihedralBendConstraints) JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVolumeConstraints) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mSkinnedConstraints) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mInvBindMatrices) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mLRAConstraints) JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mMaterials) + JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVertexRadius) +} + +void SoftBodySharedSettings::CalculateClosestKinematic() +{ + // Check if we already calculated this + if (!mClosestKinematic.empty()) + return; + + // Reserve output size + mClosestKinematic.resize(mVertices.size()); + + // Create a list of connected vertices + Array> connectivity; + connectivity.resize(mVertices.size()); + for (const Edge &e : mEdgeConstraints) + { + connectivity[e.mVertex[0]].push_back(e.mVertex[1]); + connectivity[e.mVertex[1]].push_back(e.mVertex[0]); + } + + // Use Dijkstra's algorithm to find the closest kinematic vertex for each vertex + // See: https://en.wikipedia.org/wiki/Dijkstra's_algorithm + // + // An element in the open list + struct Open + { + // Order so that we get the shortest distance first + bool operator < (const Open &inRHS) const + { + return mDistance > inRHS.mDistance; + } + + uint32 mVertex; + float mDistance; + }; + + // Start with all kinematic elements + PriorityQueue to_visit; + for (uint32 v = 0; v < mVertices.size(); ++v) + if (mVertices[v].mInvMass == 0.0f) + { + mClosestKinematic[v].mVertex = v; + mClosestKinematic[v].mDistance = 0.0f; + to_visit.push({ v, 0.0f }); + } + + // Visit all vertices remembering the closest kinematic vertex and its distance + while (!to_visit.empty()) + { + // Pop element from the open list + Open current = to_visit.top(); + to_visit.pop(); + + // Loop through all of its connected vertices + for (uint32 v : connectivity[current.mVertex]) + { + // Calculate distance from the current vertex to this target vertex and check if it is smaller + float new_distance = current.mDistance + (Vec3(mVertices[v].mPosition) - Vec3(mVertices[current.mVertex].mPosition)).Length(); + if (new_distance < mClosestKinematic[v].mDistance) + { + // Remember new closest vertex + mClosestKinematic[v].mVertex = mClosestKinematic[current.mVertex].mVertex; + mClosestKinematic[v].mDistance = new_distance; + to_visit.push({ v, new_distance }); + } + } + } +} + +void SoftBodySharedSettings::CreateConstraints(const VertexAttributes *inVertexAttributes, uint inVertexAttributesLength, EBendType inBendType, float inAngleTolerance) +{ + struct EdgeHelper + { + uint32 mVertex[2]; + uint32 mEdgeIdx; + }; + + // Create list of all edges + Array edges; + edges.reserve(mFaces.size() * 3); + for (const Face &f : mFaces) + for (int i = 0; i < 3; ++i) + { + uint32 v0 = f.mVertex[i]; + uint32 v1 = f.mVertex[(i + 1) % 3]; + + EdgeHelper e; + e.mVertex[0] = min(v0, v1); + e.mVertex[1] = max(v0, v1); + e.mEdgeIdx = uint32(&f - mFaces.data()) * 3 + i; + edges.push_back(e); + } + + // Sort the edges + QuickSort(edges.begin(), edges.end(), [](const EdgeHelper &inLHS, const EdgeHelper &inRHS) { return inLHS.mVertex[0] < inRHS.mVertex[0] || (inLHS.mVertex[0] == inRHS.mVertex[0] && inLHS.mVertex[1] < inRHS.mVertex[1]); }); + + // Only add edges if one of the vertices is movable + auto add_edge = [this](uint32 inVtx1, uint32 inVtx2, float inCompliance1, float inCompliance2) { + if ((mVertices[inVtx1].mInvMass > 0.0f || mVertices[inVtx2].mInvMass > 0.0f) + && inCompliance1 < FLT_MAX && inCompliance2 < FLT_MAX) + { + Edge temp_edge; + temp_edge.mVertex[0] = inVtx1; + temp_edge.mVertex[1] = inVtx2; + temp_edge.mCompliance = 0.5f * (inCompliance1 + inCompliance2); + temp_edge.mRestLength = (Vec3(mVertices[inVtx2].mPosition) - Vec3(mVertices[inVtx1].mPosition)).Length(); + JPH_ASSERT(temp_edge.mRestLength > 0.0f); + mEdgeConstraints.push_back(temp_edge); + } + }; + + // Helper function to get the attributes of a vertex + auto attr = [inVertexAttributes, inVertexAttributesLength](uint32 inVertex) { + return inVertexAttributes[min(inVertex, inVertexAttributesLength - 1)]; + }; + + // Create the constraints + float sq_sin_tolerance = Square(Sin(inAngleTolerance)); + float sq_cos_tolerance = Square(Cos(inAngleTolerance)); + mEdgeConstraints.clear(); + mEdgeConstraints.reserve(edges.size()); + for (Array::size_type i = 0; i < edges.size(); ++i) + { + const EdgeHelper &e0 = edges[i]; + + // Get attributes for the vertices of the edge + const VertexAttributes &a0 = attr(e0.mVertex[0]); + const VertexAttributes &a1 = attr(e0.mVertex[1]); + + // Flag that indicates if this edge is a shear edge (if 2 triangles form a quad-like shape and this edge is on the diagonal) + bool is_shear = false; + + // Test if there are any shared edges + for (Array::size_type j = i + 1; j < edges.size(); ++j) + { + const EdgeHelper &e1 = edges[j]; + if (e0.mVertex[0] == e1.mVertex[0] && e0.mVertex[1] == e1.mVertex[1]) + { + // Get opposing vertices + const Face &f0 = mFaces[e0.mEdgeIdx / 3]; + const Face &f1 = mFaces[e1.mEdgeIdx / 3]; + uint32 vopposite0 = f0.mVertex[(e0.mEdgeIdx + 2) % 3]; + uint32 vopposite1 = f1.mVertex[(e1.mEdgeIdx + 2) % 3]; + const VertexAttributes &a_opposite0 = attr(vopposite0); + const VertexAttributes &a_opposite1 = attr(vopposite1); + + // Faces should be roughly in a plane + Vec3 n0 = (Vec3(mVertices[f0.mVertex[2]].mPosition) - Vec3(mVertices[f0.mVertex[0]].mPosition)).Cross(Vec3(mVertices[f0.mVertex[1]].mPosition) - Vec3(mVertices[f0.mVertex[0]].mPosition)); + Vec3 n1 = (Vec3(mVertices[f1.mVertex[2]].mPosition) - Vec3(mVertices[f1.mVertex[0]].mPosition)).Cross(Vec3(mVertices[f1.mVertex[1]].mPosition) - Vec3(mVertices[f1.mVertex[0]].mPosition)); + if (Square(n0.Dot(n1)) > sq_cos_tolerance * n0.LengthSq() * n1.LengthSq()) + { + // Faces should approximately form a quad + Vec3 e0_dir = Vec3(mVertices[vopposite0].mPosition) - Vec3(mVertices[e0.mVertex[0]].mPosition); + Vec3 e1_dir = Vec3(mVertices[vopposite1].mPosition) - Vec3(mVertices[e0.mVertex[0]].mPosition); + if (Square(e0_dir.Dot(e1_dir)) < sq_sin_tolerance * e0_dir.LengthSq() * e1_dir.LengthSq()) + { + // Shear constraint + add_edge(vopposite0, vopposite1, a_opposite0.mShearCompliance, a_opposite1.mShearCompliance); + is_shear = true; + } + } + + // Bend constraint + switch (inBendType) + { + case EBendType::None: + // Do nothing + break; + + case EBendType::Distance: + // Create an edge constraint to represent the bend constraint + // Use the bend compliance of the shared edge + if (!is_shear) + add_edge(vopposite0, vopposite1, a0.mBendCompliance, a1.mBendCompliance); + break; + + case EBendType::Dihedral: + // Test if both opposite vertices are free to move + if ((mVertices[vopposite0].mInvMass > 0.0f || mVertices[vopposite1].mInvMass > 0.0f) + && a0.mBendCompliance < FLT_MAX && a1.mBendCompliance < FLT_MAX) + { + // Create a bend constraint + // Use the bend compliance of the shared edge + mDihedralBendConstraints.emplace_back(e0.mVertex[0], e0.mVertex[1], vopposite0, vopposite1, 0.5f * (a0.mBendCompliance + a1.mBendCompliance)); + } + break; + } + } + else + { + // Start iterating from the first non-shared edge + i = j - 1; + break; + } + } + + // Create a edge constraint for the current edge + add_edge(e0.mVertex[0], e0.mVertex[1], is_shear? a0.mShearCompliance : a0.mCompliance, is_shear? a1.mShearCompliance : a1.mCompliance); + } + mEdgeConstraints.shrink_to_fit(); + + // Calculate the initial angle for all bend constraints + CalculateBendConstraintConstants(); + + // Check if any vertices have LRA constraints + bool has_lra_constraints = false; + for (const VertexAttributes *va = inVertexAttributes; va < inVertexAttributes + inVertexAttributesLength; ++va) + if (va->mLRAType != ELRAType::None) + { + has_lra_constraints = true; + break; + } + if (has_lra_constraints) + { + // Ensure we have calculated the closest kinematic vertex for each vertex + CalculateClosestKinematic(); + + // Find non-kinematic vertices + for (uint32 v = 0; v < (uint32)mVertices.size(); ++v) + if (mVertices[v].mInvMass > 0.0f) + { + // Check if a closest vertex was found + uint32 closest = mClosestKinematic[v].mVertex; + if (closest != 0xffffffff) + { + // Check which LRA constraint to create + const VertexAttributes &va = attr(v); + switch (va.mLRAType) + { + case ELRAType::None: + break; + + case ELRAType::EuclideanDistance: + mLRAConstraints.emplace_back(closest, v, va.mLRAMaxDistanceMultiplier * (Vec3(mVertices[closest].mPosition) - Vec3(mVertices[v].mPosition)).Length()); + break; + + case ELRAType::GeodesicDistance: + mLRAConstraints.emplace_back(closest, v, va.mLRAMaxDistanceMultiplier * mClosestKinematic[v].mDistance); + break; + } + } + } + } } void SoftBodySharedSettings::CalculateEdgeLengths() @@ -59,6 +349,54 @@ void SoftBodySharedSettings::CalculateEdgeLengths() } } +void SoftBodySharedSettings::CalculateLRALengths(float inMaxDistanceMultiplier) +{ + for (LRA &l : mLRAConstraints) + { + l.mMaxDistance = inMaxDistanceMultiplier * (Vec3(mVertices[l.mVertex[1]].mPosition) - Vec3(mVertices[l.mVertex[0]].mPosition)).Length(); + JPH_ASSERT(l.mMaxDistance > 0.0f); + } +} + +void SoftBodySharedSettings::CalculateBendConstraintConstants() +{ + for (DihedralBend &b : mDihedralBendConstraints) + { + // Get positions + Vec3 x0 = Vec3(mVertices[b.mVertex[0]].mPosition); + Vec3 x1 = Vec3(mVertices[b.mVertex[1]].mPosition); + Vec3 x2 = Vec3(mVertices[b.mVertex[2]].mPosition); + Vec3 x3 = Vec3(mVertices[b.mVertex[3]].mPosition); + + /* + x2 + e1/ \e3 + / \ + x0----x1 + \ e0 / + e2\ /e4 + x3 + */ + + // Calculate edges + Vec3 e0 = x1 - x0; + Vec3 e1 = x2 - x0; + Vec3 e2 = x3 - x0; + + // Normals of both triangles + Vec3 n1 = e0.Cross(e1); + Vec3 n2 = e2.Cross(e0); + float denom = sqrt(n1.LengthSq() * n2.LengthSq()); + if (denom < 1.0e-12f) + b.mInitialAngle = 0.0f; + else + { + float sign = Sign(n2.Cross(n1).Dot(e0)); + b.mInitialAngle = sign * ACos(n1.Dot(n2) / denom); + } + } +} + void SoftBodySharedSettings::CalculateVolumeConstraintVolumes() { for (Volume &v : mVolumeConstraints) @@ -76,6 +414,54 @@ void SoftBodySharedSettings::CalculateVolumeConstraintVolumes() } } +void SoftBodySharedSettings::CalculateSkinnedConstraintNormals() +{ + // Clear any previous results + mSkinnedConstraintNormals.clear(); + + // If there are no skinned constraints, we're done + if (mSkinnedConstraints.empty()) + return; + + // First collect all vertices that are skinned + UnorderedSet skinned_vertices; + skinned_vertices.reserve(mSkinnedConstraints.size()); + for (const Skinned &s : mSkinnedConstraints) + skinned_vertices.insert(s.mVertex); + + // Now collect all faces that connect only to skinned vertices + UnorderedMap> connected_faces; + connected_faces.reserve(mVertices.size()); + for (const Face &f : mFaces) + { + // Must connect to only skinned vertices + bool valid = true; + for (uint32 v : f.mVertex) + valid &= skinned_vertices.find(v) != skinned_vertices.end(); + if (!valid) + continue; + + // Store faces that connect to vertices + for (uint32 v : f.mVertex) + connected_faces[v].insert(uint32(&f - mFaces.data())); + } + + // Populate the list of connecting faces per skinned vertex + mSkinnedConstraintNormals.reserve(mFaces.size()); + for (Skinned &s : mSkinnedConstraints) + { + uint32 start = uint32(mSkinnedConstraintNormals.size()); + JPH_ASSERT((start >> 24) == 0); + const UnorderedSet &faces = connected_faces[s.mVertex]; + uint32 num = uint32(faces.size()); + JPH_ASSERT(num < 256); + mSkinnedConstraintNormals.insert(mSkinnedConstraintNormals.end(), faces.begin(), faces.end()); + QuickSort(mSkinnedConstraintNormals.begin() + start, mSkinnedConstraintNormals.begin() + start + num); + s.mNormalInfo = start + (num << 24); + } + mSkinnedConstraintNormals.shrink_to_fit(); +} + void SoftBodySharedSettings::Optimize(OptimizationResults &outResults) { const uint cMaxNumGroups = 32; @@ -105,14 +491,25 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults) edge_groups[i].clear(); } - // Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges). - // Note we could also re-order the vertices but that would be much more of a burden to the end user + // Make sure we know the closest kinematic vertex so we can sort + CalculateClosestKinematic(); + + // Sort the edge constraints for (Array &group : edge_groups) QuickSort(group.begin(), group.end(), [this](uint inLHS, uint inRHS) { const Edge &e1 = mEdgeConstraints[inLHS]; const Edge &e2 = mEdgeConstraints[inRHS]; - return min(e1.mVertex[0], e1.mVertex[1]) < min(e2.mVertex[0], e2.mVertex[1]); + + // First sort so that the edge with the smallest distance to a kinematic vertex comes first + float d1 = min(mClosestKinematic[e1.mVertex[0]].mDistance, mClosestKinematic[e1.mVertex[1]].mDistance); + float d2 = min(mClosestKinematic[e2.mVertex[0]].mDistance, mClosestKinematic[e2.mVertex[1]].mDistance); + if (d1 != d2) + return d1 < d2; + + // Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges). + // Note we could also re-order the vertices but that would be much more of a burden to the end user + return e1.GetMinVertexIndex() < e2.GetMinVertexIndex(); }); // Assign the edges to groups and reorder them @@ -133,6 +530,58 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults) // If there is no non-parallel group then add an empty group at the end if (edge_groups[cNonParallelGroupIdx].empty()) mEdgeGroupEndIndices.push_back((uint)mEdgeConstraints.size()); + + // Sort the bend constraints + outResults.mDihedralBendRemap.resize(mDihedralBendConstraints.size()); + for (int i = 0; i < (int)mDihedralBendConstraints.size(); ++i) + outResults.mDihedralBendRemap[i] = i; + QuickSort(outResults.mDihedralBendRemap.begin(), outResults.mDihedralBendRemap.end(), [this](uint inLHS, uint inRHS) + { + const DihedralBend &b1 = mDihedralBendConstraints[inLHS]; + const DihedralBend &b2 = mDihedralBendConstraints[inRHS]; + + // First sort so that the constraint with the smallest distance to a kinematic vertex comes first + float d1 = min( + min(mClosestKinematic[b1.mVertex[0]].mDistance, mClosestKinematic[b1.mVertex[1]].mDistance), + min(mClosestKinematic[b1.mVertex[2]].mDistance, mClosestKinematic[b1.mVertex[3]].mDistance)); + float d2 = min( + min(mClosestKinematic[b2.mVertex[0]].mDistance, mClosestKinematic[b2.mVertex[1]].mDistance), + min(mClosestKinematic[b2.mVertex[2]].mDistance, mClosestKinematic[b2.mVertex[3]].mDistance)); + if (d1 != d2) + return d1 < d2; + + // Order constraints so that the ones with the smallest index go first + return b1.GetMinVertexIndex() < b2.GetMinVertexIndex(); + }); + + // Reorder the bend constraints + Array temp_bends; + temp_bends.swap(mDihedralBendConstraints); + mDihedralBendConstraints.reserve(temp_bends.size()); + for (uint idx : outResults.mDihedralBendRemap) + mDihedralBendConstraints.push_back(temp_bends[idx]); + + // Free closest kinematic buffer + mClosestKinematic.clear(); + mClosestKinematic.shrink_to_fit(); +} + +Ref SoftBodySharedSettings::Clone() const +{ + Ref clone = new SoftBodySharedSettings; + clone->mVertices = mVertices; + clone->mFaces = mFaces; + clone->mEdgeConstraints = mEdgeConstraints; + clone->mEdgeGroupEndIndices = mEdgeGroupEndIndices; + clone->mDihedralBendConstraints = mDihedralBendConstraints; + clone->mVolumeConstraints = mVolumeConstraints; + clone->mSkinnedConstraints = mSkinnedConstraints; + clone->mSkinnedConstraintNormals = mSkinnedConstraintNormals; + clone->mInvBindMatrices = mInvBindMatrices; + clone->mLRAConstraints = mLRAConstraints; + clone->mMaterials = mMaterials; + clone->mVertexRadius = mVertexRadius; + return clone; } void SoftBodySharedSettings::SaveBinaryState(StreamOut &inStream) const @@ -141,7 +590,18 @@ void SoftBodySharedSettings::SaveBinaryState(StreamOut &inStream) const inStream.Write(mFaces); inStream.Write(mEdgeConstraints); inStream.Write(mEdgeGroupEndIndices); + inStream.Write(mDihedralBendConstraints); inStream.Write(mVolumeConstraints); + inStream.Write(mSkinnedConstraints); + inStream.Write(mSkinnedConstraintNormals); + inStream.Write(mLRAConstraints); + inStream.Write(mVertexRadius); + + // Can't write mInvBindMatrices directly because the class contains padding + inStream.Write(mInvBindMatrices, [](const InvBind &inElement, StreamOut &inS) { + inS.Write(inElement.mJointIndex); + inS.Write(inElement.mInvBind); + }); } void SoftBodySharedSettings::RestoreBinaryState(StreamIn &inStream) @@ -150,7 +610,17 @@ void SoftBodySharedSettings::RestoreBinaryState(StreamIn &inStream) inStream.Read(mFaces); inStream.Read(mEdgeConstraints); inStream.Read(mEdgeGroupEndIndices); + inStream.Read(mDihedralBendConstraints); inStream.Read(mVolumeConstraints); + inStream.Read(mSkinnedConstraints); + inStream.Read(mSkinnedConstraintNormals); + inStream.Read(mLRAConstraints); + inStream.Read(mVertexRadius); + + inStream.Read(mInvBindMatrices, [](StreamIn &inS, InvBind &outElement) { + inS.Read(outElement.mJointIndex); + inS.Read(outElement.mInvBind); + }); } void SoftBodySharedSettings::SaveWithMaterials(StreamOut &inStream, SharedSettingsToIDMap &ioSettingsMap, MaterialToIDMap &ioMaterialMap) const diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.h index b4f2240c..250bd689 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.h +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodySharedSettings.h @@ -17,17 +17,67 @@ class JPH_EXPORT SoftBodySharedSettings : public RefTarget mEdgeRemap; ///< Maps old edge index to new edge index + Array mDihedralBendRemap; ///< Maps old dihedral bend index to new dihedral bend index }; /// Optimize the soft body settings for simulation. This will reorder constraints so they can be executed in parallel. @@ -36,6 +86,9 @@ class JPH_EXPORT SoftBodySharedSettings : public RefTarget Clone() const; + /// Saves the state of this object in binary form to inStream. Doesn't store the material list. void SaveBinaryState(StreamOut &inStream) const; @@ -94,11 +147,48 @@ class JPH_EXPORT SoftBodySharedSettings : public RefTarget 0.0f) + for (SkinWeight &w : mWeights) + w.mWeight /= total; + } + + uint32 mVertex = 0; ///< Index in mVertices which indicates which vertex is being skinned + SkinWeight mWeights[4]; ///< Skin weights, the bind pose of the vertex is assumed to be stored in Vertex::mPosition. The first weight that is zero indicates the end of the list. Weights should add up to 1. + float mMaxDistance = FLT_MAX; ///< Maximum distance that this vertex can reach from the skinned vertex, disabled when FLT_MAX. 0 when you want to hard skin the vertex to the skinned vertex. + float mBackStopDistance = FLT_MAX; ///< Disabled if mBackStopDistance >= mMaxDistance. The faces surrounding mVertex determine an average normal. mBackStopDistance behind the vertex in the opposite direction of this normal, the back stop sphere starts. The simulated vertex will be pushed out of this sphere and it can be used to approximate the volume of she skinned mesh behind the skinned vertex. + float mBackStopRadius = 40.0f; ///< Radius of the backstop sphere. By default this is a fairly large radius so the sphere approximates a plane. + uint32 mNormalInfo = 0; ///< Information needed to calculate the normal of this vertex, lowest 24 bit is start index in mSkinnedConstraintNormals, highest 8 bit is number of faces (generated by CalculateSkinnedConstraintNormals()) + }; + + /// A long range attachment constraint, this is a constraint that sets a max distance between a kinematic vertex and a dynamic vertex + /// See: "Long Range Attachments - A Method to Simulate Inextensible Clothing in Computer Games", Tae-Yong Kim, Nuttapong Chentanez and Matthias Mueller-Fischer + class JPH_EXPORT LRA + { + JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, LRA) + + public: + /// Constructor + LRA() = default; + LRA(uint32 inVertex1, uint32 inVertex2, float inMaxDistance) : mVertex { inVertex1, inVertex2 }, mMaxDistance(inMaxDistance) { } + + /// Return the lowest vertex index of this constraint + uint32 GetMinVertexIndex() const { return min(mVertex[0], mVertex[1]); } + + uint32 mVertex[2]; ///< The vertices that are connected. The first vertex should be kinematic, the 2nd dynamic. + float mMaxDistance = 0.0f; ///< The maximum distance between the vertices + }; + /// Add a face to this soft body void AddFace(const Face &inFace) { JPH_ASSERT(!inFace.IsDegenerate()); mFaces.push_back(inFace); } - /// Get the size of an edge group (edge groups can run in parallel) - uint GetEdgeGroupSize(uint inGroupIdx) const { return inGroupIdx == 0? mEdgeGroupEndIndices[0] : mEdgeGroupEndIndices[inGroupIdx] - mEdgeGroupEndIndices[inGroupIdx - 1]; } - Array mVertices; ///< The list of vertices or particles of the body Array mFaces; ///< The list of faces of the body Array mEdgeConstraints; ///< The list of edges or springs of the body - Array mEdgeGroupEndIndices; ///< The start index of each group of edges that can be solved in parallel + Array mDihedralBendConstraints; ///< The list of dihedral bend constraints of the body Array mVolumeConstraints; ///< The list of volume constraints of the body that keep the volume of tetrahedra in the soft body constant + Array mSkinnedConstraints; ///< The list of vertices that are constrained to a skinned vertex + Array mInvBindMatrices; ///< The list of inverse bind matrices for skinning vertices + Array mLRAConstraints; ///< The list of long range attachment constraints PhysicsMaterialList mMaterials { PhysicsMaterial::sDefault }; ///< The materials of the faces of the body, referenced by Face::mMaterialIndex + float mVertexRadius = 0.0f; ///< How big the particles are, can be used to push the vertices a little bit away from the surface of other bodies to prevent z-fighting + +private: + friend class SoftBodyMotionProperties; + + /// Tracks the closest kinematic vertex + struct ClosestKinematic + { + uint32 mVertex = 0xffffffff; ///< Vertex index of closest kinematic vertex + float mDistance = FLT_MAX; ///< Distance to the closest kinematic vertex + }; + + /// Calculate the closest kinematic vertex array + void CalculateClosestKinematic(); + + /// Get the size of an edge group (edge groups can run in parallel) + uint GetEdgeGroupSize(uint inGroupIdx) const { return inGroupIdx == 0? mEdgeGroupEndIndices[0] : mEdgeGroupEndIndices[inGroupIdx] - mEdgeGroupEndIndices[inGroupIdx - 1]; } + + Array mClosestKinematic; ///< The closest kinematic vertex to each vertex in mVertices + Array mEdgeGroupEndIndices; ///< The start index of each group of edges that can be solved in parallel, calculated by Optimize() + Array mSkinnedConstraintNormals; ///< A list of indices in the mFaces array used by mSkinnedConstraints, calculated by CalculateSkinnedConstraintNormals() }; JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h index 9e1e19ca..cd50aea0 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h @@ -11,6 +11,7 @@ JPH_NAMESPACE_BEGIN class Body; class SoftBodyMotionProperties; +class SoftBodyContactListener; /// Temporary data used by the update of a soft body class SoftBodyUpdateContext : public NonCopyable @@ -22,6 +23,7 @@ class SoftBodyUpdateContext : public NonCopyable // Input Body * mBody; ///< Body that is being updated SoftBodyMotionProperties * mMotionProperties; ///< Motion properties of that body + SoftBodyContactListener * mContactListener; ///< Contact listener to fire callbacks to RMat44 mCenterOfMassTransform; ///< Transform of the body relative to the soft body Vec3 mGravity; ///< Gravity vector in local space of the soft body Vec3 mDisplacementDueToGravity; ///< Displacement of the center of mass due to gravity in the current time step diff --git a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyVertex.h b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyVertex.h index 55bac6b0..34e7bb32 100644 --- a/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyVertex.h +++ b/Dependencies/Jolt/Jolt/Physics/SoftBody/SoftBodyVertex.h @@ -20,6 +20,7 @@ class SoftBodyVertex Vec3 mVelocity; ///< Velocity, relative to the center of mass of the soft body Plane mCollisionPlane; ///< Nearest collision plane, relative to the center of mass of the soft body int mCollidingShapeIndex; ///< Index in the colliding shapes list of the body we may collide with + bool mHasContact; ///< True if the vertex has collided with anything in the last update float mLargestPenetration; ///< Used while finding the collision plane, stores the largest penetration found so far float mInvMass; ///< Inverse mass (1 / mass) }; diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.cpp b/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.cpp index a4a8c5c6..533d4cfc 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.cpp @@ -171,7 +171,8 @@ void MotorcycleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysic float steer_angle = steer_strength * w->GetSettings()->mMaxSteerAngle; // Clamp to max steering angle - if (velocity_sq > 1.0e-6f && cos_caster_angle > 1.0e-6f) + if (mEnableLeanSteeringLimit + && velocity_sq > 1.0e-6f && cos_caster_angle > 1.0e-6f) { float max_steer_angle = ASin(max_steer_angle_factor / (velocity_sq * cos_caster_angle)); steer_angle = min(steer_angle, max_steer_angle); @@ -259,14 +260,14 @@ bool MotorcycleController::SolveLongitudinalAndLateralConstraints(float inDeltaT return impulse; } -void MotorcycleController::SaveState(StateRecorder& inStream) const +void MotorcycleController::SaveState(StateRecorder &inStream) const { WheeledVehicleController::SaveState(inStream); inStream.Write(mTargetLean); } -void MotorcycleController::RestoreState(StateRecorder& inStream) +void MotorcycleController::RestoreState(StateRecorder &inStream) { WheeledVehicleController::RestoreState(inStream); diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.h index f0f1d5ae..59ee17cd 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/MotorcycleController.h @@ -58,18 +58,44 @@ class JPH_EXPORT MotorcycleController : public WheeledVehicleController /// Check if the lean spring is enabled. bool IsLeanControllerEnabled() const { return mEnableLeanController; } + /// Enable or disable the lean steering limit. When enabled (default) the steering angle is limited based on the vehicle speed to prevent steering that would cause an inertial force that causes the motorcycle to topple over. + void EnableLeanSteeringLimit(bool inEnable) { mEnableLeanSteeringLimit = inEnable; } + bool IsLeanSteeringLimitEnabled() const { return mEnableLeanSteeringLimit; } + + /// Spring constant for the lean spring + void SetLeanSpringConstant(float inConstant) { mLeanSpringConstant = inConstant; } + float GetLeanSpringConstant() const { return mLeanSpringConstant; } + + /// Spring damping constant for the lean spring + void SetLeanSpringDamping(float inDamping) { mLeanSpringDamping = inDamping; } + float GetLeanSpringDamping() const { return mLeanSpringDamping; } + + /// The lean spring applies an additional force equal to this coefficient * Integral(delta angle, 0, t), this effectively makes the lean spring a PID controller + void SetLeanSpringIntegrationCoefficient(float inCoefficient) { mLeanSpringIntegrationCoefficient = inCoefficient; } + float GetLeanSpringIntegrationCoefficient() const { return mLeanSpringIntegrationCoefficient; } + + /// How much to decay the angle integral when the wheels are not touching the floor: new_value = e^(-decay * t) * initial_value + void SetLeanSpringIntegrationCoefficientDecay(float inDecay) { mLeanSpringIntegrationCoefficientDecay = inDecay; } + float GetLeanSpringIntegrationCoefficientDecay() const { return mLeanSpringIntegrationCoefficientDecay; } + + /// How much to smooth the lean angle (0 = no smoothing, 1 = lean angle never changes) + /// Note that this is frame rate dependent because the formula is: smoothing_factor * previous + (1 - smoothing_factor) * current + void SetLeanSmoothingFactor(float inFactor) { mLeanSmoothingFactor = inFactor; } + float GetLeanSmoothingFactor() const { return mLeanSmoothingFactor; } + protected: // See: VehicleController virtual void PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem) override; virtual bool SolveLongitudinalAndLateralConstraints(float inDeltaTime) override; - virtual void SaveState(StateRecorder& inStream) const override; - virtual void RestoreState(StateRecorder& inStream) override; + virtual void SaveState(StateRecorder &inStream) const override; + virtual void RestoreState(StateRecorder &inStream) override; #ifdef JPH_DEBUG_RENDERER virtual void Draw(DebugRenderer *inRenderer) const override; #endif // JPH_DEBUG_RENDERER // Configuration properties bool mEnableLeanController = true; + bool mEnableLeanSteeringLimit = true; float mMaxLeanAngle; float mLeanSpringConstant; float mLeanSpringDamping; diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.cpp b/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.cpp index 340340ab..c941a829 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.cpp @@ -57,7 +57,7 @@ void WheelTV::CalculateAngularVelocity(const VehicleConstraint &inConstraint) mAngularVelocity = track.mAngularVelocity * wheels[track.mDrivenWheel]->GetSettings()->mRadius / settings->mRadius; } -void WheelTV::Update(float inDeltaTime, const VehicleConstraint &inConstraint) +void WheelTV::Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint) { CalculateAngularVelocity(inConstraint); @@ -72,8 +72,9 @@ void WheelTV::Update(float inDeltaTime, const VehicleConstraint &inConstraint) // Friction at the point of this wheel between track and floor const WheelSettingsTV *settings = GetSettings(); VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction(); - mCombinedLongitudinalFriction = combine_friction(settings->mLongitudinalFriction, *mContactBody, mContactSubShapeID); - mCombinedLateralFriction = combine_friction(settings->mLateralFriction, *mContactBody, mContactSubShapeID); + mCombinedLongitudinalFriction = settings->mLongitudinalFriction; + mCombinedLateralFriction = settings->mLateralFriction; + combine_friction(inWheelIndex, mCombinedLongitudinalFriction, mCombinedLateralFriction, *mContactBody, mContactSubShapeID); } else { @@ -127,6 +128,7 @@ TrackedVehicleController::TrackedVehicleController(const TrackedVehicleControlle static_cast(mEngine) = inSettings.mEngine; JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f); JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM); + mEngine.SetCurrentRPM(mEngine.mMinRPM); // Copy transmission settings static_cast(mTransmission) = inSettings.mTransmission; @@ -206,10 +208,10 @@ void TrackedVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inP Wheels &wheels = mConstraint.GetWheels(); // Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again) - for (Wheel *w_base : wheels) + for (uint wheel_index = 0, num_wheels = (uint)wheels.size(); wheel_index < num_wheels; ++wheel_index) { - WheelTV *w = static_cast(w_base); - w->Update(inDeltaTime, mConstraint); + WheelTV *w = static_cast(wheels[wheel_index]); + w->Update(wheel_index, inDeltaTime, mConstraint); } // First calculate engine speed based on speed of all wheels @@ -387,10 +389,10 @@ bool TrackedVehicleController::SolveLongitudinalAndLateralConstraints(float inDe float linear_impulse = (track.mAngularVelocity - desired_angular_velocity) * track.mInertia / settings->mRadius; // Limit the impulse by max track friction - min_longitudinal_impulse = max_longitudinal_impulse = w->GetLongitudinalLambda() + Sign(linear_impulse) * min(abs(linear_impulse), max_longitudinal_friction_impulse); + float prev_lambda = w->GetLongitudinalLambda(); + min_longitudinal_impulse = max_longitudinal_impulse = Clamp(prev_lambda + linear_impulse, -max_longitudinal_friction_impulse, max_longitudinal_friction_impulse); // Longitudinal impulse - float prev_lambda = w->GetLongitudinalLambda(); impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse); // Update the angular velocity of the track according to the lambda that was applied @@ -479,6 +481,11 @@ void TrackedVehicleController::Draw(DebugRenderer *inRenderer) const inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_right, Color::sBlue); inRenderer->DrawLine(wheel_pos, wheel_pos + steering_axis, Color::sYellow); + // Draw wheel + RMat44 wheel_transform(Vec4(wheel_up, 0.0f), Vec4(wheel_right, 0.0f), Vec4(wheel_forward, 0.0f), wheel_pos); + wheel_transform.SetRotation(wheel_transform.GetRotation() * Mat44::sRotationY(-w->GetRotationAngle())); + inRenderer->DrawCylinder(wheel_transform, settings->mWidth * 0.5f, settings->mRadius, w->GetSuspensionLength() <= settings->mSuspensionMinLength? Color::sRed : Color::sGreen, DebugRenderer::ECastShadow::Off, DebugRenderer::EDrawMode::Wireframe); + if (w->HasContact()) { // Draw contact diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.h index 808519c4..f5dff527 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/TrackedVehicleController.h @@ -44,7 +44,7 @@ class JPH_EXPORT WheelTV : public Wheel void CalculateAngularVelocity(const VehicleConstraint &inConstraint); /// Update the wheel rotation based on the current angular velocity - void Update(float inDeltaTime, const VehicleConstraint &inConstraint); + void Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint); int mTrackIndex = -1; ///< Index in mTracks to which this wheel is attached (calculated on initialization) float mCombinedLongitudinalFriction = 0.0f; ///< Combined friction coefficient in longitudinal direction (combines terrain and track) @@ -151,7 +151,7 @@ class JPH_EXPORT TrackedVehicleController : public VehicleController float mRightRatio = 1.0f; ///< Value between -1 and 1 indicating an extra multiplier to the rotation rate of the right track (used for steering) float mBrakeInput = 0.0f; ///< Value between 0 and 1 indicating how strong the brake pedal is pressed - // Simluation information + // Simulation information VehicleEngine mEngine; ///< Engine state of the vehicle VehicleTransmission mTransmission; ///< Transmission state of the vehicle VehicleTracks mTracks; ///< Tracks of the vehicle diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp index 3392f0c0..56246835 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp @@ -102,6 +102,26 @@ bool VehicleCollisionTesterRay::Collide(PhysicsSystem &inPhysicsSystem, const Ve return true; } +void VehicleCollisionTesterRay::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const +{ + // Recalculate the contact points assuming the contact point is on an infinite plane + const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings(); + float d_dot_n = inDirection.Dot(ioContactNormal); + if (d_dot_n < -1.0e-6f) + { + // Reproject the contact position using the suspension ray and the plane formed by the contact position and normal + ioContactPosition = inOrigin + Vec3(ioContactPosition - inOrigin).Dot(ioContactNormal) / d_dot_n * inDirection; + + // The suspension length is simply the distance between the contact position and the suspension origin excluding the wheel radius + ioSuspensionLength = Clamp(Vec3(ioContactPosition - inOrigin).Dot(inDirection) - wheel_settings->mRadius, 0.0f, wheel_settings->mSuspensionMaxLength); + } + else + { + // If the normal is pointing away we assume there's no collision anymore + ioSuspensionLength = wheel_settings->mSuspensionMaxLength; + } +} + bool VehicleCollisionTesterCastSphere::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const { const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer); @@ -195,6 +215,29 @@ bool VehicleCollisionTesterCastSphere::Collide(PhysicsSystem &inPhysicsSystem, c return true; } +void VehicleCollisionTesterCastSphere::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const +{ + // Recalculate the contact points assuming the contact point is on an infinite plane + const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings(); + float d_dot_n = inDirection.Dot(ioContactNormal); + if (d_dot_n < -1.0e-6f) + { + // Reproject the contact position using the suspension cast sphere and the plane formed by the contact position and normal + // This solves x = inOrigin + fraction * inDirection and (x - ioContactPosition) . ioContactNormal = mRadius for fraction + float oc_dot_n = Vec3(ioContactPosition - inOrigin).Dot(ioContactNormal); + float fraction = (mRadius + oc_dot_n) / d_dot_n; + ioContactPosition = inOrigin + fraction * inDirection - mRadius * ioContactNormal; + + // Calculate the new suspension length in the same way as the cast sphere normally does + ioSuspensionLength = Clamp(fraction + mRadius - wheel_settings->mRadius, 0.0f, wheel_settings->mSuspensionMaxLength); + } + else + { + // If the normal is pointing away we assume there's no collision anymore + ioSuspensionLength = wheel_settings->mSuspensionMaxLength; + } +} + bool VehicleCollisionTesterCastCylinder::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const { const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer); @@ -285,4 +328,49 @@ bool VehicleCollisionTesterCastCylinder::Collide(PhysicsSystem &inPhysicsSystem, return true; } +void VehicleCollisionTesterCastCylinder::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const +{ + // Recalculate the contact points assuming the contact point is on an infinite plane + const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings(); + float d_dot_n = inDirection.Dot(ioContactNormal); + if (d_dot_n < -1.0e-6f) + { + // Wheel size + float half_width = 0.5f * wheel_settings->mWidth; + float radius = wheel_settings->mRadius; + + // Get the inverse local space contact normal for a cylinder pointing along Y + RMat44 wheel_transform = inVehicleConstraint.GetWheelWorldTransform(inWheelIndex, Vec3::sAxisY(), Vec3::sAxisX()); + Vec3 inverse_local_normal = -wheel_transform.Multiply3x3Transposed(ioContactNormal); + + // Get the support point of this normal in local space of the cylinder + // See CylinderShape::Cylinder::GetSupport + float x = inverse_local_normal.GetX(), y = inverse_local_normal.GetY(), z = inverse_local_normal.GetZ(); + float o = sqrt(Square(x) + Square(z)); + Vec3 support_point; + if (o > 0.0f) + support_point = Vec3((radius * x) / o, Sign(y) * half_width, (radius * z) / o); + else + support_point = Vec3(0, Sign(y) * half_width, 0); + + // Rotate back to world space + support_point = wheel_transform.Multiply3x3(support_point); + + // Now we can use inOrigin + support_point as the start of a ray of our suspension to the contact plane + // as know that it is the first point on the wheel that will hit the plane + RVec3 origin = inOrigin + support_point; + + // Calculate contact position and suspension length, the is the same as VehicleCollisionTesterRay + // but we don't need to take the radius into account anymore + Vec3 oc(ioContactPosition - origin); + ioContactPosition = origin + oc.Dot(ioContactNormal) / d_dot_n * inDirection; + ioSuspensionLength = Clamp(oc.Dot(inDirection), 0.0f, wheel_settings->mSuspensionMaxLength); + } + else + { + // If the normal is pointing away we assume there's no collision anymore + ioSuspensionLength = wheel_settings->mSuspensionMaxLength; + } +} + JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.h index b8956109..7c222756 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleCollisionTester.h @@ -59,6 +59,20 @@ class JPH_EXPORT VehicleCollisionTester : public RefTarget= 0.0f); } - // Construct our controler class + // Construct our controller class mController = inSettings.mController->ConstructController(*this); // Create wheels mWheels.resize(inSettings.mWheels.size()); for (uint i = 0; i < mWheels.size(); ++i) mWheels[i] = mController->ConstructWheel(*inSettings.mWheels[i]); + + // Use the body ID as a seed for the step counter so that not all vehicles will update at the same time + mCurrentStep = uint32(Hash64(inVehicleBody.GetID().GetIndex())); } VehicleConstraint::~VehicleConstraint() @@ -171,6 +174,9 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem // Calculate if this constraint is active by checking if our main vehicle body is active or any of the bodies we touch are active mIsActive = mBody->IsActive(); + // Test how often we need to update the wheels + uint num_steps_between_collisions = mIsActive? mNumStepsBetweenCollisionTestActive : mNumStepsBetweenCollisionTestInactive; + RMat44 body_transform = mBody->GetWorldTransform(); // Test collision for wheels @@ -179,20 +185,51 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem Wheel *w = mWheels[wheel_index]; const WheelSettings *settings = w->mSettings; - // Reset contact - w->mContactBodyID = BodyID(); - w->mContactBody = nullptr; - w->mContactSubShapeID = SubShapeID(); - w->mSuspensionLength = settings->mSuspensionMaxLength; - - // Test collision to find the floor + // Calculate suspension origin and direction RVec3 ws_origin = body_transform * settings->mPosition; Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection); - if (mVehicleCollisionTester->Collide(inPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength)) + + // Test if we need to update this wheel + if (num_steps_between_collisions == 0 + || (mCurrentStep + wheel_index) % num_steps_between_collisions != 0) + { + // Simplified wheel contact test + if (!w->mContactBodyID.IsInvalid()) + { + // Test if the body is still valid + w->mContactBody = inPhysicsSystem.GetBodyLockInterfaceNoLock().TryGetBody(w->mContactBodyID); + if (w->mContactBody == nullptr) + { + // It's not, forget the contact + w->mContactBodyID = BodyID(); + w->mContactSubShapeID = SubShapeID(); + w->mSuspensionLength = settings->mSuspensionMaxLength; + } + else + { + // Extrapolate the wheel contact properties + mVehicleCollisionTester->PredictContactProperties(inPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength); + } + } + } + else { - // Store ID (pointer is not valid outside of the simulation step) - w->mContactBodyID = w->mContactBody->GetID(); + // Full wheel contact test, start by resetting the contact data + w->mContactBodyID = BodyID(); + w->mContactBody = nullptr; + w->mContactSubShapeID = SubShapeID(); + w->mSuspensionLength = settings->mSuspensionMaxLength; + + // Test collision to find the floor + if (mVehicleCollisionTester->Collide(inPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength)) + { + // Store ID (pointer is not valid outside of the simulation step) + w->mContactBodyID = w->mContactBody->GetID(); + } + } + if (w->mContactBody != nullptr) + { // Store contact velocity, cache this as the contact body may be removed w->mContactPointVelocity = w->mContactBody->GetPointVelocity(w->mContactPosition); @@ -266,6 +303,9 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem } if (mBody->GetAllowSleeping() != allow_sleep) mBody->SetAllowSleeping(allow_sleep); + + // Increment step counter + ++mCurrentStep; } void VehicleConstraint::BuildIslands(uint32 inConstraintIndex, IslandBuilder &ioBuilder, BodyManager &inBodyManager) @@ -466,6 +506,19 @@ void VehicleConstraint::SetupVelocityConstraint(float inDeltaTime) CalculatePitchRollConstraintProperties(body_transform); } +void VehicleConstraint::ResetWarmStart() +{ + for (Wheel *w : mWheels) + { + w->mSuspensionPart.Deactivate(); + w->mSuspensionMaxUpPart.Deactivate(); + w->mLongitudinalPart.Deactivate(); + w->mLateralPart.Deactivate(); + } + + mPitchRollPart.Deactivate(); +} + void VehicleConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { for (Wheel *w : mWheels) @@ -575,8 +628,10 @@ void VehicleConstraint::SaveState(StateRecorder &inStream) const inStream.Write(w->mAngularVelocity); inStream.Write(w->mAngle); inStream.Write(w->mContactBodyID); // Used by MotorcycleController::PreCollide + inStream.Write(w->mContactPosition); // Used by VehicleCollisionTester::PredictContactProperties inStream.Write(w->mContactNormal); // Used by MotorcycleController::PreCollide inStream.Write(w->mContactLateral); // Used by MotorcycleController::PreCollide + inStream.Write(w->mSuspensionLength); // Used by VehicleCollisionTester::PredictContactProperties w->mSuspensionPart.SaveState(inStream); w->mSuspensionMaxUpPart.SaveState(inStream); @@ -586,6 +641,7 @@ void VehicleConstraint::SaveState(StateRecorder &inStream) const inStream.Write(mPitchRollRotationAxis); // When rotation is too small we use last frame so we need to store it mPitchRollPart.SaveState(inStream); + inStream.Write(mCurrentStep); } void VehicleConstraint::RestoreState(StateRecorder &inStream) @@ -599,8 +655,10 @@ void VehicleConstraint::RestoreState(StateRecorder &inStream) inStream.Read(w->mAngularVelocity); inStream.Read(w->mAngle); inStream.Read(w->mContactBodyID); + inStream.Read(w->mContactPosition); inStream.Read(w->mContactNormal); inStream.Read(w->mContactLateral); + inStream.Read(w->mSuspensionLength); w->mContactBody = nullptr; // No longer valid w->mSuspensionPart.RestoreState(inStream); @@ -611,6 +669,7 @@ void VehicleConstraint::RestoreState(StateRecorder &inStream) inStream.Read(mPitchRollRotationAxis); mPitchRollPart.RestoreState(inStream); + inStream.Read(mCurrentStep); } Ref VehicleConstraint::GetConstraintSettings() const diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleConstraint.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleConstraint.h index f98aeba8..e855b12a 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleConstraint.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleConstraint.h @@ -33,7 +33,7 @@ class JPH_EXPORT VehicleConstraintSettings : public ConstraintSettings float mMaxPitchRollAngle = JPH_PI; ///< Defines the maximum pitch/roll angle (rad), can be used to avoid the car from getting upside down. The vehicle up direction will stay within a cone centered around the up axis with half top angle mMaxPitchRollAngle, set to pi to turn off. Array> mWheels; ///< List of wheels and their properties Array mAntiRollBars; ///< List of anti rollbars and their properties - Ref mController; ///< Defines how the vehicle can accelerate / decellerate + Ref mController; ///< Defines how the vehicle can accelerate / decelerate protected: /// This function should not be called directly, it is used by sRestoreFromBinaryState. @@ -79,12 +79,13 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen void SetVehicleCollisionTester(const VehicleCollisionTester *inTester) { mVehicleCollisionTester = inTester; } /// Callback function to combine the friction of a tire with the friction of the body it is colliding with. - using CombineFunction = float (*)(float inTireFriction, const Body &inBody2, const SubShapeID &inSubShapeID2); + /// On input ioLongitudinalFriction and ioLateralFriction contain the friction of the tire, on output they should contain the combined friction with inBody2. + using CombineFunction = function; /// Set the function that combines the friction of two bodies and returns it /// Default method is the geometric mean: sqrt(friction1 * friction2). - void SetCombineFriction(CombineFunction inCombineFriction) { mCombineFriction = inCombineFriction; } - CombineFunction GetCombineFriction() const { return mCombineFriction; } + void SetCombineFriction(const CombineFunction &inCombineFriction) { mCombineFriction = inCombineFriction; } + const CombineFunction & GetCombineFriction() const { return mCombineFriction; } /// Callback function to notify of current stage in PhysicsStepListener::OnStep. using StepCallback = function; @@ -119,10 +120,10 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen /// Access to the vehicle body Body * GetVehicleBody() const { return mBody; } - /// Access to the vehicle controller interface (determines acceleration / decelleration) + /// Access to the vehicle controller interface (determines acceleration / deceleration) const VehicleController * GetController() const { return mController; } - /// Access to the vehicle controller interface (determines acceleration / decelleration) + /// Access to the vehicle controller interface (determines acceleration / deceleration) VehicleController * GetController() { return mController; } /// Get the state of the wheels @@ -135,7 +136,7 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen Wheel * GetWheel(uint inIdx) { return mWheels[inIdx]; } const Wheel * GetWheel(uint inIdx) const { return mWheels[inIdx]; } - /// Get the basis vectors for the wheel in local space to the vehicle body (note: basis does not rotate when the wheel rotates arounds its axis) + /// Get the basis vectors for the wheel in local space to the vehicle body (note: basis does not rotate when the wheel rotates around its axis) /// @param inWheel Wheel to fetch basis for /// @param outForward Forward vector for the wheel /// @param outUp Up vector for the wheel @@ -154,10 +155,26 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen /// @param inWheelUp Unit vector that indicates up in model space of the wheel RMat44 GetWheelWorldTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const; + /// Number of simulation steps between wheel collision tests when the vehicle is active. Default is 1. 0 = never, 1 = every step, 2 = every other step, etc. + /// Note that if a vehicle has multiple wheels and the number of steps > 1, the wheels will be tested in a round robin fashion. + /// If there are multiple vehicles, the tests will be spread out based on the BodyID of the vehicle. + /// If you set this to test less than every step, you may see simulation artifacts. This setting can be used to reduce the cost of simulating vehicles in the distance. + void SetNumStepsBetweenCollisionTestActive(uint inSteps) { mNumStepsBetweenCollisionTestActive = inSteps; } + uint GetNumStepsBetweenCollisionTestActive() const { return mNumStepsBetweenCollisionTestActive; } + + /// Number of simulation steps between wheel collision tests when the vehicle is inactive. Default is 1. 0 = never, 1 = every step, 2 = every other step, etc. + /// Note that if a vehicle has multiple wheels and the number of steps > 1, the wheels will be tested in a round robin fashion. + /// If there are multiple vehicles, the tests will be spread out based on the BodyID of the vehicle. + /// This number can be lower than the number of steps when the vehicle is active as the only purpose of this test is + /// to allow the vehicle to wake up in response to bodies moving into the wheels but not touching the body of the vehicle. + void SetNumStepsBetweenCollisionTestInactive(uint inSteps) { mNumStepsBetweenCollisionTestInactive = inSteps; } + uint GetNumStepsBetweenCollisionTestInactive() const { return mNumStepsBetweenCollisionTestInactive; } + // Generic interface of a constraint virtual bool IsActive() const override { return mIsActive && Constraint::IsActive(); } virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override { /* Do nothing */ } virtual void SetupVelocityConstraint(float inDeltaTime) override; + virtual void ResetWarmStart() override; virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override; virtual bool SolveVelocityConstraint(float inDeltaTime) override; virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override; @@ -181,15 +198,18 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen // Calculate the constraint properties for mPitchRollPart void CalculatePitchRollConstraintProperties(RMat44Arg inBodyTransform); - // Simluation information + // Simulation information Body * mBody; ///< Body of the vehicle Vec3 mForward; ///< Local space forward vector for the vehicle Vec3 mUp; ///< Local space up vector for the vehicle Vec3 mWorldUp; ///< Vector indicating the world space up direction (used to limit vehicle pitch/roll) Wheels mWheels; ///< Wheel states of the vehicle Array mAntiRollBars; ///< Anti rollbars of the vehicle - VehicleController * mController; ///< Controls the acceleration / declerration of the vehicle + VehicleController * mController; ///< Controls the acceleration / deceleration of the vehicle bool mIsActive = false; ///< If this constraint is active + uint mNumStepsBetweenCollisionTestActive = 1; ///< Number of simulation steps between wheel collision tests when the vehicle is active + uint mNumStepsBetweenCollisionTestInactive = 1; ///< Number of simulation steps between wheel collision tests when the vehicle is inactive + uint mCurrentStep = 0; ///< Current step number, used to determine when to test a wheel // Prevent vehicle from toppling over float mCosMaxPitchRollAngle; ///< Cos of the max pitch/roll angle @@ -199,7 +219,13 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen // Interfaces RefConst mVehicleCollisionTester; ///< Class that performs testing of collision for the wheels - CombineFunction mCombineFriction = [](float inTireFriction, const Body &inBody2, const SubShapeID &) { return sqrt(inTireFriction * inBody2.GetFriction()); }; + CombineFunction mCombineFriction = [](uint, float &ioLongitudinalFriction, float &ioLateralFriction, const Body &inBody2, const SubShapeID &) + { + float body_friction = inBody2.GetFriction(); + + ioLongitudinalFriction = sqrt(ioLongitudinalFriction * body_friction); + ioLateralFriction = sqrt(ioLateralFriction * body_friction); + }; // Callbacks StepCallback mPreStepCallback; diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleController.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleController.h index 02bd3dee..b916a8bf 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleController.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleController.h @@ -20,7 +20,7 @@ class WheelSettings; class Wheel; class StateRecorder; -/// Basic settings object for interface that controls acceleration / decelleration of the vehicle +/// Basic settings object for interface that controls acceleration / deceleration of the vehicle class JPH_EXPORT VehicleControllerSettings : public SerializableObject, public RefTarget { public: @@ -36,7 +36,7 @@ class JPH_EXPORT VehicleControllerSettings : public SerializableObject, public R virtual VehicleController * ConstructController(VehicleConstraint &inConstraint) const = 0; }; -/// Runtime data for interface that controls acceleration / decelleration of the vehicle +/// Runtime data for interface that controls acceleration / deceleration of the vehicle class JPH_EXPORT VehicleController : public RefTarget, public NonCopyable { public: diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleEngine.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleEngine.h index 36237674..25cf6391 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleEngine.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleEngine.h @@ -34,7 +34,7 @@ class JPH_EXPORT VehicleEngineSettings float mMaxTorque = 500.0f; ///< Max amount of torque (Nm) that the engine can deliver float mMinRPM = 1000.0f; ///< Min amount of revolutions per minute (rpm) the engine can produce without stalling float mMaxRPM = 6000.0f; ///< Max amount of revolutions per minute (rpm) the engine can generate - LinearCurve mNormalizedTorque; ///< Curve that describes a ratio of the max torque the engine can produce vs the fraction of the max RPM of the engine + LinearCurve mNormalizedTorque; ///< Y-axis: Curve that describes a ratio of the max torque the engine can produce (0 = 0, 1 = mMaxTorque). X-axis: the fraction of the RPM of the engine (0 = mMinRPM, 1 = mMaxRPM) float mInertia = 0.5f; ///< Moment of inertia (kg m^2) of the engine float mAngularDamping = 0.2f; ///< Angular damping factor of the wheel: dw/dt = -c * w }; diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleTransmission.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleTransmission.h index ba1499d4..1d3306a8 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleTransmission.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/VehicleTransmission.h @@ -50,7 +50,7 @@ class JPH_EXPORT VehicleTransmission : public VehicleTransmissionSettings /// @param inClutchFriction Value between 0 and 1 indicating how much friction the clutch gives (0 = no friction, 1 = full friction) void Set(int inCurrentGear, float inClutchFriction) { mCurrentGear = inCurrentGear; mClutchFriction = inClutchFriction; } - /// Update the current gear and clutch friction if the transmission is in aut mode + /// Update the current gear and clutch friction if the transmission is in auto mode /// @param inDeltaTime Time step delta time in s /// @param inCurrentRPM Current RPM for engine /// @param inForwardInput Hint if the user wants to drive forward (> 0) or backwards (< 0) diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/Wheel.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/Wheel.h index c540b2e9..eb5f6874 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/Wheel.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/Wheel.h @@ -38,7 +38,7 @@ class JPH_EXPORT WheelSettings : public SerializableObject, public RefTarget= 0.0f); } -void WheelWV::Update(float inDeltaTime, const VehicleConstraint &inConstraint) +void WheelWV::Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint) { const WheelSettingsWV *settings = GetSettings(); @@ -122,8 +122,9 @@ void WheelWV::Update(float inDeltaTime, const VehicleConstraint &inConstraint) // Tire friction VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction(); - mCombinedLongitudinalFriction = combine_friction(longitudinal_slip_friction, *mContactBody, mContactSubShapeID); - mCombinedLateralFriction = combine_friction(lateral_slip_friction, *mContactBody, mContactSubShapeID); + mCombinedLongitudinalFriction = longitudinal_slip_friction; + mCombinedLateralFriction = lateral_slip_friction; + combine_friction(inWheelIndex, mCombinedLongitudinalFriction, mCombinedLateralFriction, *mContactBody, mContactSubShapeID); } else { @@ -175,6 +176,7 @@ WheeledVehicleController::WheeledVehicleController(const WheeledVehicleControlle static_cast(mEngine) = inSettings.mEngine; JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f); JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM); + mEngine.SetCurrentRPM(mEngine.mMinRPM); // Copy transmission settings static_cast(mTransmission) = inSettings.mTransmission; @@ -265,10 +267,10 @@ void WheeledVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inP Wheels &wheels = mConstraint.GetWheels(); // Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again) - for (Wheel *w_base : wheels) + for (uint wheel_index = 0, num_wheels = (uint)wheels.size(); wheel_index < num_wheels; ++wheel_index) { - WheelWV *w = static_cast(w_base); - w->Update(inDeltaTime, mConstraint); + WheelWV *w = static_cast(wheels[wheel_index]); + w->Update(wheel_index, inDeltaTime, mConstraint); } // In auto transmission mode, don't accelerate the engine when switching gears @@ -429,7 +431,7 @@ void WheeledVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inP // TW(i) is the torque applied to the wheel outside of the engine (brake + torque due to friction with the ground) // F(i) is the fraction of the engine torque applied from engine to wheel i // - // Because the angular accelaration and torque are connected through: Torque = I * dw/dt + // Because the angular acceleration and torque are connected through: Torque = I * dw/dt // // We have the angular acceleration of the engine at time t: // @@ -651,14 +653,21 @@ bool WheeledVehicleController::SolveLongitudinalAndLateralConstraints(float inDe { bool impulse = false; + float *max_lateral_friction_impulse = (float *)JPH_STACK_ALLOC(mConstraint.GetWheels().size() * sizeof(float)); + + uint wheel_index = 0; for (Wheel *w_base : mConstraint.GetWheels()) + { if (w_base->HasContact()) { WheelWV *w = static_cast(w_base); const WheelSettingsWV *settings = w->GetSettings(); // Calculate max impulse that we can apply on the ground - float max_longitudinal_friction_impulse = w->mCombinedLongitudinalFriction * w->GetSuspensionLambda(); + float max_longitudinal_friction_impulse; + mTireMaxImpulseCallback(wheel_index, + max_longitudinal_friction_impulse, max_lateral_friction_impulse[wheel_index], w->GetSuspensionLambda(), + w->mCombinedLongitudinalFriction, w->mCombinedLateralFriction, w->mLongitudinalSlip, w->mLateralSlip, inDeltaTime); // Calculate relative velocity between wheel contact point and floor in longitudinal direction Vec3 relative_velocity = mConstraint.GetVehicleBody()->GetPointVelocity(w->GetContactPosition()) - w->GetContactPointVelocity(); @@ -693,26 +702,32 @@ bool WheeledVehicleController::SolveLongitudinalAndLateralConstraints(float inDe float linear_impulse = (w->GetAngularVelocity() - desired_angular_velocity) * settings->mInertia / settings->mRadius; // Limit the impulse by max tire friction - min_longitudinal_impulse = max_longitudinal_impulse = w->GetLongitudinalLambda() + Sign(linear_impulse) * min(abs(linear_impulse), max_longitudinal_friction_impulse); + float prev_lambda = w->GetLongitudinalLambda(); + min_longitudinal_impulse = max_longitudinal_impulse = Clamp(prev_lambda + linear_impulse, -max_longitudinal_friction_impulse, max_longitudinal_friction_impulse); // Longitudinal impulse - float prev_lambda = w->GetLongitudinalLambda(); impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse); // Update the angular velocity of the wheels according to the lambda that was applied w->SetAngularVelocity(w->GetAngularVelocity() - (w->GetLongitudinalLambda() - prev_lambda) * settings->mRadius / settings->mInertia); } } + ++wheel_index; + } + wheel_index = 0; for (Wheel *w_base : mConstraint.GetWheels()) + { if (w_base->HasContact()) { WheelWV *w = static_cast(w_base); // Lateral friction - float max_lateral_friction_impulse = w->mCombinedLateralFriction * w->GetSuspensionLambda(); - impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_friction_impulse, max_lateral_friction_impulse); + float max_lateral_impulse = max_lateral_friction_impulse[wheel_index]; + impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_impulse, max_lateral_impulse); } + ++wheel_index; + } return impulse; } @@ -779,6 +794,11 @@ void WheeledVehicleController::Draw(DebugRenderer *inRenderer) const inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_right, Color::sBlue); inRenderer->DrawLine(wheel_pos, wheel_pos + steering_axis, Color::sYellow); + // Draw wheel + RMat44 wheel_transform(Vec4(wheel_up, 0.0f), Vec4(wheel_right, 0.0f), Vec4(wheel_forward, 0.0f), wheel_pos); + wheel_transform.SetRotation(wheel_transform.GetRotation() * Mat44::sRotationY(-w->GetRotationAngle())); + inRenderer->DrawCylinder(wheel_transform, settings->mWidth * 0.5f, settings->mRadius, w->GetSuspensionLength() <= settings->mSuspensionMinLength? Color::sRed : Color::sGreen, DebugRenderer::ECastShadow::Off, DebugRenderer::EDrawMode::Wireframe); + if (w->HasContact()) { // Draw contact diff --git a/Dependencies/Jolt/Jolt/Physics/Vehicle/WheeledVehicleController.h b/Dependencies/Jolt/Jolt/Physics/Vehicle/WheeledVehicleController.h index aa4d68ed..3d314e29 100644 --- a/Dependencies/Jolt/Jolt/Physics/Vehicle/WheeledVehicleController.h +++ b/Dependencies/Jolt/Jolt/Physics/Vehicle/WheeledVehicleController.h @@ -31,8 +31,8 @@ class JPH_EXPORT WheelSettingsWV : public WheelSettings float mInertia = 0.9f; ///< Moment of inertia (kg m^2), for a cylinder this would be 0.5 * M * R^2 which is 0.9 for a wheel with a mass of 20 kg and radius 0.3 m float mAngularDamping = 0.2f; ///< Angular damping factor of the wheel: dw/dt = -c * w float mMaxSteerAngle = DegreesToRadians(70.0f); ///< How much this wheel can steer (radians) - LinearCurve mLongitudinalFriction; ///< Friction in forward direction of tire as a function of the slip ratio (fraction): (omega_wheel * r_wheel - v_longitudinal) / |v_longitudinal| - LinearCurve mLateralFriction; ///< Friction in sideway direction of tire as a function of the slip angle (degrees): angle between relative contact velocity and vehicle direction + LinearCurve mLongitudinalFriction; ///< On the Y-axis: friction in the forward direction of the tire. Friction is normally between 0 (no friction) and 1 (full friction) although friction can be a little bit higher than 1 because of the profile of a tire. On the X-axis: the slip ratio (fraction) defined as (omega_wheel * r_wheel - v_longitudinal) / |v_longitudinal|. You can see slip ratio as the amount the wheel is spinning relative to the floor: 0 means the wheel has full traction and is rolling perfectly in sync with the ground, 1 is for example when the wheel is locked and sliding over the ground. + LinearCurve mLateralFriction; ///< On the Y-axis: friction in the sideways direction of the tire. Friction is normally between 0 (no friction) and 1 (full friction) although friction can be a little bit higher than 1 because of the profile of a tire. On the X-axis: the slip angle (degrees) defined as angle between relative contact velocity and tire direction. float mMaxBrakeTorque = 1500.0f; ///< How much torque (Nm) the brakes can apply to this wheel float mMaxHandBrakeTorque = 4000.0f; ///< How much torque (Nm) the hand brake can apply to this wheel (usually only applied to the rear wheels) }; @@ -56,7 +56,7 @@ class JPH_EXPORT WheelWV : public Wheel } /// Update the wheel rotation based on the current angular velocity - void Update(float inDeltaTime, const VehicleConstraint &inConstraint); + void Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint); float mLongitudinalSlip = 0.0f; ///< Velocity difference between ground and wheel relative to ground velocity float mLateralSlip = 0.0f; ///< Angular difference (in radians) between ground and wheel relative to ground velocity @@ -145,6 +145,11 @@ class JPH_EXPORT WheeledVehicleController : public VehicleController /// Get the average wheel speed of all driven wheels (measured at the clutch) float GetWheelSpeedAtClutch() const; + /// Calculate max tire impulses by combining friction, slip, and suspension impulse. Note that the actual applied impulse may be lower (e.g. when the vehicle is stationary on a horizontal surface the actual impulse applied will be 0). + using TireMaxImpulseCallback = function; + const TireMaxImpulseCallback&GetTireMaxImpulseCallback() const { return mTireMaxImpulseCallback; } + void SetTireMaxImpulseCallback(const TireMaxImpulseCallback &inTireMaxImpulseCallback) { mTireMaxImpulseCallback = inTireMaxImpulseCallback; } + #ifdef JPH_DEBUG_RENDERER /// Debug drawing of RPM meter void SetRPMMeter(Vec3Arg inPosition, float inSize) { mRPMMeterPosition = inPosition; mRPMMeterSize = inSize; } @@ -169,13 +174,21 @@ class JPH_EXPORT WheeledVehicleController : public VehicleController float mBrakeInput = 0.0f; ///< Value between 0 and 1 indicating how strong the brake pedal is pressed float mHandBrakeInput = 0.0f; ///< Value between 0 and 1 indicating how strong the hand brake is pulled - // Simluation information + // Simulation information VehicleEngine mEngine; ///< Engine state of the vehicle VehicleTransmission mTransmission; ///< Transmission state of the vehicle Differentials mDifferentials; ///< Differential states of the vehicle float mDifferentialLimitedSlipRatio; ///< Ratio max / min average wheel speed of each differential (measured at the clutch). float mPreviousDeltaTime = 0.0f; ///< Delta time of the last step + // Callback that calculates the max impulse that the tire can apply to the ground + TireMaxImpulseCallback mTireMaxImpulseCallback = + [](uint, float &outLongitudinalImpulse, float &outLateralImpulse, float inSuspensionImpulse, float inLongitudinalFriction, float inLateralFriction, float, float, float) + { + outLongitudinalImpulse = inLongitudinalFriction * inSuspensionImpulse; + outLateralImpulse = inLateralFriction * inSuspensionImpulse; + }; + #ifdef JPH_DEBUG_RENDERER // Debug settings Vec3 mRPMMeterPosition { 0, 1, 0 }; ///< Position (in local space of the body) of the RPM meter when drawing the constraint diff --git a/Dependencies/Jolt/Jolt/RegisterTypes.cpp b/Dependencies/Jolt/Jolt/RegisterTypes.cpp index cd0cc3fb..d9f9b896 100644 --- a/Dependencies/Jolt/Jolt/RegisterTypes.cpp +++ b/Dependencies/Jolt/Jolt/RegisterTypes.cpp @@ -80,8 +80,20 @@ void RegisterTypesInternal(uint64 inVersionID) // Version check if (!VerifyJoltVersionIDInternal(inVersionID)) { - JPH_ASSERT(false, "Version mismatch, make sure you compile the client code with the same Jolt version and compiler definitions!"); - JPH_CRASH; + Trace("Version mismatch, make sure you compile the client code with the same Jolt version and compiler definitions!"); + uint64 mismatch = JPH_VERSION_ID ^ inVersionID; + auto check_bit = [mismatch](int inBit, const char *inLabel) { if (mismatch & (uint64(1) << (inBit + 23))) Trace("Mismatching define %s.", inLabel); }; + check_bit(1, "JPH_DOUBLE_PRECISION"); + check_bit(2, "JPH_CROSS_PLATFORM_DETERMINISTIC"); + check_bit(3, "JPH_FLOATING_POINT_EXCEPTIONS_ENABLED"); + check_bit(4, "JPH_PROFILE_ENABLED"); + check_bit(5, "JPH_EXTERNAL_PROFILE"); + check_bit(6, "JPH_DEBUG_RENDERER"); + check_bit(7, "JPH_DISABLE_TEMP_ALLOCATOR"); + check_bit(8, "JPH_DISABLE_CUSTOM_ALLOCATOR"); + check_bit(9, "JPH_OBJECT_LAYER_BITS"); + check_bit(10, "JPH_ENABLE_ASSERTS"); + std::abort(); } #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR diff --git a/Dependencies/Jolt/Jolt/RegisterTypes.h b/Dependencies/Jolt/Jolt/RegisterTypes.h index 908322a0..372ef7f4 100644 --- a/Dependencies/Jolt/Jolt/RegisterTypes.h +++ b/Dependencies/Jolt/Jolt/RegisterTypes.h @@ -18,7 +18,9 @@ JPH_INLINE bool VerifyJoltVersionID() { return VerifyJoltVersionIDInternal(JPH_V /// Internal helper function JPH_EXPORT extern void RegisterTypesInternal(uint64 inVersionID); -/// Register all physics types with the factory +/// Register all physics types with the factory and install their collision handlers with the CollisionDispatch class. +/// If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function. +/// If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you. JPH_INLINE void RegisterTypes() { RegisterTypesInternal(JPH_VERSION_ID); } /// Unregisters all types with the factory and cleans up the default material diff --git a/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.cpp b/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.cpp index b91684de..44025aaf 100644 --- a/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.cpp +++ b/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.cpp @@ -797,7 +797,63 @@ void DebugRenderer::DrawOpenCone(RVec3Arg inTop, Vec3Arg inAxis, Vec3Arg inPerpe } } -void DebugRenderer::DrawSwingLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, float inSwingZHalfAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow, EDrawMode inDrawMode) +DebugRenderer::Geometry *DebugRenderer::CreateSwingLimitGeometry(int inNumSegments, const Vec3 *inVertices) +{ + // Allocate space for vertices + int num_vertices = 2 * inNumSegments; + Vertex *vertices_start = (Vertex *)JPH_STACK_ALLOC(num_vertices * sizeof(Vertex)); + Vertex *vertices = vertices_start; + + for (int i = 0; i < inNumSegments; ++i) + { + // Get output vertices + Vertex &top = *(vertices++); + Vertex &bottom = *(vertices++); + + // Get local position + const Vec3 &pos = inVertices[i]; + + // Get local normal + const Vec3 &prev_pos = inVertices[(i + inNumSegments - 1) % inNumSegments]; + const Vec3 &next_pos = inVertices[(i + 1) % inNumSegments]; + Vec3 normal = 0.5f * (next_pos.Cross(pos).NormalizedOr(Vec3::sZero()) + pos.Cross(prev_pos).NormalizedOr(Vec3::sZero())); + + // Store top vertex + top.mPosition = { 0, 0, 0 }; + normal.StoreFloat3(&top.mNormal); + top.mColor = Color::sWhite; + top.mUV = { 0, 0 }; + + // Store bottom vertex + pos.StoreFloat3(&bottom.mPosition); + normal.StoreFloat3(&bottom.mNormal); + bottom.mColor = Color::sWhite; + bottom.mUV = { 0, 0 }; + } + + // Allocate space for indices + int num_indices = 3 * inNumSegments; + uint32 *indices_start = (uint32 *)JPH_STACK_ALLOC(num_indices * sizeof(uint32)); + uint32 *indices = indices_start; + + // Calculate indices + for (int i = 0; i < inNumSegments; ++i) + { + int first = 2 * i; + int second = (first + 3) % num_vertices; + int third = first + 1; + + // Triangle + *indices++ = first; + *indices++ = second; + *indices++ = third; + } + + // Convert to triangle batch + return new Geometry(CreateTriangleBatch(vertices_start, num_vertices, indices_start, num_indices), sCalculateBounds(vertices_start, num_vertices)); +} + +void DebugRenderer::DrawSwingConeLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, float inSwingZHalfAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow, EDrawMode inDrawMode) { JPH_PROFILE_FUNCTION(); @@ -807,8 +863,14 @@ void DebugRenderer::DrawSwingLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, JPH_ASSERT(inEdgeLength > 0.0f); // Check cache - SwingLimits limits { inSwingYHalfAngle, inSwingZHalfAngle }; - GeometryRef &geometry = mSwingLimits[limits]; + SwingConeLimits limits { inSwingYHalfAngle, inSwingZHalfAngle }; + GeometryRef &geometry = mSwingConeLimits[limits]; + if (geometry == nullptr) + { + SwingConeBatches::iterator it = mPrevSwingConeLimits.find(limits); + if (it != mPrevSwingConeLimits.end()) + geometry = it->second; + } if (geometry == nullptr) { // Number of segments to draw the cone with @@ -827,11 +889,6 @@ void DebugRenderer::DrawSwingLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, float e1_sq = Square(e1); float e2_sq = Square(e2); - // Allocate space for vertices - int num_vertices = 2 * num_segments; - Vertex *vertices_start = (Vertex *)JPH_STACK_ALLOC(num_vertices * sizeof(Vertex)); - Vertex *vertices = vertices_start; - // Calculate local space vertices for shape Vec3 ls_vertices[num_segments]; int tgt_vertex = 0; @@ -874,59 +931,63 @@ void DebugRenderer::DrawSwingLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, ls_vertices[tgt_vertex++] = q.RotateAxisX(); } - for (int i = 0; i < num_segments; ++i) - { - // Get output vertices - Vertex &top = *(vertices++); - Vertex &bottom = *(vertices++); - - // Get local position - Vec3 &pos = ls_vertices[i]; - - // Get local normal - Vec3 &prev_pos = ls_vertices[(i + num_segments - 1) % num_segments]; - Vec3 &next_pos = ls_vertices[(i + 1) % num_segments]; - Vec3 normal = 0.5f * (next_pos.Cross(pos).Normalized() + pos.Cross(prev_pos).Normalized()); - - // Store top vertex - top.mPosition = { 0, 0, 0 }; - normal.StoreFloat3(&top.mNormal); - top.mColor = Color::sWhite; - top.mUV = { 0, 0 }; - - // Store bottom vertex - pos.StoreFloat3(&bottom.mPosition); - normal.StoreFloat3(&bottom.mNormal); - bottom.mColor = Color::sWhite; - bottom.mUV = { 0, 0 }; - } + geometry = CreateSwingLimitGeometry(num_segments, ls_vertices); + } - // Allocate space for indices - int num_indices = 3 * num_segments; - uint32 *indices_start = (uint32 *)JPH_STACK_ALLOC(num_indices * sizeof(uint32)); - uint32 *indices = indices_start; + DrawGeometry(inMatrix * Mat44::sScale(inEdgeLength), inColor, geometry, ECullMode::Off, inCastShadow, inDrawMode); +} - // Calculate indices - for (int i = 0; i < num_segments; ++i) - { - int first = 2 * i; - int second = (first + 3) % num_vertices; - int third = first + 1; - - // Triangle - *indices++ = first; - *indices++ = second; - *indices++ = third; - } +void DebugRenderer::DrawSwingPyramidLimits(RMat44Arg inMatrix, float inMinSwingYAngle, float inMaxSwingYAngle, float inMinSwingZAngle, float inMaxSwingZAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow, EDrawMode inDrawMode) +{ + JPH_PROFILE_FUNCTION(); - // Convert to triangle batch - geometry = new Geometry(CreateTriangleBatch(vertices_start, num_vertices, indices_start, num_indices), sCalculateBounds(vertices_start, num_vertices)); + // Assert sane input + JPH_ASSERT(inMinSwingYAngle <= inMaxSwingYAngle && inMinSwingZAngle <= inMaxSwingZAngle); + JPH_ASSERT(inEdgeLength > 0.0f); + + // Check cache + SwingPyramidLimits limits { inMinSwingYAngle, inMaxSwingYAngle, inMinSwingZAngle, inMaxSwingZAngle }; + GeometryRef &geometry = mSwingPyramidLimits[limits]; + if (geometry == nullptr) + { + SwingPyramidBatches::iterator it = mPrevSwingPyramidLimits.find(limits); + if (it != mPrevSwingPyramidLimits.end()) + geometry = it->second; + } + if (geometry == nullptr) + { + // Number of segments to draw the cone with + const int num_segments = 64; + int quarter_num_segments = num_segments / 4; + + // Note that this is q = Quat::sRotation(Vec3::sAxisZ(), z) * Quat::sRotation(Vec3::sAxisY(), y) with q.x set to zero so we don't introduce twist + // This matches the calculation in SwingTwistConstraintPart::ClampSwingTwist + auto get_axis = [](float inY, float inZ) { + float hy = 0.5f * inY; + float hz = 0.5f * inZ; + float cos_hy = Cos(hy); + float cos_hz = Cos(hz); + return Quat(0, Sin(hy) * cos_hz, cos_hy * Sin(hz), cos_hy * cos_hz).Normalized().RotateAxisX(); + }; + + // Calculate local space vertices for shape + Vec3 ls_vertices[num_segments]; + int tgt_vertex = 0; + for (int segment_iter = 0; segment_iter < quarter_num_segments; ++segment_iter) + ls_vertices[tgt_vertex++] = get_axis(inMinSwingYAngle, inMaxSwingZAngle - (inMaxSwingZAngle - inMinSwingZAngle) * segment_iter / quarter_num_segments); + for (int segment_iter = 0; segment_iter < quarter_num_segments; ++segment_iter) + ls_vertices[tgt_vertex++] = get_axis(inMinSwingYAngle + (inMaxSwingYAngle - inMinSwingYAngle) * segment_iter / quarter_num_segments, inMinSwingZAngle); + for (int segment_iter = 0; segment_iter < quarter_num_segments; ++segment_iter) + ls_vertices[tgt_vertex++] = get_axis(inMaxSwingYAngle, inMinSwingZAngle + (inMaxSwingZAngle - inMinSwingZAngle) * segment_iter / quarter_num_segments); + for (int segment_iter = 0; segment_iter < quarter_num_segments; ++segment_iter) + ls_vertices[tgt_vertex++] = get_axis(inMaxSwingYAngle - (inMaxSwingYAngle - inMinSwingYAngle) * segment_iter / quarter_num_segments, inMaxSwingZAngle); + + geometry = CreateSwingLimitGeometry(num_segments, ls_vertices); } DrawGeometry(inMatrix * Mat44::sScale(inEdgeLength), inColor, geometry, ECullMode::Off, inCastShadow, inDrawMode); } - void DebugRenderer::DrawPie(RVec3Arg inCenter, float inRadius, Vec3Arg inNormal, Vec3Arg inAxis, float inMinAngle, float inMaxAngle, ColorArg inColor, ECastShadow inCastShadow, EDrawMode inDrawMode) { if (inMinAngle >= inMaxAngle) @@ -942,6 +1003,12 @@ void DebugRenderer::DrawPie(RVec3Arg inCenter, float inRadius, Vec3Arg inNormal, float delta_angle = inMaxAngle - inMinAngle; GeometryRef &geometry = mPieLimits[delta_angle]; if (geometry == nullptr) + { + PieBatces::iterator it = mPrevPieLimits.find(delta_angle); + if (it != mPrevPieLimits.end()) + geometry = it->second; + } + if (geometry == nullptr) { int num_parts = (int)ceil(64.0f * delta_angle / (2.0f * JPH_PI)); @@ -987,6 +1054,18 @@ void DebugRenderer::DrawPie(RVec3Arg inCenter, float inRadius, Vec3Arg inNormal, DrawGeometry(matrix, inColor, geometry, ECullMode::Off, inCastShadow, inDrawMode); } +void DebugRenderer::NextFrame() +{ + mPrevSwingConeLimits.clear(); + std::swap(mSwingConeLimits, mPrevSwingConeLimits); + + mPrevSwingPyramidLimits.clear(); + std::swap(mSwingPyramidLimits, mPrevSwingPyramidLimits); + + mPrevPieLimits.clear(); + std::swap(mPieLimits, mPrevPieLimits); +} + JPH_NAMESPACE_END #endif // JPH_DEBUG_RENDERER diff --git a/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.h b/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.h index 4d36d76d..dd3e7bc8 100644 --- a/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.h +++ b/Dependencies/Jolt/Jolt/Renderer/DebugRenderer.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,23 @@ JPH_NAMESPACE_BEGIN class OrientedBox; /// Simple triangle renderer for debugging purposes. -class JPH_DEBUG_RENDERER_EXPORT DebugRenderer +/// +/// Inherit from this class to provide your own implementation. +/// +/// Implement the following virtual functions: +/// - DrawLine +/// - DrawTriangle +/// - DrawText3D +/// - CreateTriangleBatch +/// - DrawGeometry +/// +/// Make sure you call Initialize() from the constructor of your implementation. +/// +/// The CreateTriangleBatch is used to prepare a batch of triangles to be drawn by a single DrawGeometry call, +/// which means that Jolt can render a complex scene much more efficiently than when each triangle in that scene would have been drawn through DrawTriangle. +/// +/// Note that an implementation that implements CreateTriangleBatch and DrawGeometry is provided by DebugRendererSimple which can be used to start quickly. +class JPH_DEBUG_RENDERER_EXPORT DebugRenderer : public NonCopyable { public: JPH_OVERRIDE_NEW_DELETE @@ -35,6 +52,9 @@ class JPH_DEBUG_RENDERER_EXPORT DebugRenderer DebugRenderer(); virtual ~DebugRenderer(); + /// Call once after frame is complete. Releases unused dynamically generated geometry assets. + void NextFrame(); + /// Draw line virtual void DrawLine(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor) = 0; @@ -106,19 +126,31 @@ class JPH_DEBUG_RENDERER_EXPORT DebugRenderer /// @param inHalfAngle Specifies the cone angle in radians (angle measured between inAxis and cone surface). /// @param inLength The length of the cone. /// @param inColor Color to use for drawing the cone. - /// @param inCastShadow determins if this geometry should cast a shadow or not. + /// @param inCastShadow determines if this geometry should cast a shadow or not. /// @param inDrawMode determines if we draw the geometry solid or in wireframe. void DrawOpenCone(RVec3Arg inTop, Vec3Arg inAxis, Vec3Arg inPerpendicular, float inHalfAngle, float inLength, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid); - /// Draws rotation limits as used by the SwingTwistConstraintPart. + /// Draws cone rotation limits as used by the SwingTwistConstraintPart. /// @param inMatrix Matrix that transforms from constraint space to world space /// @param inSwingYHalfAngle See SwingTwistConstraintPart /// @param inSwingZHalfAngle See SwingTwistConstraintPart /// @param inEdgeLength Size of the edge of the cone shape /// @param inColor Color to use for drawing the cone. - /// @param inCastShadow determins if this geometry should cast a shadow or not. + /// @param inCastShadow determines if this geometry should cast a shadow or not. /// @param inDrawMode determines if we draw the geometry solid or in wireframe. - void DrawSwingLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, float inSwingZHalfAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid); + void DrawSwingConeLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, float inSwingZHalfAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid); + + /// Draws rotation limits as used by the SwingTwistConstraintPart. + /// @param inMatrix Matrix that transforms from constraint space to world space + /// @param inMinSwingYAngle See SwingTwistConstraintPart + /// @param inMaxSwingYAngle See SwingTwistConstraintPart + /// @param inMinSwingZAngle See SwingTwistConstraintPart + /// @param inMaxSwingZAngle See SwingTwistConstraintPart + /// @param inEdgeLength Size of the edge of the cone shape + /// @param inColor Color to use for drawing the cone. + /// @param inCastShadow determines if this geometry should cast a shadow or not. + /// @param inDrawMode determines if we draw the geometry solid or in wireframe. + void DrawSwingPyramidLimits(RMat44Arg inMatrix, float inMinSwingYAngle, float inMaxSwingYAngle, float inMinSwingZAngle, float inMaxSwingZAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid); /// Draw a pie (part of a circle). /// @param inCenter The center of the circle. @@ -128,7 +160,7 @@ class JPH_DEBUG_RENDERER_EXPORT DebugRenderer /// @param inMinAngle The pie will be drawn between [inMinAngle, inMaxAngle] (in radians). /// @param inMaxAngle The pie will be drawn between [inMinAngle, inMaxAngle] (in radians). /// @param inColor Color to use for drawing the pie. - /// @param inCastShadow determins if this geometry should cast a shadow or not. + /// @param inCastShadow determines if this geometry should cast a shadow or not. /// @param inDrawMode determines if we draw the geometry solid or in wireframe. void DrawPie(RVec3Arg inCenter, float inRadius, Vec3Arg inNormal, Vec3Arg inAxis, float inMinAngle, float inMaxAngle, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid); @@ -177,6 +209,21 @@ class JPH_DEBUG_RENDERER_EXPORT DebugRenderer Geometry(const AABox &inBounds) : mBounds(inBounds) { } Geometry(const Batch &inBatch, const AABox &inBounds) : mBounds(inBounds) { mLODs.push_back({ inBatch, FLT_MAX }); } + /// Determine which LOD to render + /// @param inCameraPosition Current position of the camera + /// @param inWorldSpaceBounds World space bounds for this geometry (transform mBounds by model space matrix) + /// @param inLODScaleSq is the squared scale of the model matrix, it is multiplied with the LOD distances in inGeometry to calculate the real LOD distance (so a number > 1 will force a higher LOD). + /// @return The selected LOD. + const LOD & GetLOD(Vec3Arg inCameraPosition, const AABox &inWorldSpaceBounds, float inLODScaleSq) const + { + float dist_sq = inWorldSpaceBounds.GetSqDistanceTo(inCameraPosition); + for (const LOD &lod : mLODs) + if (dist_sq <= inLODScaleSq * Square(lod.mDistance)) + return lod; + + return mLODs.back(); + } + /// All level of details for this mesh Array mLODs; @@ -240,6 +287,9 @@ class JPH_DEBUG_RENDERER_EXPORT DebugRenderer void Create8thSphereRecursive(Array &ioIndices, Array &ioVertices, Vec3Arg inDir1, uint32 &ioIdx1, Vec3Arg inDir2, uint32 &ioIdx2, Vec3Arg inDir3, uint32 &ioIdx3, const Float2 &inUV, SupportFunction inGetSupport, int inLevel); void Create8thSphere(Array &ioIndices, Array &ioVertices, Vec3Arg inDir1, Vec3Arg inDir2, Vec3Arg inDir3, const Float2 &inUV, SupportFunction inGetSupport, int inLevel); + /// Helper function for DrawSwingConeLimits and DrawSwingPyramidLimits + Geometry * CreateSwingLimitGeometry(int inNumSegments, const Vec3 *inVertices); + // Predefined shapes GeometryRef mBox; GeometryRef mSphere; @@ -249,21 +299,49 @@ class JPH_DEBUG_RENDERER_EXPORT DebugRenderer GeometryRef mOpenCone; GeometryRef mCylinder; - struct SwingLimits + struct SwingConeLimits { - bool operator == (const SwingLimits &inRHS) const { return mSwingYHalfAngle == inRHS.mSwingYHalfAngle && mSwingZHalfAngle == inRHS.mSwingZHalfAngle; } + bool operator == (const SwingConeLimits &inRHS) const + { + return mSwingYHalfAngle == inRHS.mSwingYHalfAngle + && mSwingZHalfAngle == inRHS.mSwingZHalfAngle; + } float mSwingYHalfAngle; float mSwingZHalfAngle; }; - JPH_MAKE_HASH_STRUCT(SwingLimits, SwingLimitsHasher, t.mSwingYHalfAngle, t.mSwingZHalfAngle) + JPH_MAKE_HASH_STRUCT(SwingConeLimits, SwingConeLimitsHasher, t.mSwingYHalfAngle, t.mSwingZHalfAngle) + + using SwingConeBatches = UnorderedMap; + SwingConeBatches mSwingConeLimits; + SwingConeBatches mPrevSwingConeLimits; + + struct SwingPyramidLimits + { + bool operator == (const SwingPyramidLimits &inRHS) const + { + return mMinSwingYAngle == inRHS.mMinSwingYAngle + && mMaxSwingYAngle == inRHS.mMaxSwingYAngle + && mMinSwingZAngle == inRHS.mMinSwingZAngle + && mMaxSwingZAngle == inRHS.mMaxSwingZAngle; + } + + float mMinSwingYAngle; + float mMaxSwingYAngle; + float mMinSwingZAngle; + float mMaxSwingZAngle; + }; + + JPH_MAKE_HASH_STRUCT(SwingPyramidLimits, SwingPyramidLimitsHasher, t.mMinSwingYAngle, t.mMaxSwingYAngle, t.mMinSwingZAngle, t.mMaxSwingZAngle) - using SwingBatches = UnorderedMap; - SwingBatches mSwingLimits; + using SwingPyramidBatches = UnorderedMap; + SwingPyramidBatches mSwingPyramidLimits; + SwingPyramidBatches mPrevSwingPyramidLimits; using PieBatces = UnorderedMap; PieBatces mPieLimits; + PieBatces mPrevPieLimits; }; JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.cpp b/Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.cpp new file mode 100644 index 00000000..a404d95a --- /dev/null +++ b/Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.cpp @@ -0,0 +1,80 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#ifdef JPH_DEBUG_RENDERER + +#include + +JPH_NAMESPACE_BEGIN + +DebugRendererSimple::DebugRendererSimple() +{ + Initialize(); +} + +DebugRenderer::Batch DebugRendererSimple::CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount) +{ + BatchImpl *batch = new BatchImpl; + if (inTriangles == nullptr || inTriangleCount == 0) + return batch; + + batch->mTriangles.assign(inTriangles, inTriangles + inTriangleCount); + return batch; +} + +DebugRenderer::Batch DebugRendererSimple::CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount) +{ + BatchImpl *batch = new BatchImpl; + if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0) + return batch; + + // Convert indexed triangle list to triangle list + batch->mTriangles.resize(inIndexCount / 3); + for (size_t t = 0; t < batch->mTriangles.size(); ++t) + { + Triangle &triangle = batch->mTriangles[t]; + triangle.mV[0] = inVertices[inIndices[t * 3 + 0]]; + triangle.mV[1] = inVertices[inIndices[t * 3 + 1]]; + triangle.mV[2] = inVertices[inIndices[t * 3 + 2]]; + } + + return batch; +} + +void DebugRendererSimple::DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode) +{ + // Figure out which LOD to use + const LOD *lod = inGeometry->mLODs.data(); + if (mCameraPosSet) + lod = &inGeometry->GetLOD(Vec3(mCameraPos), inWorldSpaceBounds, inLODScaleSq); + + // Draw the batch + const BatchImpl *batch = static_cast(lod->mTriangleBatch.GetPtr()); + for (const Triangle &triangle : batch->mTriangles) + { + RVec3 v0 = inModelMatrix * Vec3(triangle.mV[0].mPosition); + RVec3 v1 = inModelMatrix * Vec3(triangle.mV[1].mPosition); + RVec3 v2 = inModelMatrix * Vec3(triangle.mV[2].mPosition); + Color color = inModelColor * triangle.mV[0].mColor; + + switch (inDrawMode) + { + case EDrawMode::Wireframe: + DrawLine(v0, v1, color); + DrawLine(v1, v2, color); + DrawLine(v2, v0, color); + break; + + case EDrawMode::Solid: + DrawTriangle(v0, v1, v2, color, inCastShadow); + break; + } + } +} + +JPH_NAMESPACE_END + +#endif // JPH_DEBUG_RENDERER diff --git a/Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.h b/Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.h new file mode 100644 index 00000000..4a23ab75 --- /dev/null +++ b/Dependencies/Jolt/Jolt/Renderer/DebugRendererSimple.h @@ -0,0 +1,88 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#ifndef JPH_DEBUG_RENDERER + #error This file should only be included when JPH_DEBUG_RENDERER is defined +#endif // !JPH_DEBUG_RENDERER + +#include + +JPH_NAMESPACE_BEGIN + +/// Inherit from this class to simplify implementing a debug renderer, start with this implementation: +/// +/// class MyDebugRenderer : public JPH::DebugRendererSimple +/// { +/// public: +/// virtual void DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo, JPH::ColorArg inColor) override +/// { +/// // Implement +/// } +/// +/// virtual void DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2, JPH::RVec3Arg inV3, JPH::ColorArg inColor, ECastShadow inCastShadow) override +/// { +/// // Implement +/// } +/// +/// virtual void DrawText3D(JPH::RVec3Arg inPosition, const string_view &inString, JPH::ColorArg inColor, float inHeight) override +/// { +/// // Implement +/// } +/// }; +/// +/// Note that this class is meant to be a quick start for implementing a debug renderer, it is not the most efficient way to implement a debug renderer. +class JPH_DEBUG_RENDERER_EXPORT DebugRendererSimple : public DebugRenderer +{ +public: + JPH_OVERRIDE_NEW_DELETE + + /// Constructor + DebugRendererSimple(); + + /// Should be called every frame by the application to provide the camera position. + /// This is used to determine the correct LOD for rendering. + void SetCameraPos(RVec3Arg inCameraPos) + { + mCameraPos = inCameraPos; + mCameraPosSet = true; + } + + /// Fallback implementation that uses DrawLine to draw a triangle (override this if you have a version that renders solid triangles) + virtual void DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow) override + { + DrawLine(inV1, inV2, inColor); + DrawLine(inV2, inV3, inColor); + DrawLine(inV3, inV1, inColor); + } + +protected: + /// Implementation of DebugRenderer interface + virtual Batch CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount) override; + virtual Batch CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount) override; + virtual void DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode) override; + +private: + /// Implementation specific batch object + class BatchImpl : public RefTargetVirtual + { + public: + JPH_OVERRIDE_NEW_DELETE + + virtual void AddRef() override { ++mRefCount; } + virtual void Release() override { if (--mRefCount == 0) delete this; } + + Array mTriangles; + + private: + atomic mRefCount = 0; + }; + + /// Last provided camera position + RVec3 mCameraPos; + bool mCameraPosSet = false; +}; + +JPH_NAMESPACE_END diff --git a/Dependencies/Jolt/Jolt/Skeleton/SkeletalAnimation.h b/Dependencies/Jolt/Jolt/Skeleton/SkeletalAnimation.h index 43f7a517..80591209 100644 --- a/Dependencies/Jolt/Jolt/Skeleton/SkeletalAnimation.h +++ b/Dependencies/Jolt/Jolt/Skeleton/SkeletalAnimation.h @@ -17,7 +17,7 @@ class JPH_EXPORT SkeletalAnimation : public RefTarget public: JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, SkeletalAnimation) - /// Constains the current state of a joint, a local space transformation relative to its parent joint + /// Contains the current state of a joint, a local space transformation relative to its parent joint class JointState { public: diff --git a/Dependencies/Jolt/Jolt/Skeleton/SkeletonPose.cpp b/Dependencies/Jolt/Jolt/Skeleton/SkeletonPose.cpp index 856709c4..c64bf049 100644 --- a/Dependencies/Jolt/Jolt/Skeleton/SkeletonPose.cpp +++ b/Dependencies/Jolt/Jolt/Skeleton/SkeletonPose.cpp @@ -47,7 +47,7 @@ void SkeletonPose::CalculateJointStates() JointState &joint = mJoints[i]; joint.mTranslation = local_transform.GetTranslation(); - joint.mRotation = local_transform.GetRotation().GetQuaternion(); + joint.mRotation = local_transform.GetQuaternion(); } } diff --git a/Dependencies/jolt/version-4.0.2 b/Dependencies/Jolt/version-5.0.0 similarity index 100% rename from Dependencies/jolt/version-4.0.2 rename to Dependencies/Jolt/version-5.0.0 diff --git a/Dependencies/jolt/jolt.lua b/Dependencies/jolt/jolt.lua index 6cdd5da0..8f351cb7 100644 --- a/Dependencies/jolt/jolt.lua +++ b/Dependencies/jolt/jolt.lua @@ -1,12 +1,57 @@ -local function SetupLib() - local basePath = path.getabsolute("jolt/", Game.dependencyDir) - local dependencies = { } +local dep = Solution.Util.CreateDepTable("Jolt", {}) + +local function GetJoltDefines() + local additionalDefines = { "JPH_PROFILE_ENABLED", "CROSS_PLATFORM_DETERMINISTIC" } + + local enableDebugRenderer = BuildSettings:Get("Jolt Enable Debug Renderer") + if enableDebugRenderer ~= nil and enableDebugRenderer == true then + table.insert(additionalDefines, "JPH_DEBUG_RENDERER") + end + + local floatPointExceptions = BuildSettings:Get("Jolt Floating Point Exceptions") + if floatPointExceptions ~= nil and floatPointExceptions == true then + table.insert(additionalDefines, "JPH_FLOATING_POINT_EXCEPTIONS_ENABLED") + end + + local doublePrecision = BuildSettings:Get("Jolt Double Precision") + if doublePrecision ~= nil and doublePrecision == true then + table.insert(additionalDefines, "JPH_DOUBLE_PRECISION") + end + + local crossPlatformDeterministic = BuildSettings:Get("Jolt Cross Platform Deterministic") + if crossPlatformDeterministic ~= nil and crossPlatformDeterministic == true then + table.insert(additionalDefines, "JPH_CROSS_PLATFORM_DETERMINISTIC") + end + + local objectLayerBits = BuildSettings:Get("Jolt Object Layer Bits") + if objectLayerBits ~= nil then + if objectLayerBits ~= 16 and objectLayerBits ~= 32 then + error("Jolt Object Layer Bits must be set to either 16 or 32") + end + + table.insert(additionalDefines, "JPH_OBJECT_LAYER_BITS=" .. tostring(objectLayerBits)) + end + + local trackBroadphaseStats = BuildSettings:Get("Jolt Track Broadphase Stats") + if trackBroadphaseStats ~= nil and trackBroadphaseStats == true then + table.insert(additionalDefines, "JPH_TRACK_BROADPHASE_STATS") + end + + local trackNarrowphaseStats = BuildSettings:Get("Jolt Track Narrowphase Stats") + if trackNarrowphaseStats ~= nil and trackNarrowphaseStats == true then + table.insert(additionalDefines, "JPH_TRACK_NARROWPHASE_STATS") + end + + return additionalDefines +end + +Solution.Util.CreateStaticLib(dep.Name, Solution.Projects.Current.BinDir, dep.Dependencies, function() local defines = { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS", "_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS" } - ProjectTemplate("Jolt", "StaticLib", nil, Game.binDir, dependencies, defines) + Solution.Util.SetLanguage("C++") + Solution.Util.SetCppDialect(20) - local sourceDir = path.getabsolute("Jolt", basePath) - local includeDir = { basePath, sourceDir } + local sourceDir = dep.Path .. "/Jolt" local files = { sourceDir .. "/AABBTree/AABBTreeBuilder.cpp", @@ -434,113 +479,24 @@ local function SetupLib() sourceDir .. "/TriangleSplitter/TriangleSplitterMorton.cpp", sourceDir .. "/TriangleSplitter/TriangleSplitterMorton.h" } - AddFiles(files) - - AddIncludeDirs(includeDir) - - local enableDebugRenderer = BuildSettings:Get("Jolt Enable Debug Renderer") - if enableDebugRenderer ~= nil and enableDebugRenderer == true then - AddDefines({"JPH_DEBUG_RENDERER"}) - end - - local floatPointExceptions = BuildSettings:Get("Jolt Floating Point Exceptions") - if floatPointExceptions ~= nil and floatPointExceptions == true then - AddDefines({"JPH_FLOATING_POINT_EXCEPTIONS_ENABLED"}) - end - - local doublePrecision = BuildSettings:Get("Jolt Double Precision") - if doublePrecision ~= nil and doublePrecision == true then - AddDefines({"JPH_DOUBLE_PRECISION"}) - end - - local crossPlatformDeterministic = BuildSettings:Get("Jolt Cross Platform Deterministic") - if crossPlatformDeterministic ~= nil and crossPlatformDeterministic == true then - AddDefines({"JPH_CROSS_PLATFORM_DETERMINISTIC"}) - end - - local objectLayerBits = BuildSettings:Get("Jolt Object Layer Bits") - if objectLayerBits ~= nil then - if objectLayerBits ~= 16 and objectLayerBits ~= 32 then - error("Jolt Object Layer Bits must be set to either 16 or 32") - end - - AddDefines({"JPH_OBJECT_LAYER_BITS=" .. tostring(objectLayerBits)}) - end - - local trackBroadphaseStats = BuildSettings:Get("Jolt Track Broadphase Stats") - if trackBroadphaseStats ~= nil and trackBroadphaseStats == true then - AddDefines({"JPH_TRACK_BROADPHASE_STATS"}) - end - - local trackNarrowphaseStats = BuildSettings:Get("Jolt Track Narrowphase Stats") - if trackNarrowphaseStats ~= nil and trackNarrowphaseStats == true then - AddDefines({"JPH_TRACK_NARROWPHASE_STATS"}) - end + Solution.Util.SetFiles(files) + Solution.Util.SetIncludes({ dep.Path, sourceDir }) + Solution.Util.SetDefines(defines) -- TODO : Support EMIT_X86_INSTRUCTION_SET_DEFINITIONS - filter "configurations:Debug" - AddDefines({"_DEBUG"}) - - filter "configurations:RelDebug" - AddDefines({"NDEBUG"}) - - filter "configurations:Release" - AddDefines({"NDEBUG"}) - - filter { } - AddDefines({"JPH_PROFILE_ENABLED", "CROSS_PLATFORM_DETERMINISTIC"}) - AddLinks("wininet") -end -SetupLib() - -local function Include() - local basePath = path.getabsolute("jolt/", Game.dependencyDir) - local includeDir = basePath - - AddIncludeDirs(includeDir) - - local enableDebugRenderer = BuildSettings:Get("Jolt Enable Debug Renderer") - if enableDebugRenderer ~= nil and enableDebugRenderer == true then - AddDefines({"JPH_DEBUG_RENDERER"}) - end - - local floatPointExceptions = BuildSettings:Get("Jolt Floating Point Exceptions") - if floatPointExceptions ~= nil and floatPointExceptions == true then - AddDefines({"JPH_FLOATING_POINT_EXCEPTIONS_ENABLED"}) - end - - local doublePrecision = BuildSettings:Get("Jolt Double Precision") - if doublePrecision ~= nil and doublePrecision == true then - AddDefines({"JPH_DOUBLE_PRECISION"}) - end + local additionalDefines = GetJoltDefines() + Solution.Util.SetDefines(additionalDefines) - local crossPlatformDeterministic = BuildSettings:Get("Jolt Cross Platform Deterministic") - if crossPlatformDeterministic ~= nil and crossPlatformDeterministic == true then - AddDefines({"JPH_CROSS_PLATFORM_DETERMINISTIC"}) - end + Solution.Util.SetFilter("platforms:Win64", function() + Solution.Util.SetLinks({ "wininet" }) + end) +end) - local objectLayerBits = BuildSettings:Get("Jolt Object Layer Bits") - if objectLayerBits ~= nil then - if objectLayerBits ~= 16 and objectLayerBits ~= 32 then - error("Jolt Object Layer Bits must be set to either 16 or 32") - end - - AddDefines({"JPH_OBJECT_LAYER_BITS=" .. tostring(objectLayerBits)}) - end - - local trackBroadphaseStats = BuildSettings:Get("Jolt Track Broadphase Stats") - if trackBroadphaseStats ~= nil and trackBroadphaseStats == true then - AddDefines({"JPH_TRACK_BROADPHASE_STATS"}) - end - - local trackNarrowphaseStats = BuildSettings:Get("Jolt Track Narrowphase Stats") - if trackNarrowphaseStats ~= nil and trackNarrowphaseStats == true then - AddDefines({"JPH_TRACK_NARROWPHASE_STATS"}) - end - - AddDefines({"JPH_PROFILE_ENABLED", "CROSS_PLATFORM_DETERMINISTIC"}) - - AddLinks("Jolt") -end -CreateDep("jolt", Include) \ No newline at end of file +Solution.Util.CreateDep(dep.NameLow, dep.Dependencies, function() + Solution.Util.SetIncludes(dep.Path) + Solution.Util.SetLinks(dep.Name) + + local additionalDefines = GetJoltDefines() + Solution.Util.SetDefines(additionalDefines) +end) \ No newline at end of file diff --git a/Dependencies/meshoptimizer/.clang-format b/Dependencies/meshoptimizer/.clang-format deleted file mode 100644 index 039f5100..00000000 --- a/Dependencies/meshoptimizer/.clang-format +++ /dev/null @@ -1,13 +0,0 @@ -Standard: Cpp03 -UseTab: ForIndentation -TabWidth: 4 -IndentWidth: 4 -AccessModifierOffset: -4 -BreakBeforeBraces: Allman -IndentCaseLabels: false -ColumnLimit: 0 -PointerAlignment: Left -BreakConstructorInitializersBeforeComma: true -NamespaceIndentation: None -AlignEscapedNewlines: DontAlign -AlignAfterOpenBracket: DontAlign diff --git a/Dependencies/meshoptimizer/.editorconfig b/Dependencies/meshoptimizer/.editorconfig deleted file mode 100644 index 09eb07cf..00000000 --- a/Dependencies/meshoptimizer/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -# See https://editorconfig.org/ for more info - -[*] -charset = utf-8 -indent_style = tab -indent_size = 4 -trim_trailing_whitespace = true -insert_final_newline = true diff --git a/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/bug_report.md b/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index ab8ecead..00000000 --- a/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Bug report -about: Create a report if you believe you've found a bug in this project; please use GitHub Discussions instead if you think the bug may be in your code. -title: '' -labels: bug -assignees: '' - ---- diff --git a/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/config.yml b/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 28b90188..00000000 --- a/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Help and support - url: https://github.com/zeux/meshoptimizer/discussions - about: Please use GitHub Discussions if you have questions or need help. diff --git a/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/feature_request.md b/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 83826c66..00000000 --- a/Dependencies/meshoptimizer/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- diff --git a/Dependencies/meshoptimizer/.github/workflows/build.yml b/Dependencies/meshoptimizer/.github/workflows/build.yml deleted file mode 100644 index 43b5f8b2..00000000 --- a/Dependencies/meshoptimizer/.github/workflows/build.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: build - -on: - push: - branches: - - 'master' - pull_request: - -jobs: - unix: - strategy: - matrix: - os: [ubuntu, macos] - name: ${{matrix.os}} - runs-on: ${{matrix.os}}-latest - steps: - - uses: actions/checkout@v1 - - name: make test - run: | - make -j2 config=sanitize test - make -j2 config=debug test - make -j2 config=release test - make -j2 config=coverage test - - name: make gltfpack - run: | - make -j2 config=release gltfpack - strip gltfpack - - name: upload coverage - run: | - find . -type f -name '*.gcno' -exec gcov -p {} + - sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov - bash <(curl -s https://codecov.io/bash) -f './src*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}} - - windows: - runs-on: windows-latest - strategy: - matrix: - arch: [Win32, x64] - steps: - - uses: actions/checkout@v1 - - name: cmake configure - run: cmake . -DMESHOPT_BUILD_DEMO=ON -DMESHOPT_BUILD_GLTFPACK=ON -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -A ${{matrix.arch}} - - name: cmake test - shell: bash # necessary for fail-fast - run: | - cmake --build . -- -property:Configuration=Debug -verbosity:minimal - Debug/demo.exe demo/pirate.obj - cmake --build . -- -property:Configuration=Release -verbosity:minimal - Release/demo.exe demo/pirate.obj - - nodejs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: '16' - - name: test decoder - run: node js/meshopt_decoder.test.js - - name: test simd decoder - run: node --experimental-wasm-simd js/meshopt_decoder.test.js - - name: test encoder - run: node js/meshopt_encoder.test.js - - gltfpack: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/checkout@v2 - with: - repository: KhronosGroup/glTF-Sample-Models - path: glTF-Sample-Models - - name: make - run: make -j2 config=sanitize gltfpack - - name: test - run: find glTF-Sample-Models/2.0/ -name *.gltf -or -name *.glb | xargs -d '\n' ./gltfpack -cc -test - - name: pack - run: find glTF-Sample-Models/2.0/ -name *.gltf | grep -v glTF-Draco | grep -v glTF-KTX-BasisU | xargs -d '\n' -I '{}' ./gltfpack -i '{}' -o '{}pack.gltf' - - name: validate - run: | - curl -sL $VALIDATOR | tar xJ - find glTF-Sample-Models/2.0/ -name *.gltfpack.gltf | xargs -d '\n' -L 1 ./gltf_validator -r -a - env: - VALIDATOR: https://github.com/KhronosGroup/glTF-Validator/releases/download/2.0.0-dev.3.3/gltf_validator-2.0.0-dev.3.3-linux64.tar.xz - - gltfpackjs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: '14.x' - - name: install wasi - run: | - curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sdk-$VERSION.0-linux.tar.gz | tar xz - curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/libclang_rt.builtins-wasm32-wasi-$VERSION.0.tar.gz | tar xz -C wasi-sdk-$VERSION.0 - curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sysroot-$VERSION.0.tar.gz | tar xz - mv wasi-sdk-$VERSION.0 wasi-sdk - env: - VERSION: 14 - - name: build - run: | - make -B WASMCC=wasi-sdk/bin/clang++ WASI_SDK=./wasi-sysroot gltf/library.wasm - make -B WASMCC=wasi-sdk/bin/clang++ WASI_SDK=./wasi-sysroot js/meshopt_decoder.js js/meshopt_decoder.module.js - make -B WASMCC=wasi-sdk/bin/clang++ WASI_SDK=./wasi-sysroot js/meshopt_encoder.js js/meshopt_encoder.module.js - - name: test - run: | - node gltf/cli.js -i demo/pirate.obj -o pirate.glb -v - node gltf/cli.js -i `pwd`/pirate.glb -o pirate-repack.glb -cc -v - wc -c pirate.glb pirate-repack.glb - node js/meshopt_decoder.test.js - node js/meshopt_encoder.test.js - - name: npm pack - run: | - cd gltf && npm pack && cd .. - cd js && npm pack && cd .. - - uses: actions/upload-artifact@v2 - with: - name: gltfpack-npm - path: gltf/gltfpack-*.tgz - - uses: actions/upload-artifact@v2 - with: - name: meshoptimizer-npm - path: js/meshoptimizer-*.tgz - - arm64: - runs-on: ubuntu-latest - steps: - - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset - - uses: docker://multiarch/ubuntu-core:arm64-focal - with: - args: 'uname -a' - - uses: actions/checkout@v1 - - name: make test - uses: docker://multiarch/ubuntu-core:arm64-focal - with: - args: 'bash -c "apt-get update && apt-get install -y build-essential && make -j2 config=coverage test"' - - name: upload coverage - run: | - find . -type f -name '*.gcno' -exec gcov -p {} + - sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov - bash <(curl -s https://codecov.io/bash) -f './src*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}} - - iphone: - runs-on: macos-latest - steps: - - uses: actions/checkout@v1 - - name: make - run: make -j2 config=iphone diff --git a/Dependencies/meshoptimizer/.github/workflows/release.yml b/Dependencies/meshoptimizer/.github/workflows/release.yml deleted file mode 100644 index 92b32742..00000000 --- a/Dependencies/meshoptimizer/.github/workflows/release.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: release - -on: - push: - branches: - - 'master' - pull_request: - -jobs: - gltfpack: - strategy: - matrix: - os: [windows, ubuntu, macos] - name: gltfpack-${{matrix.os}} - runs-on: ${{matrix.os}}-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/checkout@v2 - with: - repository: zeux/basis_universal - ref: gltfpack - path: basis_universal - - name: cmake configure - run: cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_BASISU_PATH=basis_universal -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded" -DCMAKE_BUILD_TYPE=Release - - name: cmake build - run: cmake --build . --target gltfpack --config Release - - uses: actions/upload-artifact@v1 - with: - name: gltfpack-windows - path: Release/gltfpack.exe - if: matrix.os == 'windows' - - uses: actions/upload-artifact@v1 - with: - name: gltfpack-${{matrix.os}} - path: gltfpack - if: matrix.os != 'windows' diff --git a/Dependencies/meshoptimizer/.gitignore b/Dependencies/meshoptimizer/.gitignore deleted file mode 100644 index 6519d256..00000000 --- a/Dependencies/meshoptimizer/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build/ -/data/ -/gltf/library.wasm diff --git a/Dependencies/meshoptimizer/CMakeLists.txt b/Dependencies/meshoptimizer/CMakeLists.txt deleted file mode 100644 index ed1a84c7..00000000 --- a/Dependencies/meshoptimizer/CMakeLists.txt +++ /dev/null @@ -1,151 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -if(POLICY CMP0077) - cmake_policy(SET CMP0077 NEW) # Enables override of options from parent CMakeLists.txt -endif() - -if(POLICY CMP0091) - cmake_policy(SET CMP0091 NEW) # Enables use of MSVC_RUNTIME_LIBRARY -endif() -if(POLICY CMP0092) - cmake_policy(SET CMP0092 NEW) # Enables clean /W4 override for MSVC -endif() - -project(meshoptimizer VERSION 0.17 LANGUAGES CXX) - -option(MESHOPT_BUILD_DEMO "Build demo" OFF) -option(MESHOPT_BUILD_GLTFPACK "Build gltfpack" OFF) -option(MESHOPT_BUILD_SHARED_LIBS "Build shared libraries" OFF) -set(MESHOPT_BASISU_PATH "" CACHE STRING "") - -set(SOURCES - src/meshoptimizer.h - src/allocator.cpp - src/clusterizer.cpp - src/indexcodec.cpp - src/indexgenerator.cpp - src/overdrawanalyzer.cpp - src/overdrawoptimizer.cpp - src/simplifier.cpp - src/spatialorder.cpp - src/stripifier.cpp - src/vcacheanalyzer.cpp - src/vcacheoptimizer.cpp - src/vertexcodec.cpp - src/vertexfilter.cpp - src/vfetchanalyzer.cpp - src/vfetchoptimizer.cpp -) - -set(GLTF_SOURCES - gltf/animation.cpp - gltf/basisenc.cpp - gltf/basislib.cpp - gltf/fileio.cpp - gltf/gltfpack.cpp - gltf/image.cpp - gltf/json.cpp - gltf/material.cpp - gltf/mesh.cpp - gltf/node.cpp - gltf/parseobj.cpp - gltf/parsegltf.cpp - gltf/stream.cpp - gltf/write.cpp -) - -if(MSVC) - add_compile_options(/W4 /WX) -else() - add_compile_options(-Wall -Wextra -Wshadow -Wno-missing-field-initializers -Werror) -endif() - -if(MESHOPT_BUILD_SHARED_LIBS) - add_library(meshoptimizer SHARED ${SOURCES}) -else() - add_library(meshoptimizer STATIC ${SOURCES}) -endif() - -target_include_directories(meshoptimizer INTERFACE "$") - -if(MESHOPT_BUILD_SHARED_LIBS) - set_target_properties(meshoptimizer PROPERTIES CXX_VISIBILITY_PRESET hidden) - set_target_properties(meshoptimizer PROPERTIES VISIBILITY_INLINES_HIDDEN ON) - - if(WIN32) - target_compile_definitions(meshoptimizer INTERFACE "MESHOPTIMIZER_API=__declspec(dllimport)") - target_compile_definitions(meshoptimizer PRIVATE "MESHOPTIMIZER_API=__declspec(dllexport)") - else() - target_compile_definitions(meshoptimizer PUBLIC "MESHOPTIMIZER_API=__attribute__((visibility(\"default\")))") - endif() -endif() - -set(TARGETS meshoptimizer) - -if(MESHOPT_BUILD_DEMO) - add_executable(demo demo/main.cpp demo/tests.cpp tools/meshloader.cpp) - target_link_libraries(demo meshoptimizer) -endif() - -if(MESHOPT_BUILD_GLTFPACK) - add_executable(gltfpack ${GLTF_SOURCES} tools/meshloader.cpp) - set_target_properties(gltfpack PROPERTIES CXX_STANDARD 11) - target_link_libraries(gltfpack meshoptimizer) - list(APPEND TARGETS gltfpack) - - if(MESHOPT_BUILD_SHARED_LIBS) - string(CONCAT RPATH "$ORIGIN/../" ${CMAKE_INSTALL_LIBDIR}) - set_target_properties(gltfpack PROPERTIES INSTALL_RPATH ${RPATH}) - endif() - - if(NOT MESHOPT_BASISU_PATH STREQUAL "") - get_filename_component(BASISU_PATH ${MESHOPT_BASISU_PATH} ABSOLUTE) - - target_compile_definitions(gltfpack PRIVATE WITH_BASISU) - set_source_files_properties(gltf/basisenc.cpp gltf/basislib.cpp PROPERTIES INCLUDE_DIRECTORIES ${BASISU_PATH}) - - if(NOT MSVC AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64") - set_source_files_properties(gltf/basislib.cpp PROPERTIES COMPILE_OPTIONS -msse4.1) - endif() - - if(UNIX) - target_link_libraries(gltfpack pthread) - endif() - endif() -endif() - -include(GNUInstallDirs) - -install(TARGETS ${TARGETS} EXPORT meshoptimizerTargets - COMPONENT meshoptimizer - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - -install(FILES src/meshoptimizer.h COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(EXPORT meshoptimizerTargets COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer NAMESPACE meshoptimizer::) - -# TARGET_PDB_FILE is available since 3.1 -if(MSVC AND NOT (CMAKE_VERSION VERSION_LESS "3.1")) - foreach(TARGET ${TARGETS}) - get_target_property(TARGET_TYPE ${TARGET} TYPE) - if(NOT ${TARGET_TYPE} STREQUAL "STATIC_LIBRARY") - install(FILES $ COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) - endif() - endforeach(TARGET) -endif() - -include(CMakePackageConfigHelpers) - -configure_package_config_file(config.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer NO_SET_AND_CHECK_MACRO) - -write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake COMPATIBILITY ExactVersion) - -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake - ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake - COMPONENT meshoptimizer - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer) diff --git a/Dependencies/meshoptimizer/CONTRIBUTING.md b/Dependencies/meshoptimizer/CONTRIBUTING.md deleted file mode 100644 index 8d40ee72..00000000 --- a/Dependencies/meshoptimizer/CONTRIBUTING.md +++ /dev/null @@ -1,54 +0,0 @@ -Thanks for deciding to contribute to meshoptimizer! These guidelines will try to help make the process painless and efficient. - -## Questions - -If you have a question regarding the library usage, please [open a GitHub issue](https://github.com/zeux/meshoptimizer/issues/new). -Some questions just need answers, but it's nice to keep them for future reference in case other people want to know the same thing. -Some questions help improve the library interface or documentation by inspiring future changes. - -## Bugs - -If the library doesn't compile on your system, compiles with warnings, doesn't seem to run correctly for your input data or if anything else is amiss, please [open a GitHub issue](https://github.com/zeux/meshoptimizer/issues/new). -It helps if you note the version of the library this issue happens in, the version of your compiler for compilation issues, and a reproduction case for runtime bugs. - -Of course, feel free to [create a pull request](https://help.github.com/articles/about-pull-requests/) to fix the bug yourself. - -## Features - -New algorithms and improvements to existing algorithms are always welcome; you can open an issue or make the change yourself and submit a pull request. - -For major features, consider opening an issue describing an improvement you'd like to see or make before opening a pull request. -This will give us a chance to discuss the idea before implementing it - some algorithms may not be easy to integrate into existing programs, may not be robust to arbitrary meshes or may be expensive to run or implement/maintain, so a discussion helps make sure these don't block the algorithm development. - -## Code style - -Contributions to this project are expected to follow the existing code style. -`.clang-format` file mostly defines syntactic styling rules (you can run `make format` to format the code accordingly). - -As for naming conventions, this library uses `snake_case` for variables, `lowerCamelCase` for functions, `UpperCamelCase` for types, `kCamelCase` for global constants and `SCARY_CASE` for macros. All public functions/types must additionally have an extra `meshopt_` prefix to avoid symbol conflicts. - -## Dependencies - -Please note that this library uses C89 interface for all APIs and a C++98 implementation - C++11 features can not be used. -This choice is made to maximize compatibility to make sure that any toolchain, including legacy proprietary gaming console toolchains, can compile this code. - -Additionally, the library code has zero external dependencies, does not depend on STL and does not use RTTI or exceptions. -This, again, maximizes compatibility and makes sure the library can be used in environments where STL use is discouraged or prohibited, as well as maximizing runtime performance and minimizing compilation times. - -The demo program uses STL since it serves as an example of usage and as a test harness, not as production-ready code. - -## Testing - -All pull requests will run through a continuous integration pipeline using GitHub Actions that will run the built-in unit tests and integration tests on Windows, macOS and Linux with gcc, clang and msvc compilers. -You can run the tests yourself using `make test` or building the demo program with `cmake -DBUILD_DEMO=ON` and running it. - -Unit tests can be found in `demo/tests.cpp` and functional tests - in `demo/main.cpp`; when making code changes please try to make sure they are covered by an existing test or add a new test accordingly. - -## Documentation - -Documentation for this library resides in the `meshoptimizer.h` header, with examples as part of a usage manual available in `README.md`. -Changes to documentation are always welcome and should use issues/pull requests as outlined above; please note that `README.md` only contains documentation for stable algorithms, as experimental algorithms may change the interface without concern for backwards compatibility. - -## Sensitive communication - -If you prefer to not disclose the issues or information relevant to the issue such as reproduction case to the public, you can always contact the author via e-mail (arseny.kapoulkine@gmail.com). diff --git a/Dependencies/meshoptimizer/LICENSE.md b/Dependencies/meshoptimizer/LICENSE.md deleted file mode 100644 index 3c52415f..00000000 --- a/Dependencies/meshoptimizer/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016-2021 Arseny Kapoulkine - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Dependencies/meshoptimizer/Makefile b/Dependencies/meshoptimizer/Makefile deleted file mode 100644 index 69cf8678..00000000 --- a/Dependencies/meshoptimizer/Makefile +++ /dev/null @@ -1,189 +0,0 @@ -MAKEFLAGS+=-r -j - -config=debug -files=demo/pirate.obj - -BUILD=build/$(config) - -LIBRARY_SOURCES=$(wildcard src/*.cpp) -LIBRARY_OBJECTS=$(LIBRARY_SOURCES:%=$(BUILD)/%.o) - -DEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/meshloader.cpp -DEMO_OBJECTS=$(DEMO_SOURCES:%=$(BUILD)/%.o) - -GLTFPACK_SOURCES=$(wildcard gltf/*.cpp) tools/meshloader.cpp -GLTFPACK_OBJECTS=$(GLTFPACK_SOURCES:%=$(BUILD)/%.o) - -OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(GLTFPACK_OBJECTS) - -LIBRARY=$(BUILD)/libmeshoptimizer.a -DEMO=$(BUILD)/meshoptimizer - -CFLAGS=-g -Wall -Wextra -Werror -std=c89 -CXXFLAGS=-g -Wall -Wextra -Wshadow -Wno-missing-field-initializers -Werror -std=c++98 -LDFLAGS= - -$(GLTFPACK_OBJECTS): CXXFLAGS+=-std=c++11 - -ifdef BASISU - $(GLTFPACK_OBJECTS): CXXFLAGS+=-DWITH_BASISU - $(BUILD)/gltf/basis%.cpp.o: CXXFLAGS+=-I$(BASISU) - gltfpack: LDFLAGS+=-lpthread - - ifeq ($(HOSTTYPE),x86_64) - $(BUILD)/gltf/basislib.cpp.o: CXXFLAGS+=-msse4.1 - endif -endif - -WASMCC=clang++ -WASI_SDK= - -WASM_FLAGS=--target=wasm32-wasi --sysroot=$(WASI_SDK) -WASM_FLAGS+=-O3 -DNDEBUG -nostartfiles -nostdlib -Wl,--no-entry -Wl,-s -WASM_FLAGS+=-fno-slp-vectorize -fno-vectorize -fno-unroll-loops -WASM_FLAGS+=-Wl,-z -Wl,stack-size=24576 -Wl,--initial-memory=65536 -WASM_EXPORT_PREFIX=-Wl,--export - -WASM_DECODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp tools/wasmstubs.cpp -WASM_DECODER_EXPORTS=meshopt_decodeVertexBuffer meshopt_decodeIndexBuffer meshopt_decodeIndexSequence meshopt_decodeFilterOct meshopt_decodeFilterQuat meshopt_decodeFilterExp sbrk __wasm_call_ctors - -WASM_ENCODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp src/vcacheoptimizer.cpp src/vfetchoptimizer.cpp tools/wasmstubs.cpp -WASM_ENCODER_EXPORTS=meshopt_encodeVertexBuffer meshopt_encodeVertexBufferBound meshopt_encodeIndexBuffer meshopt_encodeIndexBufferBound meshopt_encodeIndexSequence meshopt_encodeIndexSequenceBound meshopt_encodeVertexVersion meshopt_encodeIndexVersion meshopt_encodeFilterOct meshopt_encodeFilterQuat meshopt_encodeFilterExp meshopt_optimizeVertexCache meshopt_optimizeVertexCacheStrip meshopt_optimizeVertexFetchRemap sbrk __wasm_call_ctors - -WASM_SIMPLIFIER_SOURCES=src/simplifier.cpp tools/wasmstubs.cpp -WASM_SIMPLIFIER_EXPORTS=meshopt_simplify meshopt_simplifySloppy meshopt_simplifyPoints sbrk __wasm_call_ctors - -ifeq ($(config),iphone) - IPHONESDK=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk - CFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) - CXXFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -stdlib=libc++ - LDFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -L $(IPHONESDK)/usr/lib -mios-version-min=7.0 -endif - -ifeq ($(config),trace) - CXXFLAGS+=-DTRACE=1 -endif - -ifeq ($(config),scalar) - CXXFLAGS+=-O3 -DNDEBUG -DMESHOPTIMIZER_NO_SIMD -endif - -ifeq ($(config),release) - CXXFLAGS+=-O3 -DNDEBUG -endif - -ifeq ($(config),coverage) - CXXFLAGS+=-coverage - LDFLAGS+=-coverage -endif - -ifeq ($(config),sanitize) - CXXFLAGS+=-fsanitize=address,undefined -fno-sanitize-recover=all - LDFLAGS+=-fsanitize=address,undefined -endif - -ifeq ($(config),analyze) - CXXFLAGS+=--analyze -endif - -all: $(DEMO) - -test: $(DEMO) - $(DEMO) $(files) - -check: $(DEMO) - $(DEMO) - -dev: $(DEMO) - $(DEMO) -d $(files) - -format: - clang-format -i $(LIBRARY_SOURCES) $(DEMO_SOURCES) $(GLTFPACK_SOURCES) - -js: js/meshopt_decoder.js js/meshopt_decoder.module.js js/meshopt_encoder.js js/meshopt_encoder.module.js js/meshopt_simplifier.js js/meshopt_simplifier.module.js - -gltfpack: $(BUILD)/gltfpack - ln -fs $^ $@ - -$(BUILD)/gltfpack: $(GLTFPACK_OBJECTS) $(LIBRARY) - $(CXX) $^ $(LDFLAGS) -o $@ - -gltfpack.wasm: gltf/library.wasm - -gltf/library.wasm: ${LIBRARY_SOURCES} ${GLTFPACK_SOURCES} tools/meshloader.cpp - $(WASMCC) $^ -o $@ -Os -DNDEBUG --target=wasm32-wasi --sysroot=$(WASI_SDK) -nostartfiles -Wl,--no-entry -Wl,--export=pack -Wl,--export=malloc -Wl,--export=free -Wl,--export=__wasm_call_ctors -Wl,-s -Wl,--allow-undefined-file=gltf/wasistubs.txt - -build/decoder_base.wasm: $(WASM_DECODER_SOURCES) - @mkdir -p build - $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@ - -build/decoder_simd.wasm: $(WASM_DECODER_SOURCES) - @mkdir -p build - $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@ -msimd128 -mbulk-memory - -js/meshopt_decoder.js: build/decoder_base.wasm build/decoder_simd.wasm - sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1)#" $@ - sed -i "s#\(var wasm_base = \)\".*\";#\\1\"$$(cat build/decoder_base.wasm | python3 tools/wasmpack.py)\";#" $@ - sed -i "s#\(var wasm_simd = \)\".*\";#\\1\"$$(cat build/decoder_simd.wasm | python3 tools/wasmpack.py)\";#" $@ - -build/encoder.wasm: $(WASM_ENCODER_SOURCES) - @mkdir -p build - $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_ENCODER_EXPORTS)) -lc -o $@ - -js/meshopt_encoder.js: build/encoder.wasm - sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1)#" $@ - sed -i "s#\(var wasm = \)\".*\";#\\1\"$$(cat build/encoder.wasm | python3 tools/wasmpack.py)\";#" $@ - -build/simplifier.wasm: $(WASM_SIMPLIFIER_SOURCES) - @mkdir -p build - $(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_SIMPLIFIER_EXPORTS)) -lc -o $@ - -js/meshopt_simplifier.js: build/simplifier.wasm - sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1)#" $@ - sed -i "s#\(var wasm = \)\".*\";#\\1\"$$(cat build/simplifier.wasm | python3 tools/wasmpack.py)\";#" $@ - -js/%.module.js: js/%.js - sed '/UMD-style export/,$$d' <$< >$@ - sed -n "s#\s*module.exports = \(.*\);#export { \\1 };#p" <$< >>$@ - -$(DEMO): $(DEMO_OBJECTS) $(LIBRARY) - $(CXX) $^ $(LDFLAGS) -o $@ - -vcachetuner: tools/vcachetuner.cpp $(BUILD)/tools/meshloader.cpp.o $(BUILD)/demo/miniz.cpp.o $(LIBRARY) - $(CXX) $^ -fopenmp $(CXXFLAGS) -std=c++11 $(LDFLAGS) -o $@ - -codecbench: tools/codecbench.cpp $(LIBRARY) - $(CXX) $^ $(CXXFLAGS) $(LDFLAGS) -o $@ - -codecbench.js: tools/codecbench.cpp ${LIBRARY_SOURCES} - emcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -o $@ - -codecbench-simd.js: tools/codecbench.cpp ${LIBRARY_SOURCES} - emcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -msimd128 -o $@ - -codecbench.wasm: tools/codecbench.cpp ${LIBRARY_SOURCES} - $(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASI_SDK) -lc++ -lc++abi -O3 -g -DNDEBUG -o $@ - -codecbench-simd.wasm: tools/codecbench.cpp ${LIBRARY_SOURCES} - $(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASI_SDK) -lc++ -lc++abi -O3 -g -DNDEBUG -msimd128 -o $@ - -codecfuzz: tools/codecfuzz.cpp src/vertexcodec.cpp src/indexcodec.cpp - $(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@ - -$(LIBRARY): $(LIBRARY_OBJECTS) - ar rcs $@ $^ - -$(BUILD)/%.cpp.o: %.cpp - @mkdir -p $(dir $@) - $(CXX) $< $(CXXFLAGS) -c -MMD -MP -o $@ - -$(BUILD)/%.c.o: %.c - @mkdir -p $(dir $@) - $(CC) $< $(CFLAGS) -c -MMD -MP -o $@ - --include $(OBJECTS:.o=.d) - -clean: - rm -rf $(BUILD) - -.PHONY: all clean format diff --git a/Dependencies/meshoptimizer/README.md b/Dependencies/meshoptimizer/README.md deleted file mode 100644 index 43585b95..00000000 --- a/Dependencies/meshoptimizer/README.md +++ /dev/null @@ -1,372 +0,0 @@ -# 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer) - -## Purpose - -When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead. - -The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). - -[gltfpack](gltf), which is a tool that can automatically optimize glTF files, is developed and distributed alongside the library. - -## Installing - -meshoptimizer is hosted on GitHub; you can download the latest release using git: - -``` -git clone -b v0.17 https://github.com/zeux/meshoptimizer.git -``` - -Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.15.zip). - -The library is also available as a package ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer), [Vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer)). - -### Installing gltfpack - -`gltfpack` is a CLI tool for optimizing meshes using meshoptimizer. - -You can download a pre-built binary for gltfpack on [Releases page](https://github.com/zeux/meshoptimizer/releases), or install [npm package](https://www.npmjs.com/package/gltfpack) as follows: - -``` -npm install -g gltfpack -``` - -You can also find prebuilt binaries of `gltfpack` built from master on [Actions page](https://github.com/zeux/meshoptimizer/actions). - -[Learn more about gltfpack](./gltf/README.md) - -## Building - -meshoptimizer is distributed as a set of C++ source files. To include it into your project, you can use one of the two options: - -* Use CMake to build the library (either as a standalone project or as part of your project) -* Add source files to your project's build system - -The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the files for the algorithms you use. - -## Pipeline - -When optimizing a mesh, you should typically feed it through a set of optimizations (the order is important!): - -1. Indexing -2. (optional, discussed last) Simplification -3. Vertex cache optimization -4. Overdraw optimization -5. Vertex fetch optimization -6. Vertex quantization -7. (optional) Vertex/index buffer compression - -## Indexing - -Most algorithms in this library assume that a mesh has a vertex buffer and an index buffer. For algorithms to work well and also for GPU to render your mesh efficiently, the vertex buffer has to have no redundant vertices; you can generate an index buffer from an unindexed vertex buffer or reindex an existing (potentially redundant) index buffer as follows: - -First, generate a remap table from your existing vertex (and, optionally, index) data: - -```c++ -size_t index_count = face_count * 3; -std::vector remap(index_count); // allocate temporary memory for the remap table -size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count, &unindexed_vertices[0], index_count, sizeof(Vertex)); -``` - -Note that in this case we only have an unindexed vertex buffer; the remap table is generated based on binary equivalence of the input vertices, so the resulting mesh will render the same way. Binary equivalence considers all input bytes, including padding which should be zero-initialized if the vertex structure has gaps. - -After generating the remap table, you can allocate space for the target vertex buffer (`vertex_count` elements) and index buffer (`index_count` elements) and generate them: - -```c++ -meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]); -meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], index_count, sizeof(Vertex), &remap[0]); -``` - -You can then further optimize the resulting buffers by calling the other functions on them in-place. - -## Vertex cache optimization - -When the GPU renders the mesh, it has to run the vertex shader for each vertex; usually GPUs have a built-in fixed size cache that stores the transformed vertices (the result of running the vertex shader), and uses this cache to reduce the number of vertex shader invocations. This cache is usually small, 16-32 vertices, and can have different replacement policies; to use this cache efficiently, you have to reorder your triangles to maximize the locality of reused vertex references like so: - -```c++ -meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count); -``` - -## Overdraw optimization - -After transforming the vertices, GPU sends the triangles for rasterization which results in generating pixels that are usually first ran through the depth test, and pixels that pass it get the pixel shader executed to generate the final color. As pixel shaders get more expensive, it becomes more and more important to reduce overdraw. While in general improving overdraw requires view-dependent operations, this library provides an algorithm to reorder triangles to minimize the overdraw from all directions, which you should run after vertex cache optimization like this: - -```c++ -meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f); -``` - -The overdraw optimizer needs to read vertex positions as a float3 from the vertex; the code snippet above assumes that the vertex stores position as `float x, y, z`. - -When performing the overdraw optimization you have to specify a floating-point threshold parameter. The algorithm tries to maintain a balance between vertex cache efficiency and overdraw; the threshold determines how much the algorithm can compromise the vertex cache hit ratio, with 1.05 meaning that the resulting ratio should be at most 5% worse than before the optimization. - -## Vertex fetch optimization - -After the final triangle order has been established, we still can optimize the vertex buffer for memory efficiency. Before running the vertex shader GPU has to fetch the vertex attributes from the vertex buffer; the fetch is usually backed by a memory cache, and as such optimizing the data for the locality of memory access is important. You can do this by running this code: - -To optimize the index/vertex buffers for vertex fetch efficiency, call: - -```c++ -meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex)); -``` - -This will reorder the vertices in the vertex buffer to try to improve the locality of reference, and rewrite the indices in place to match; if the vertex data is stored using multiple streams, you should use `meshopt_optimizeVertexFetchRemap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order. - -Note that the algorithm does not try to model cache replacement precisely and instead just orders vertices in the order of use, which generally produces results that are close to optimal. - -## Vertex quantization - -To optimize memory bandwidth when fetching the vertex data even further, and to reduce the amount of memory required to store the mesh, it is often beneficial to quantize the vertex attributes to smaller types. While this optimization can technically run at any part of the pipeline (and sometimes doing quantization as the first step can improve indexing by merging almost identical vertices), it generally is easier to run this after all other optimizations since some of them require access to float3 positions. - -Quantization is usually domain specific; it's common to quantize normals using 3 8-bit integers but you can use higher-precision quantization (for example using 10 bits per component in a 10_10_10_2 format), or a different encoding to use just 2 components. For positions and texture coordinate data the two most common storage formats are half precision floats, and 16-bit normalized integers that encode the position relative to the AABB of the mesh or the UV bounding rectangle. - -The number of possible combinations here is very large but this library does provide the building blocks, specifically functions to quantize floating point values to normalized integers, as well as half-precision floats. For example, here's how you can quantize a normal: - -```c++ -unsigned int normal = - (meshopt_quantizeUnorm(v.nx, 10) << 20) | - (meshopt_quantizeUnorm(v.ny, 10) << 10) | - meshopt_quantizeUnorm(v.nz, 10); -``` - -and here's how you can quantize a position: - -```c++ -unsigned short px = meshopt_quantizeHalf(v.x); -unsigned short py = meshopt_quantizeHalf(v.y); -unsigned short pz = meshopt_quantizeHalf(v.z); -``` - -## Vertex/index buffer compression - -In case storage size or transmission bandwidth is of importance, you might want to additionally compress vertex and index data. While several mesh compression libraries, like Google Draco, are available, they typically are designed to maximize the compression ratio at the cost of disturbing the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. They also frequently don't support custom game-ready quantized vertex formats and thus require to re-quantize the data after loading it, introducing extra quantization errors and making decoding slower. - -Alternatively you can use general purpose compression libraries like zstd or Oodle to compress vertex/index data - however these compressors aren't designed to exploit redundancies in vertex/index data and as such compression rates can be unsatisfactory. - -To that end, this library provides algorithms to "encode" vertex and index data. The result of the encoding is generally significantly smaller than initial data, and remains compressible with general purpose compressors - so you can either store encoded data directly (for modest compression ratios and maximum decoding performance), or further compress it with zstd/Oodle to maximize compression ratio. - -> Note: this compression scheme is available as a glTF extension [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md). - -To encode, you need to allocate target buffers (preferably using the worst case bound) and call encoding functions: - -```c++ -std::vector vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex))); -vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex))); - -std::vector ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count)); -ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count)); -``` - -You can then either serialize `vbuf`/`ibuf` as is, or compress them further. To decode the data at runtime, call decoding functions: - -```c++ -int resvb = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size()); -int resib = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size()); -assert(resvb == 0 && resib == 0); -``` - -Note that vertex encoding assumes that vertex buffer was optimized for vertex fetch, and that vertices are quantized; index encoding assumes that the vertex/index buffers were optimized for vertex cache and vertex fetch. Feeding unoptimized data into the encoders will produce poor compression ratios. Both codecs are lossless - the only lossy step is quantization that happens before encoding. - -To reduce the data size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache, and to use new index codec version (`meshopt_encodeIndexVersion(1)`). This trades off some efficiency in vertex transform for smaller vertex and index data. - -Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-3 GB/s on modern desktop CPUs. Compression ratios depend on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized data), index data compression ratio is around 5-6x (compared to raw 16-bit index data). General purpose lossless compressors can further improve on these results. - -Index buffer codec only supports triangle list topology; when encoding triangle strips or line lists, use `meshopt_encodeIndexSequence`/`meshopt_decodeIndexSequence` instead. This codec typically encodes indices into ~1 byte per index, but compressing the results further with a general purpose compressor can improve the results to 1-3 bits per index. - -The following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch): - -- Data encoded with older versions of the library can always be decoded with newer versions; -- Data encoded with newer versions of the library can be decoded with older versions, provided that encoding versions are set correctly; if binary stability of encoded data is important, use `meshopt_encodeVertexVersion` and `meshopt_encodeIndexVersion` to 'pin' the data versions. - -Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/meshopt_decoder.js`, that you can use to decode meshes that were encoded offline: - -```js -// ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes -await MeshoptDecoder.ready; - -// decode from *Data (Uint8Array) into *Buffer (Uint8Array) -MeshoptDecoder.decodeVertexBuffer(vertexBuffer, vertexCount, vertexSize, vertexData); -MeshoptDecoder.decodeIndexBuffer(indexBuffer, indexCount, indexSize, indexData); -``` - -[Usage example](https://meshoptimizer.org/demo/) is available, with source in `demo/index.html`; this example uses .GLB files encoded using `gltfpack`. - -## Point cloud compression - -The vertex encoding algorithms can be used to compress arbitrary streams of attribute data; one other use case besides triangle meshes is point cloud data. Typically point clouds come with position, color and possibly other attributes but don't have an implied point order. - -To compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm: - -```c++ -std::vector remap(point_count); -meshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3)); - -// for each attribute stream -meshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]); -``` - -After this the resulting arrays should be quantized (e.g. using 16-bit fixed point numbers for positions and 8-bit color components), and the result can be compressed using `meshopt_encodeVertexBuffer` as described in the previous section. To decompress, `meshopt_decodeVertexBuffer` will recover the quantized data that can be used directly or converted back to original floating-point data. The compression ratio depends on the nature of source data, for colored points it's typical to get 35-40 bits per point as a result. - -## Triangle strip conversion - -On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial: - -- On some older GPUs, triangle strips may be a bit more efficient to render -- On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory - -This library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip: - -```c++ -std::vector strip(meshopt_stripifyBound(index_count)); -unsigned int restart_index = ~0u; -size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index); -``` - -Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. -Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance. - -To reduce the triangle strip size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index buffers. - -## Deinterleaved geometry - -All of the examples above assume that geometry is represented as a single vertex buffer and a single index buffer. This requires storing all vertex attributes - position, normal, texture coordinate, skinning weights etc. - in a single contiguous struct. However, in some cases using multiple vertex streams may be preferable. In particular, if some passes require only positional data - such as depth pre-pass or shadow map - then it may be beneficial to split it from the rest of the vertex attributes to make sure the bandwidth use during these passes is optimal. On some mobile GPUs a position-only attribute stream also improves efficiency of tiling algorithms. - -Most of the functions in this library either only need the index buffer (such as vertex cache optimization) or only need positional information (such as overdraw optimization). However, several tasks require knowledge about all vertex attributes. - -For indexing, `meshopt_generateVertexRemap` assumes that there's just one vertex stream; when multiple vertex streams are used, it's necessary to use `meshopt_generateVertexRemapMulti` as follows: - -```c++ -meshopt_Stream streams[] = { - {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, -}; - -std::vector remap(index_count); -size_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0])); -``` - -After this `meshopt_remapVertexBuffer` needs to be called once for each vertex stream to produce the correctly reindexed stream. - -Instead of calling `meshopt_optimizeVertexFetch` for reordering vertices in a single vertex buffer for efficiency, calling `meshopt_optimizeVertexFetchRemap` and then calling `meshopt_remapVertexBuffer` for each stream again is recommended. - -Finally, when compressing vertex data, `meshopt_encodeVertexBuffer` should be used on each vertex stream separately - this allows the encoder to best utilize corellation between attribute values for different vertices. - -## Simplification - -All algorithms presented so far don't affect visual appearance at all, with the exception of quantization that has minimal controlled impact. However, fundamentally the most effective way at reducing the rendering or transmission cost of a mesh is to make the mesh simpler. - -This library provides two simplification algorithms that reduce the number of triangles in the mesh. Given a vertex and an index buffer, they generate a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices. - -The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully; it's recommended to preprocess the index buffer with `meshopt_generateShadowIndexBuffer` to discard any vertex attributes that aren't critical and can be rebuilt later such as normals. - -```c++ -float threshold = 0.2f; -size_t target_index_count = size_t(index_count * threshold); -float target_error = 1e-2f; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - target_index_count, target_error, &lod_error)); -``` - -Target error is an approximate measure of the deviation from the original mesh using distance normalized to 0..1 (so 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that because of topological restrictions and error bounds simplifier isn't guaranteed to reach the target index count and can stop earlier. - -The second simplification algorithm, `meshopt_simplifySloppy`, doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter better because it can merge mesh features that are topologically disjoint but spatially close. - -```c++ -float threshold = 0.2f; -size_t target_index_count = size_t(index_count * threshold); -float target_error = 1e-1f; - -std::vector lod(index_count); -float lod_error = 0.f; -lod.resize(meshopt_simplifySloppy(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), - target_index_count, target_error, &lod_error)); -``` - -This algorithm will not stop early due to topology restrictions but can still do so if target index count can't be reached without introducing an error larger than target. It is 5-6x faster than `meshopt_simplify` when simplification ratio is large, and is able to reach ~20M triangles/sec on a desktop CPU (`meshopt_simplify` works at ~3M triangles/sec). - -When a sequence of LOD meshes is generated that all use the original vertex buffer, care must be taken to order vertices optimally to not penalize mobile GPU architectures that are only capable of transforming a sequential vertex buffer range. It's recommended in this case to first optimize each LOD for vertex cache, then assemble all LODs in one large index buffer starting from the coarsest LOD (the one with fewest triangles), and call `meshopt_optimizeVertexFetch` on the final large index buffer. This will make sure that coarser LODs require a smaller vertex range and are efficient wrt vertex fetch and transform. - -Both algorithms can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to world space by multiplying by the scaling factor returned by `meshopt_simplifyScale`. - -## Mesh shading - -Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer. - -Using mesh shaders in context of traditional mesh rendering provides an opportunity to use a variety of optimization techniques, starting from more efficient vertex reuse, using various forms of culling (e.g. cluster frustum or occlusion culling) and in-memory compression to maximize the utilization of GPU hardware. Beyond traditional rendering mesh shaders provide a richer programming model that can synthesize new geometry more efficiently than common alternatives such as geometry shaders. Mesh shading can be accessed via Vulkan or Direct3D 12 APIs; please refer to [Introduction to Turing Mesh Shaders](https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/) and [Mesh Shaders and Amplification Shaders: Reinventing the Geometry Pipeline](https://devblogs.microsoft.com/directx/coming-to-directx-12-mesh-shaders-and-amplification-shaders-reinventing-the-geometry-pipeline/) for additional information. - -To use mesh shaders for conventional rendering efficiently, geometry needs to be converted into a series of meshlets; each meshlet represents a small subset of the original mesh and comes with a small set of vertices and a separate micro-index buffer that references vertices in the meshlet. This information can be directly fed to the rasterizer from the mesh shader. This library provides algorithms to create meshlet data for a mesh, and - assuming geometry is static - can compute bounding information that can be used to perform cluster culling, a technique that can reject a meshlet if it's invisible on screen. - -To generate meshlet data, this library provides two algorithms - `meshopt_buildMeshletsScan`, which creates the meshlet data using a vertex cache-optimized index buffer as a starting point by greedily aggregating consecutive triangles until they go over the meshlet limits, and `meshopt_buildMeshlets`, which doesn't depend on any other algorithms and tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency (by minimizing meshlet radius and triangle direction divergence). `meshopt_buildMeshlets` is recommended in cases when the resulting meshlet data will be used in cluster culling algorithms. - -```c++ -const size_t max_vertices = 64; -const size_t max_triangles = 124; -const float cone_weight = 0.0f; - -size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles); -std::vector meshlets(max_meshlets); -std::vector meshlet_vertices(max_meshlets * max_vertices); -std::vector meshlet_triangles(max_meshlets * max_triangles * 3); - -size_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), - indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight); -``` - -To generate the meshlet data, `max_vertices` and `max_triangles` need to be set within limits supported by the hardware; for NVidia the values of 64 and 124 are recommended. `cone_weight` should be left as 0 if cluster cone culling is not used, and set to a value between 0 and 1 to balance cone culling efficiency with other forms of culling like frustum or occlusion culling. - -Each resulting meshlet refers to a portion of `meshlet_vertices` and `meshlet_triangles` arrays; this data can be uploaded to GPU and used directly after trimming: - -```c++ -const meshopt_Meshlet& last = meshlets[meshlet_count - 1]; - -meshlet_vertices.resize(last.vertex_offset + last.vertex_count); -meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3)); -meshlets.resize(meshlet_count); -``` - -However depending on the application other strategies of storing the data can be useful; for example, `meshlet_vertices` serves as indices into the original vertex buffer but it might be worthwhile to generate a mini vertex buffer for each meshlet to remove the extra indirection when accessing vertex data, or it might be desirable to compress vertex data as vertices in each meshlet are likely to be very spatially coherent. - -After generating the meshlet data, it's also possible to generate extra data for each meshlet that can be saved and used at runtime to perform cluster culling, where each meshlet can be discarded if it's guaranteed to be invisible. To generate the data, `meshlet_computeMeshletBounds` can be used: - -```c++ -meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], - m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex)); -``` - -The resulting `bounds` values can be used to perform frustum or occlusion culling using the bounding sphere, or cone culling using the cone axis/angle (which will reject the entire meshlet if all triangles are guaranteed to be back-facing from the camera point of view): - -```c++ -if (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject(); -``` - -## Efficiency analyzers - -While the only way to get precise performance data is to measure performance on the target GPU, it can be valuable to measure the impact of these optimization in a GPU-independent manner. To this end, the library provides analyzers for all three major optimization routines. For each optimization there is a corresponding analyze function, like `meshopt_analyzeOverdraw`, that returns a struct with statistics. - -`meshopt_analyzeVertexCache` returns vertex cache statistics. The common metric to use is ACMR - average cache miss ratio, which is the ratio of the total number of vertex invocations to the triangle count. The worst-case ACMR is 3 (GPU has to process 3 vertices for each triangle); on regular grids the optimal ACMR approaches 0.5. On real meshes it usually is in [0.5..1.5] range depending on the amount of vertex splits. One other useful metric is ATVR - average transformed vertex ratio - which represents the ratio of vertex shader invocations to the total vertices, and has the best case of 1.0 regardless of mesh topology (each vertex is transformed once). - -`meshopt_analyzeVertexFetch` returns vertex fetch statistics. The main metric it uses is overfetch - the ratio between the number of bytes read from the vertex buffer to the total number of bytes in the vertex buffer. Assuming non-redundant vertex buffers, the best case is 1.0 - each byte is fetched once. - -`meshopt_analyzeOverdraw` returns overdraw statistics. The main metric it uses is overdraw - the ratio between the number of pixel shader invocations to the total number of covered pixels, as measured from several different orthographic cameras. The best case for overdraw is 1.0 - each pixel is shaded once. - -Note that all analyzers use approximate models for the relevant GPU units, so the numbers you will get as the result are only a rough approximation of the actual performance. - -## Memory management - -Many algorithms allocate temporary memory to store intermediate results or accelerate processing. The amount of memory allocated is a function of various input parameters such as vertex count and index count. By default memory is allocated using `operator new` and `operator delete`; if these operators are overloaded by the application, the overloads will be used instead. Alternatively it's possible to specify custom allocation/deallocation functions using `meshopt_setAllocator`, e.g. - -```c++ -meshopt_setAllocator(malloc, free); -``` - -> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead. - -Vertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments. - -All functions have bounded stack usage that does not exceed 32 KB for any algorithms. - -## License - -This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). diff --git a/Dependencies/meshoptimizer/codecov.yml b/Dependencies/meshoptimizer/codecov.yml deleted file mode 100644 index 3ed6eb4a..00000000 --- a/Dependencies/meshoptimizer/codecov.yml +++ /dev/null @@ -1,11 +0,0 @@ -comment: false - -coverage: - status: - project: off - patch: off - -ignore: - - demo - - extern - - tools diff --git a/Dependencies/meshoptimizer/config.cmake.in b/Dependencies/meshoptimizer/config.cmake.in deleted file mode 100644 index 7a07fb75..00000000 --- a/Dependencies/meshoptimizer/config.cmake.in +++ /dev/null @@ -1,4 +0,0 @@ -@PACKAGE_INIT@ - -include("${CMAKE_CURRENT_LIST_DIR}/meshoptimizerTargets.cmake") -check_required_components(meshoptimizer) diff --git a/Dependencies/meshoptimizer/demo/ansi.c b/Dependencies/meshoptimizer/demo/ansi.c deleted file mode 100644 index 9e4f3475..00000000 --- a/Dependencies/meshoptimizer/demo/ansi.c +++ /dev/null @@ -1,2 +0,0 @@ -/* This file makes sure the library can be used by C89 code */ -#include "../src/meshoptimizer.h" diff --git a/Dependencies/meshoptimizer/demo/demo.html b/Dependencies/meshoptimizer/demo/demo.html deleted file mode 100644 index 3b32876a..00000000 --- a/Dependencies/meshoptimizer/demo/demo.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - meshoptimizer - demo - - - - - - - - - - - diff --git a/Dependencies/meshoptimizer/demo/index.html b/Dependencies/meshoptimizer/demo/index.html deleted file mode 100644 index 7f1063eb..00000000 --- a/Dependencies/meshoptimizer/demo/index.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - meshoptimizer - demo - - - - - - - - - - - diff --git a/Dependencies/meshoptimizer/demo/main.cpp b/Dependencies/meshoptimizer/demo/main.cpp deleted file mode 100644 index 1e46f89d..00000000 --- a/Dependencies/meshoptimizer/demo/main.cpp +++ /dev/null @@ -1,1249 +0,0 @@ -#include "../src/meshoptimizer.h" - -#include -#include -#include -#include -#include - -#include - -#include "../extern/fast_obj.h" - -#define SDEFL_IMPLEMENTATION -#include "../extern/sdefl.h" - -// This file uses assert() to verify algorithm correctness -#undef NDEBUG -#include - -#if defined(__linux__) -double timestamp() -{ - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return double(ts.tv_sec) + 1e-9 * double(ts.tv_nsec); -} -#elif defined(_WIN32) -struct LARGE_INTEGER -{ - __int64 QuadPart; -}; -extern "C" __declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount); -extern "C" __declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency); - -double timestamp() -{ - LARGE_INTEGER freq, counter; - QueryPerformanceFrequency(&freq); - QueryPerformanceCounter(&counter); - return double(counter.QuadPart) / double(freq.QuadPart); -} -#else -double timestamp() -{ - return double(clock()) / double(CLOCKS_PER_SEC); -} -#endif - -const size_t kCacheSize = 16; - -struct Vertex -{ - float px, py, pz; - float nx, ny, nz; - float tx, ty; -}; - -struct Mesh -{ - std::vector vertices; - std::vector indices; -}; - -union Triangle -{ - Vertex v[3]; - char data[sizeof(Vertex) * 3]; -}; - -Mesh parseObj(const char* path, double& reindex) -{ - fastObjMesh* obj = fast_obj_read(path); - if (!obj) - { - printf("Error loading %s: file not found\n", path); - return Mesh(); - } - - size_t total_indices = 0; - - for (unsigned int i = 0; i < obj->face_count; ++i) - total_indices += 3 * (obj->face_vertices[i] - 2); - - std::vector vertices(total_indices); - - size_t vertex_offset = 0; - size_t index_offset = 0; - - for (unsigned int i = 0; i < obj->face_count; ++i) - { - for (unsigned int j = 0; j < obj->face_vertices[i]; ++j) - { - fastObjIndex gi = obj->indices[index_offset + j]; - - Vertex v = - { - obj->positions[gi.p * 3 + 0], - obj->positions[gi.p * 3 + 1], - obj->positions[gi.p * 3 + 2], - obj->normals[gi.n * 3 + 0], - obj->normals[gi.n * 3 + 1], - obj->normals[gi.n * 3 + 2], - obj->texcoords[gi.t * 2 + 0], - obj->texcoords[gi.t * 2 + 1], - }; - - // triangulate polygon on the fly; offset-3 is always the first polygon vertex - if (j >= 3) - { - vertices[vertex_offset + 0] = vertices[vertex_offset - 3]; - vertices[vertex_offset + 1] = vertices[vertex_offset - 1]; - vertex_offset += 2; - } - - vertices[vertex_offset] = v; - vertex_offset++; - } - - index_offset += obj->face_vertices[i]; - } - - fast_obj_destroy(obj); - - reindex = timestamp(); - - Mesh result; - - std::vector remap(total_indices); - - size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex)); - - result.indices.resize(total_indices); - meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]); - - result.vertices.resize(total_vertices); - meshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]); - - return result; -} - -void dumpObj(const Mesh& mesh, bool recomputeNormals = false) -{ - std::vector normals; - - if (recomputeNormals) - { - normals.resize(mesh.vertices.size() * 3); - - for (size_t i = 0; i < mesh.indices.size(); i += 3) - { - unsigned int a = mesh.indices[i], b = mesh.indices[i + 1], c = mesh.indices[i + 2]; - - const Vertex& va = mesh.vertices[a]; - const Vertex& vb = mesh.vertices[b]; - const Vertex& vc = mesh.vertices[c]; - - float nx = (vb.py - va.py) * (vc.pz - va.pz) - (vb.pz - va.pz) * (vc.py - va.py); - float ny = (vb.pz - va.pz) * (vc.px - va.px) - (vb.px - va.px) * (vc.pz - va.pz); - float nz = (vb.px - va.px) * (vc.py - va.py) - (vb.py - va.py) * (vc.px - va.px); - - for (int k = 0; k < 3; ++k) - { - unsigned int index = mesh.indices[i + k]; - - normals[index * 3 + 0] += nx; - normals[index * 3 + 1] += ny; - normals[index * 3 + 2] += nz; - } - } - } - - for (size_t i = 0; i < mesh.vertices.size(); ++i) - { - const Vertex& v = mesh.vertices[i]; - - float nx = v.nx, ny = v.ny, nz = v.nz; - - if (recomputeNormals) - { - nx = normals[i * 3 + 0]; - ny = normals[i * 3 + 1]; - nz = normals[i * 3 + 2]; - - float l = sqrtf(nx * nx + ny * ny + nz * nz); - float s = l == 0.f ? 0.f : 1.f / l; - - nx *= s; - ny *= s; - nz *= s; - } - - fprintf(stderr, "v %f %f %f\n", v.px, v.py, v.pz); - fprintf(stderr, "vn %f %f %f\n", nx, ny, nz); - } - - for (size_t i = 0; i < mesh.indices.size(); i += 3) - { - unsigned int a = mesh.indices[i], b = mesh.indices[i + 1], c = mesh.indices[i + 2]; - - fprintf(stderr, "f %d %d %d\n", a + 1, b + 1, c + 1); - } -} - -bool isMeshValid(const Mesh& mesh) -{ - size_t index_count = mesh.indices.size(); - size_t vertex_count = mesh.vertices.size(); - - if (index_count % 3 != 0) - return false; - - const unsigned int* indices = &mesh.indices[0]; - - for (size_t i = 0; i < index_count; ++i) - if (indices[i] >= vertex_count) - return false; - - return true; -} - -bool rotateTriangle(Triangle& t) -{ - int c01 = memcmp(&t.v[0], &t.v[1], sizeof(Vertex)); - int c02 = memcmp(&t.v[0], &t.v[2], sizeof(Vertex)); - int c12 = memcmp(&t.v[1], &t.v[2], sizeof(Vertex)); - - if (c12 < 0 && c01 > 0) - { - // 1 is minimum, rotate 012 => 120 - Vertex tv = t.v[0]; - t.v[0] = t.v[1], t.v[1] = t.v[2], t.v[2] = tv; - } - else if (c02 > 0 && c12 > 0) - { - // 2 is minimum, rotate 012 => 201 - Vertex tv = t.v[2]; - t.v[2] = t.v[1], t.v[1] = t.v[0], t.v[0] = tv; - } - - return c01 != 0 && c02 != 0 && c12 != 0; -} - -unsigned int hashRange(const char* key, size_t len) -{ - // MurmurHash2 - const unsigned int m = 0x5bd1e995; - const int r = 24; - - unsigned int h = 0; - - while (len >= 4) - { - unsigned int k = *reinterpret_cast(key); - - k *= m; - k ^= k >> r; - k *= m; - - h *= m; - h ^= k; - - key += 4; - len -= 4; - } - - return h; -} - -unsigned int hashMesh(const Mesh& mesh) -{ - size_t triangle_count = mesh.indices.size() / 3; - - const Vertex* vertices = &mesh.vertices[0]; - const unsigned int* indices = &mesh.indices[0]; - - unsigned int h1 = 0; - unsigned int h2 = 0; - - for (size_t i = 0; i < triangle_count; ++i) - { - Triangle t; - t.v[0] = vertices[indices[i * 3 + 0]]; - t.v[1] = vertices[indices[i * 3 + 1]]; - t.v[2] = vertices[indices[i * 3 + 2]]; - - // skip degenerate triangles since some algorithms don't preserve them - if (rotateTriangle(t)) - { - unsigned int hash = hashRange(t.data, sizeof(t.data)); - - h1 ^= hash; - h2 += hash; - } - } - - return h1 * 0x5bd1e995 + h2; -} - -void optNone(Mesh& mesh) -{ - (void)mesh; -} - -void optRandomShuffle(Mesh& mesh) -{ - size_t triangle_count = mesh.indices.size() / 3; - - unsigned int* indices = &mesh.indices[0]; - - unsigned int rng = 0; - - for (size_t i = triangle_count - 1; i > 0; --i) - { - // Fisher-Yates shuffle - size_t j = rng % (i + 1); - - unsigned int t; - t = indices[3 * j + 0], indices[3 * j + 0] = indices[3 * i + 0], indices[3 * i + 0] = t; - t = indices[3 * j + 1], indices[3 * j + 1] = indices[3 * i + 1], indices[3 * i + 1] = t; - t = indices[3 * j + 2], indices[3 * j + 2] = indices[3 * i + 2], indices[3 * i + 2] = t; - - // LCG RNG, constants from Numerical Recipes - rng = rng * 1664525 + 1013904223; - } -} - -void optCache(Mesh& mesh) -{ - meshopt_optimizeVertexCache(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size()); -} - -void optCacheFifo(Mesh& mesh) -{ - meshopt_optimizeVertexCacheFifo(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), kCacheSize); -} - -void optCacheStrip(Mesh& mesh) -{ - meshopt_optimizeVertexCacheStrip(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size()); -} - -void optOverdraw(Mesh& mesh) -{ - // use worst-case ACMR threshold so that overdraw optimizer can sort *all* triangles - // warning: this significantly deteriorates the vertex cache efficiency so it is not advised; look at optComplete for the recommended method - const float kThreshold = 3.f; - meshopt_optimizeOverdraw(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), kThreshold); -} - -void optFetch(Mesh& mesh) -{ - meshopt_optimizeVertexFetch(&mesh.vertices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)); -} - -void optFetchRemap(Mesh& mesh) -{ - // this produces results equivalent to optFetch, but can be used to remap multiple vertex streams - std::vector remap(mesh.vertices.size()); - meshopt_optimizeVertexFetchRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size()); - - meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &remap[0]); - meshopt_remapVertexBuffer(&mesh.vertices[0], &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex), &remap[0]); -} - -void optComplete(Mesh& mesh) -{ - // vertex cache optimization should go first as it provides starting order for overdraw - meshopt_optimizeVertexCache(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size()); - - // reorder indices for overdraw, balancing overdraw and vertex cache efficiency - const float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw - meshopt_optimizeOverdraw(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), kThreshold); - - // vertex fetch optimization should go last as it depends on the final index order - meshopt_optimizeVertexFetch(&mesh.vertices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)); -} - -struct PackedVertex -{ - unsigned short px, py, pz; - unsigned short pw; // padding to 4b boundary - signed char nx, ny, nz, nw; - unsigned short tx, ty; -}; - -void packMesh(std::vector& pv, const std::vector& vertices) -{ - for (size_t i = 0; i < vertices.size(); ++i) - { - const Vertex& vi = vertices[i]; - PackedVertex& pvi = pv[i]; - - pvi.px = meshopt_quantizeHalf(vi.px); - pvi.py = meshopt_quantizeHalf(vi.py); - pvi.pz = meshopt_quantizeHalf(vi.pz); - pvi.pw = 0; - - pvi.nx = char(meshopt_quantizeSnorm(vi.nx, 8)); - pvi.ny = char(meshopt_quantizeSnorm(vi.ny, 8)); - pvi.nz = char(meshopt_quantizeSnorm(vi.nz, 8)); - pvi.nw = 0; - - pvi.tx = meshopt_quantizeHalf(vi.tx); - pvi.ty = meshopt_quantizeHalf(vi.ty); - } -} - -struct PackedVertexOct -{ - unsigned short px, py, pz; - signed char nu, nv; // octahedron encoded normal, aliases .pw - unsigned short tx, ty; -}; - -void packMesh(std::vector& pv, const std::vector& vertices) -{ - for (size_t i = 0; i < vertices.size(); ++i) - { - const Vertex& vi = vertices[i]; - PackedVertexOct& pvi = pv[i]; - - pvi.px = meshopt_quantizeHalf(vi.px); - pvi.py = meshopt_quantizeHalf(vi.py); - pvi.pz = meshopt_quantizeHalf(vi.pz); - - float nsum = fabsf(vi.nx) + fabsf(vi.ny) + fabsf(vi.nz); - float nx = vi.nx / nsum; - float ny = vi.ny / nsum; - float nz = vi.nz; - - float nu = nz >= 0 ? nx : (1 - fabsf(ny)) * (nx >= 0 ? 1 : -1); - float nv = nz >= 0 ? ny : (1 - fabsf(nx)) * (ny >= 0 ? 1 : -1); - - pvi.nu = char(meshopt_quantizeSnorm(nu, 8)); - pvi.nv = char(meshopt_quantizeSnorm(nv, 8)); - - pvi.tx = meshopt_quantizeHalf(vi.tx); - pvi.ty = meshopt_quantizeHalf(vi.ty); - } -} - -void simplify(const Mesh& mesh, float threshold = 0.2f) -{ - Mesh lod; - - double start = timestamp(); - - size_t target_index_count = size_t(mesh.indices.size() * threshold); - float target_error = 1e-2f; - float result_error = 0; - - lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count - lod.indices.resize(meshopt_simplify(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, 0, &result_error)); - - lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize() - lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex))); - - double end = timestamp(); - - printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n", - "Simplify", - int(mesh.indices.size() / 3), int(lod.indices.size() / 3), - result_error * 100, - (end - start) * 1000); -} - -void simplifySloppy(const Mesh& mesh, float threshold = 0.2f) -{ - Mesh lod; - - double start = timestamp(); - - size_t target_index_count = size_t(mesh.indices.size() * threshold); - float target_error = 1e-1f; - float result_error = 0; - - lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count - lod.indices.resize(meshopt_simplifySloppy(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, &result_error)); - - lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize() - lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex))); - - double end = timestamp(); - - printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n", - "SimplifyS", - int(mesh.indices.size() / 3), int(lod.indices.size() / 3), - result_error * 100, - (end - start) * 1000); -} - -void simplifyPoints(const Mesh& mesh, float threshold = 0.2f) -{ - double start = timestamp(); - - size_t target_vertex_count = size_t(mesh.vertices.size() * threshold); - - std::vector indices(target_vertex_count); - indices.resize(meshopt_simplifyPoints(&indices[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_vertex_count)); - - double end = timestamp(); - - printf("%-9s: %d points => %d points in %.2f msec\n", - "SimplifyP", - int(mesh.vertices.size()), int(indices.size()), (end - start) * 1000); -} - -void simplifyComplete(const Mesh& mesh) -{ - static const size_t lod_count = 5; - - double start = timestamp(); - - // generate 4 LOD levels (1-4), with each subsequent LOD using 70% triangles - // note that each LOD uses the same (shared) vertex buffer - std::vector lods[lod_count]; - - lods[0] = mesh.indices; - - for (size_t i = 1; i < lod_count; ++i) - { - std::vector& lod = lods[i]; - - float threshold = powf(0.7f, float(i)); - size_t target_index_count = size_t(mesh.indices.size() * threshold) / 3 * 3; - float target_error = 1e-2f; - - // we can simplify all the way from base level or from the last result - // simplifying from the base level sometimes produces better results, but simplifying from last level is faster - const std::vector& source = lods[i - 1]; - - if (source.size() < target_index_count) - target_index_count = source.size(); - - lod.resize(source.size()); - lod.resize(meshopt_simplify(&lod[0], &source[0], source.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error)); - } - - double middle = timestamp(); - - // optimize each individual LOD for vertex cache & overdraw - for (size_t i = 0; i < lod_count; ++i) - { - std::vector& lod = lods[i]; - - meshopt_optimizeVertexCache(&lod[0], &lod[0], lod.size(), mesh.vertices.size()); - meshopt_optimizeOverdraw(&lod[0], &lod[0], lod.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), 1.0f); - } - - // concatenate all LODs into one IB - // note: the order of concatenation is important - since we optimize the entire IB for vertex fetch, - // putting coarse LODs first makes sure that the vertex range referenced by them is as small as possible - // some GPUs process the entire range referenced by the index buffer region so doing this optimizes the vertex transform - // cost for coarse LODs - // this order also produces much better vertex fetch cache coherency for coarse LODs (since they're essentially optimized first) - // somewhat surprisingly, the vertex fetch cache coherency for fine LODs doesn't seem to suffer that much. - size_t lod_index_offsets[lod_count] = {}; - size_t lod_index_counts[lod_count] = {}; - size_t total_index_count = 0; - - for (int i = lod_count - 1; i >= 0; --i) - { - lod_index_offsets[i] = total_index_count; - lod_index_counts[i] = lods[i].size(); - - total_index_count += lods[i].size(); - } - - std::vector indices(total_index_count); - - for (size_t i = 0; i < lod_count; ++i) - { - memcpy(&indices[lod_index_offsets[i]], &lods[i][0], lods[i].size() * sizeof(lods[i][0])); - } - - std::vector vertices = mesh.vertices; - - // vertex fetch optimization should go last as it depends on the final index order - // note that the order of LODs above affects vertex fetch results - meshopt_optimizeVertexFetch(&vertices[0], &indices[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex)); - - double end = timestamp(); - - printf("%-9s: %d triangles => %d LOD levels down to %d triangles in %.2f msec, optimized in %.2f msec\n", - "SimplifyC", - int(lod_index_counts[0]) / 3, int(lod_count), int(lod_index_counts[lod_count - 1]) / 3, - (middle - start) * 1000, (end - middle) * 1000); - - // for using LOD data at runtime, in addition to vertices and indices you have to save lod_index_offsets/lod_index_counts. - - { - meshopt_VertexCacheStatistics vcs0 = meshopt_analyzeVertexCache(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), kCacheSize, 0, 0); - meshopt_VertexFetchStatistics vfs0 = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), sizeof(Vertex)); - meshopt_VertexCacheStatistics vcsN = meshopt_analyzeVertexCache(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), kCacheSize, 0, 0); - meshopt_VertexFetchStatistics vfsN = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), sizeof(Vertex)); - - typedef PackedVertexOct PV; - - std::vector pv(vertices.size()); - packMesh(pv, vertices); - - std::vector vbuf(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(PV))); - vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], vertices.size(), sizeof(PV))); - - std::vector ibuf(meshopt_encodeIndexBufferBound(indices.size(), vertices.size())); - ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size())); - - printf("%-9s ACMR %f...%f Overfetch %f..%f Codec VB %.1f bits/vertex IB %.1f bits/triangle\n", - "", - vcs0.acmr, vcsN.acmr, vfs0.overfetch, vfsN.overfetch, - double(vbuf.size()) / double(vertices.size()) * 8, - double(ibuf.size()) / double(indices.size() / 3) * 8); - } -} - -void optimize(const Mesh& mesh, const char* name, void (*optf)(Mesh& mesh)) -{ - Mesh copy = mesh; - - double start = timestamp(); - optf(copy); - double end = timestamp(); - - assert(isMeshValid(copy)); - assert(hashMesh(mesh) == hashMesh(copy)); - - meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), kCacheSize, 0, 0); - meshopt_VertexFetchStatistics vfs = meshopt_analyzeVertexFetch(©.indices[0], copy.indices.size(), copy.vertices.size(), sizeof(Vertex)); - meshopt_OverdrawStatistics os = meshopt_analyzeOverdraw(©.indices[0], copy.indices.size(), ©.vertices[0].px, copy.vertices.size(), sizeof(Vertex)); - - meshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), 32, 32, 32); - meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), 14, 64, 128); - meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(©.indices[0], copy.indices.size(), copy.vertices.size(), 128, 0, 0); - - printf("%-9s: ACMR %f ATVR %f (NV %f AMD %f Intel %f) Overfetch %f Overdraw %f in %.2f msec\n", name, vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr, vfs.overfetch, os.overdraw, (end - start) * 1000); -} - -template -size_t compress(const std::vector& data, int level = SDEFL_LVL_DEF) -{ - std::vector cbuf(sdefl_bound(int(data.size() * sizeof(T)))); - sdefl s = {}; - return sdeflate(&s, &cbuf[0], reinterpret_cast(&data[0]), int(data.size() * sizeof(T)), level); -} - -void encodeIndex(const Mesh& mesh, char desc) -{ - // allocate result outside of the timing loop to exclude memset() from decode timing - std::vector result(mesh.indices.size()); - - double start = timestamp(); - - std::vector buffer(meshopt_encodeIndexBufferBound(mesh.indices.size(), mesh.vertices.size())); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), &mesh.indices[0], mesh.indices.size())); - - double middle = timestamp(); - - int res = meshopt_decodeIndexBuffer(&result[0], mesh.indices.size(), &buffer[0], buffer.size()); - assert(res == 0); - (void)res; - - double end = timestamp(); - - size_t csize = compress(buffer); - - for (size_t i = 0; i < mesh.indices.size(); i += 3) - { - assert( - (result[i + 0] == mesh.indices[i + 0] && result[i + 1] == mesh.indices[i + 1] && result[i + 2] == mesh.indices[i + 2]) || - (result[i + 1] == mesh.indices[i + 0] && result[i + 2] == mesh.indices[i + 1] && result[i + 0] == mesh.indices[i + 2]) || - (result[i + 2] == mesh.indices[i + 0] && result[i + 0] == mesh.indices[i + 1] && result[i + 1] == mesh.indices[i + 2])); - } - - printf("IdxCodec%c: %.1f bits/triangle (post-deflate %.1f bits/triangle); encode %.2f msec, decode %.2f msec (%.2f GB/s)\n", - desc, - double(buffer.size() * 8) / double(mesh.indices.size() / 3), - double(csize * 8) / double(mesh.indices.size() / 3), - (middle - start) * 1000, - (end - middle) * 1000, - (double(result.size() * 4) / (1 << 30)) / (end - middle)); -} - -void encodeIndexSequence(const std::vector& data, size_t vertex_count, char desc) -{ - // allocate result outside of the timing loop to exclude memset() from decode timing - std::vector result(data.size()); - - double start = timestamp(); - - std::vector buffer(meshopt_encodeIndexSequenceBound(data.size(), vertex_count)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), &data[0], data.size())); - - double middle = timestamp(); - - int res = meshopt_decodeIndexSequence(&result[0], data.size(), &buffer[0], buffer.size()); - assert(res == 0); - (void)res; - - double end = timestamp(); - - size_t csize = compress(buffer); - - assert(memcmp(&data[0], &result[0], data.size() * sizeof(unsigned int)) == 0); - - printf("IdxCodec%c: %.1f bits/index (post-deflate %.1f bits/index); encode %.2f msec, decode %.2f msec (%.2f GB/s)\n", - desc, - double(buffer.size() * 8) / double(data.size()), - double(csize * 8) / double(data.size()), - (middle - start) * 1000, - (end - middle) * 1000, - (double(result.size() * 4) / (1 << 30)) / (end - middle)); -} - -template -void packVertex(const Mesh& mesh, const char* pvn) -{ - std::vector pv(mesh.vertices.size()); - packMesh(pv, mesh.vertices); - - size_t csize = compress(pv); - - printf("VtxPack%s : %.1f bits/vertex (post-deflate %.1f bits/vertex)\n", pvn, - double(pv.size() * sizeof(PV) * 8) / double(mesh.vertices.size()), - double(csize * 8) / double(mesh.vertices.size())); -} - -template -void encodeVertex(const Mesh& mesh, const char* pvn) -{ - std::vector pv(mesh.vertices.size()); - packMesh(pv, mesh.vertices); - - // allocate result outside of the timing loop to exclude memset() from decode timing - std::vector result(mesh.vertices.size()); - - double start = timestamp(); - - std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); - vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV))); - - double middle = timestamp(); - - int res = meshopt_decodeVertexBuffer(&result[0], mesh.vertices.size(), sizeof(PV), &vbuf[0], vbuf.size()); - assert(res == 0); - (void)res; - - double end = timestamp(); - - assert(memcmp(&pv[0], &result[0], pv.size() * sizeof(PV)) == 0); - - size_t csize = compress(vbuf); - - printf("VtxCodec%1s: %.1f bits/vertex (post-deflate %.1f bits/vertex); encode %.2f msec, decode %.2f msec (%.2f GB/s)\n", pvn, - double(vbuf.size() * 8) / double(mesh.vertices.size()), - double(csize * 8) / double(mesh.vertices.size()), - (middle - start) * 1000, - (end - middle) * 1000, - (double(result.size() * sizeof(PV)) / (1 << 30)) / (end - middle)); -} - -void stripify(const Mesh& mesh, bool use_restart, char desc) -{ - unsigned int restart_index = use_restart ? ~0u : 0; - - // note: input mesh is assumed to be optimized for vertex cache and vertex fetch - double start = timestamp(); - std::vector strip(meshopt_stripifyBound(mesh.indices.size())); - strip.resize(meshopt_stripify(&strip[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), restart_index)); - double end = timestamp(); - - Mesh copy = mesh; - copy.indices.resize(meshopt_unstripify(©.indices[0], &strip[0], strip.size(), restart_index)); - assert(copy.indices.size() <= meshopt_unstripifyBound(strip.size())); - - assert(isMeshValid(copy)); - assert(hashMesh(mesh) == hashMesh(copy)); - - meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), kCacheSize, 0, 0); - meshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 32, 32, 32); - meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 14, 64, 128); - meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 128, 0, 0); - - printf("Stripify%c: ACMR %f ATVR %f (NV %f AMD %f Intel %f); %d strip indices (%.1f%%) in %.2f msec\n", - desc, - vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr, - int(strip.size()), double(strip.size()) / double(mesh.indices.size()) * 100, - (end - start) * 1000); -} - -void shadow(const Mesh& mesh) -{ - // note: input mesh is assumed to be optimized for vertex cache and vertex fetch - - double start = timestamp(); - // this index buffer can be used for position-only rendering using the same vertex data that the original index buffer uses - std::vector shadow_indices(mesh.indices.size()); - meshopt_generateShadowIndexBuffer(&shadow_indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(float) * 3, sizeof(Vertex)); - double end = timestamp(); - - // while you can't optimize the vertex data after shadow IB was constructed, you can and should optimize the shadow IB for vertex cache - // this is valuable even if the original indices array was optimized for vertex cache! - meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], shadow_indices.size(), mesh.vertices.size()); - - meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(&mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), kCacheSize, 0, 0); - meshopt_VertexCacheStatistics vcss = meshopt_analyzeVertexCache(&shadow_indices[0], shadow_indices.size(), mesh.vertices.size(), kCacheSize, 0, 0); - - std::vector shadow_flags(mesh.vertices.size()); - size_t shadow_vertices = 0; - - for (size_t i = 0; i < shadow_indices.size(); ++i) - { - unsigned int index = shadow_indices[i]; - shadow_vertices += 1 - shadow_flags[index]; - shadow_flags[index] = 1; - } - - printf("ShadowIB : ACMR %f (%.2fx improvement); %d shadow vertices (%.2fx improvement) in %.2f msec\n", - vcss.acmr, double(vcs.vertices_transformed) / double(vcss.vertices_transformed), - int(shadow_vertices), double(mesh.vertices.size()) / double(shadow_vertices), - (end - start) * 1000); -} - -void meshlets(const Mesh& mesh, bool scan) -{ - const size_t max_vertices = 64; - const size_t max_triangles = 124; // NVidia-recommended 126, rounded down to a multiple of 4 - const float cone_weight = 0.5f; // note: should be set to 0 unless cone culling is used at runtime! - - // note: input mesh is assumed to be optimized for vertex cache and vertex fetch - double start = timestamp(); - size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles); - std::vector meshlets(max_meshlets); - std::vector meshlet_vertices(max_meshlets * max_vertices); - std::vector meshlet_triangles(max_meshlets * max_triangles * 3); - - if (scan) - meshlets.resize(meshopt_buildMeshletsScan(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), max_vertices, max_triangles)); - else - meshlets.resize(meshopt_buildMeshlets(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight)); - - if (meshlets.size()) - { - const meshopt_Meshlet& last = meshlets.back(); - - // this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage - meshlet_vertices.resize(last.vertex_offset + last.vertex_count); - meshlet_triangles.resize(last.triangle_offset + ((last.triangle_count * 3 + 3) & ~3)); - } - - double end = timestamp(); - - double avg_vertices = 0; - double avg_triangles = 0; - size_t not_full = 0; - - for (size_t i = 0; i < meshlets.size(); ++i) - { - const meshopt_Meshlet& m = meshlets[i]; - - avg_vertices += m.vertex_count; - avg_triangles += m.triangle_count; - not_full += m.vertex_count < max_vertices; - } - - avg_vertices /= double(meshlets.size()); - avg_triangles /= double(meshlets.size()); - - printf("Meshlets%c: %d meshlets (avg vertices %.1f, avg triangles %.1f, not full %d) in %.2f msec\n", - scan ? 'S' : ' ', - int(meshlets.size()), avg_vertices, avg_triangles, int(not_full), (end - start) * 1000); - - float camera[3] = {100, 100, 100}; - - size_t rejected = 0; - size_t rejected_s8 = 0; - size_t rejected_alt = 0; - size_t rejected_alt_s8 = 0; - size_t accepted = 0; - size_t accepted_s8 = 0; - - std::vector radii(meshlets.size()); - - double startc = timestamp(); - for (size_t i = 0; i < meshlets.size(); ++i) - { - const meshopt_Meshlet& m = meshlets[i]; - - meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); - - radii[i] = bounds.radius; - - // trivial accept: we can't ever backface cull this meshlet - accepted += (bounds.cone_cutoff >= 1); - accepted_s8 += (bounds.cone_cutoff_s8 >= 127); - - // perspective projection: dot(normalize(cone_apex - camera_position), cone_axis) > cone_cutoff - float mview[3] = {bounds.cone_apex[0] - camera[0], bounds.cone_apex[1] - camera[1], bounds.cone_apex[2] - camera[2]}; - float mviewlength = sqrtf(mview[0] * mview[0] + mview[1] * mview[1] + mview[2] * mview[2]); - - rejected += mview[0] * bounds.cone_axis[0] + mview[1] * bounds.cone_axis[1] + mview[2] * bounds.cone_axis[2] >= bounds.cone_cutoff * mviewlength; - rejected_s8 += mview[0] * (bounds.cone_axis_s8[0] / 127.f) + mview[1] * (bounds.cone_axis_s8[1] / 127.f) + mview[2] * (bounds.cone_axis_s8[2] / 127.f) >= (bounds.cone_cutoff_s8 / 127.f) * mviewlength; - - // alternative formulation for perspective projection that doesn't use apex (and uses cluster bounding sphere instead): - // dot(normalize(center - camera_position), cone_axis) > cone_cutoff + radius / length(center - camera_position) - float cview[3] = {bounds.center[0] - camera[0], bounds.center[1] - camera[1], bounds.center[2] - camera[2]}; - float cviewlength = sqrtf(cview[0] * cview[0] + cview[1] * cview[1] + cview[2] * cview[2]); - - rejected_alt += cview[0] * bounds.cone_axis[0] + cview[1] * bounds.cone_axis[1] + cview[2] * bounds.cone_axis[2] >= bounds.cone_cutoff * cviewlength + bounds.radius; - rejected_alt_s8 += cview[0] * (bounds.cone_axis_s8[0] / 127.f) + cview[1] * (bounds.cone_axis_s8[1] / 127.f) + cview[2] * (bounds.cone_axis_s8[2] / 127.f) >= (bounds.cone_cutoff_s8 / 127.f) * cviewlength + bounds.radius; - } - double endc = timestamp(); - - double radius_mean = 0; - - for (size_t i = 0; i < meshlets.size(); ++i) - radius_mean += radii[i]; - - radius_mean /= double(meshlets.size()); - - double radius_variance = 0; - - for (size_t i = 0; i < meshlets.size(); ++i) - radius_variance += (radii[i] - radius_mean) * (radii[i] - radius_mean); - - radius_variance /= double(meshlets.size() - 1); - - double radius_stddev = sqrt(radius_variance); - - size_t meshlets_std = 0; - - for (size_t i = 0; i < meshlets.size(); ++i) - meshlets_std += radii[i] < radius_mean + radius_stddev; - - printf("BoundDist: mean %f stddev %f; %.1f%% meshlets are under mean+stddev\n", - radius_mean, - radius_stddev, - double(meshlets_std) / double(meshlets.size()) * 100); - - printf("ConeCull : rejected apex %d (%.1f%%) / center %d (%.1f%%), trivially accepted %d (%.1f%%) in %.2f msec\n", - int(rejected), double(rejected) / double(meshlets.size()) * 100, - int(rejected_alt), double(rejected_alt) / double(meshlets.size()) * 100, - int(accepted), double(accepted) / double(meshlets.size()) * 100, - (endc - startc) * 1000); - printf("ConeCull8: rejected apex %d (%.1f%%) / center %d (%.1f%%), trivially accepted %d (%.1f%%) in %.2f msec\n", - int(rejected_s8), double(rejected_s8) / double(meshlets.size()) * 100, - int(rejected_alt_s8), double(rejected_alt_s8) / double(meshlets.size()) * 100, - int(accepted_s8), double(accepted_s8) / double(meshlets.size()) * 100, - (endc - startc) * 1000); -} - -void spatialSort(const Mesh& mesh) -{ - typedef PackedVertexOct PV; - - std::vector pv(mesh.vertices.size()); - packMesh(pv, mesh.vertices); - - double start = timestamp(); - - std::vector remap(mesh.vertices.size()); - meshopt_spatialSortRemap(&remap[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); - - double end = timestamp(); - - meshopt_remapVertexBuffer(&pv[0], &pv[0], mesh.vertices.size(), sizeof(PV), &remap[0]); - - std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); - vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV))); - - size_t csize = compress(vbuf); - - printf("Spatial : %.1f bits/vertex (post-deflate %.1f bits/vertex); sort %.2f msec\n", - double(vbuf.size() * 8) / double(mesh.vertices.size()), - double(csize * 8) / double(mesh.vertices.size()), - (end - start) * 1000); -} - -void spatialSortTriangles(const Mesh& mesh) -{ - typedef PackedVertexOct PV; - - Mesh copy = mesh; - - double start = timestamp(); - - meshopt_spatialSortTriangles(©.indices[0], ©.indices[0], mesh.indices.size(), ©.vertices[0].px, copy.vertices.size(), sizeof(Vertex)); - - double end = timestamp(); - - meshopt_optimizeVertexCache(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size()); - meshopt_optimizeVertexFetch(©.vertices[0], ©.indices[0], copy.indices.size(), ©.vertices[0], copy.vertices.size(), sizeof(Vertex)); - - std::vector pv(mesh.vertices.size()); - packMesh(pv, copy.vertices); - - std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); - vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV))); - - std::vector ibuf(meshopt_encodeIndexBufferBound(mesh.indices.size(), mesh.vertices.size())); - ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), ©.indices[0], mesh.indices.size())); - - size_t csizev = compress(vbuf); - size_t csizei = compress(ibuf); - - printf("SpatialT : %.1f bits/vertex (post-deflate %.1f bits/vertex); %.1f bits/triangle (post-deflate %.1f bits/triangle); sort %.2f msec\n", - double(vbuf.size() * 8) / double(mesh.vertices.size()), - double(csizev * 8) / double(mesh.vertices.size()), - double(ibuf.size() * 8) / double(mesh.indices.size() / 3), - double(csizei * 8) / double(mesh.indices.size() / 3), - (end - start) * 1000); -} - -void tessellationAdjacency(const Mesh& mesh) -{ - double start = timestamp(); - - // 12 indices per input triangle - std::vector tessib(mesh.indices.size() * 4); - meshopt_generateTessellationIndexBuffer(&tessib[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); - - double middle = timestamp(); - - // 6 indices per input triangle - std::vector adjib(mesh.indices.size() * 2); - meshopt_generateAdjacencyIndexBuffer(&adjib[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); - - double end = timestamp(); - - printf("Tesselltn: %d patches in %.2f msec\n", int(mesh.indices.size() / 3), (middle - start) * 1000); - printf("Adjacency: %d patches in %.2f msec\n", int(mesh.indices.size() / 3), (end - middle) * 1000); -} - -bool loadMesh(Mesh& mesh, const char* path) -{ - double start = timestamp(); - double middle; - mesh = parseObj(path, middle); - double end = timestamp(); - - if (mesh.vertices.empty()) - { - printf("Mesh %s is empty, skipping\n", path); - return false; - } - - printf("# %s: %d vertices, %d triangles; read in %.2f msec; indexed in %.2f msec\n", path, int(mesh.vertices.size()), int(mesh.indices.size() / 3), (middle - start) * 1000, (end - middle) * 1000); - return true; -} - -void processDeinterleaved(const char* path) -{ - // Most algorithms in the library work out of the box with deinterleaved geometry, but some require slightly special treatment; - // this code runs a simplified version of complete opt. pipeline using deinterleaved geo. There's no compression performed but you - // can trivially run it by quantizing all elements and running meshopt_encodeVertexBuffer once for each vertex stream. - fastObjMesh* obj = fast_obj_read(path); - if (!obj) - { - printf("Error loading %s: file not found\n", path); - return; - } - - size_t total_indices = 0; - - for (unsigned int i = 0; i < obj->face_count; ++i) - total_indices += 3 * (obj->face_vertices[i] - 2); - - std::vector unindexed_pos(total_indices * 3); - std::vector unindexed_nrm(total_indices * 3); - std::vector unindexed_uv(total_indices * 2); - - size_t vertex_offset = 0; - size_t index_offset = 0; - - for (unsigned int i = 0; i < obj->face_count; ++i) - { - for (unsigned int j = 0; j < obj->face_vertices[i]; ++j) - { - fastObjIndex gi = obj->indices[index_offset + j]; - - // triangulate polygon on the fly; offset-3 is always the first polygon vertex - if (j >= 3) - { - memcpy(&unindexed_pos[(vertex_offset + 0) * 3], &unindexed_pos[(vertex_offset - 3) * 3], 3 * sizeof(float)); - memcpy(&unindexed_nrm[(vertex_offset + 0) * 3], &unindexed_nrm[(vertex_offset - 3) * 3], 3 * sizeof(float)); - memcpy(&unindexed_uv[(vertex_offset + 0) * 2], &unindexed_uv[(vertex_offset - 3) * 2], 2 * sizeof(float)); - memcpy(&unindexed_pos[(vertex_offset + 1) * 3], &unindexed_pos[(vertex_offset - 1) * 3], 3 * sizeof(float)); - memcpy(&unindexed_nrm[(vertex_offset + 1) * 3], &unindexed_nrm[(vertex_offset - 1) * 3], 3 * sizeof(float)); - memcpy(&unindexed_uv[(vertex_offset + 1) * 2], &unindexed_uv[(vertex_offset - 1) * 2], 2 * sizeof(float)); - vertex_offset += 2; - } - - memcpy(&unindexed_pos[vertex_offset * 3], &obj->positions[gi.p * 3], 3 * sizeof(float)); - memcpy(&unindexed_nrm[vertex_offset * 3], &obj->normals[gi.n * 3], 3 * sizeof(float)); - memcpy(&unindexed_uv[vertex_offset * 2], &obj->texcoords[gi.t * 2], 2 * sizeof(float)); - vertex_offset++; - } - - index_offset += obj->face_vertices[i]; - } - - fast_obj_destroy(obj); - - double start = timestamp(); - - meshopt_Stream streams[] = { - {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3}, - {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2}, - }; - - std::vector remap(total_indices); - - size_t total_vertices = meshopt_generateVertexRemapMulti(&remap[0], NULL, total_indices, total_indices, streams, sizeof(streams) / sizeof(streams[0])); - - std::vector indices(total_indices); - meshopt_remapIndexBuffer(&indices[0], NULL, total_indices, &remap[0]); - - std::vector pos(total_vertices * 3); - meshopt_remapVertexBuffer(&pos[0], &unindexed_pos[0], total_indices, sizeof(float) * 3, &remap[0]); - - std::vector nrm(total_vertices * 3); - meshopt_remapVertexBuffer(&nrm[0], &unindexed_nrm[0], total_indices, sizeof(float) * 3, &remap[0]); - - std::vector uv(total_vertices * 2); - meshopt_remapVertexBuffer(&uv[0], &unindexed_uv[0], total_indices, sizeof(float) * 2, &remap[0]); - - double reindex = timestamp(); - - meshopt_optimizeVertexCache(&indices[0], &indices[0], total_indices, total_vertices); - - meshopt_optimizeVertexFetchRemap(&remap[0], &indices[0], total_indices, total_vertices); - meshopt_remapVertexBuffer(&pos[0], &pos[0], total_vertices, sizeof(float) * 3, &remap[0]); - meshopt_remapVertexBuffer(&nrm[0], &nrm[0], total_vertices, sizeof(float) * 3, &remap[0]); - meshopt_remapVertexBuffer(&uv[0], &uv[0], total_vertices, sizeof(float) * 2, &remap[0]); - - double optimize = timestamp(); - - // note: since shadow index buffer is computed based on regular vertex/index buffer, the stream points at the indexed data - not unindexed_pos - meshopt_Stream shadow_stream = {&pos[0], sizeof(float) * 3, sizeof(float) * 3}; - - std::vector shadow_indices(total_indices); - meshopt_generateShadowIndexBufferMulti(&shadow_indices[0], &indices[0], total_indices, total_vertices, &shadow_stream, 1); - - meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], total_indices, total_vertices); - - double shadow = timestamp(); - - printf("Deintrlvd: %d vertices, reindexed in %.2f msec, optimized in %.2f msec, generated & optimized shadow indices in %.2f msec\n", - int(total_vertices), (reindex - start) * 1000, (optimize - reindex) * 1000, (shadow - optimize) * 1000); -} - -void process(const char* path) -{ - Mesh mesh; - if (!loadMesh(mesh, path)) - return; - - optimize(mesh, "Original", optNone); - optimize(mesh, "Random", optRandomShuffle); - optimize(mesh, "Cache", optCache); - optimize(mesh, "CacheFifo", optCacheFifo); - optimize(mesh, "CacheStrp", optCacheStrip); - optimize(mesh, "Overdraw", optOverdraw); - optimize(mesh, "Fetch", optFetch); - optimize(mesh, "FetchMap", optFetchRemap); - optimize(mesh, "Complete", optComplete); - - Mesh copy = mesh; - meshopt_optimizeVertexCache(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size()); - meshopt_optimizeVertexFetch(©.vertices[0], ©.indices[0], copy.indices.size(), ©.vertices[0], copy.vertices.size(), sizeof(Vertex)); - - Mesh copystrip = mesh; - meshopt_optimizeVertexCacheStrip(©strip.indices[0], ©strip.indices[0], copystrip.indices.size(), copystrip.vertices.size()); - meshopt_optimizeVertexFetch(©strip.vertices[0], ©strip.indices[0], copystrip.indices.size(), ©strip.vertices[0], copystrip.vertices.size(), sizeof(Vertex)); - - stripify(copy, false, ' '); - stripify(copy, true, 'R'); - stripify(copystrip, true, 'S'); - - meshlets(copy, false); - meshlets(copy, true); - - shadow(copy); - tessellationAdjacency(copy); - - encodeIndex(copy, ' '); - encodeIndex(copystrip, 'S'); - - std::vector strip(meshopt_stripifyBound(copystrip.indices.size())); - strip.resize(meshopt_stripify(&strip[0], ©strip.indices[0], copystrip.indices.size(), copystrip.vertices.size(), 0)); - - encodeIndexSequence(strip, copystrip.vertices.size(), 'D'); - - packVertex(copy, ""); - encodeVertex(copy, ""); - encodeVertex(copy, "O"); - - simplify(mesh); - simplifySloppy(mesh); - simplifyComplete(mesh); - simplifyPoints(mesh); - - spatialSort(mesh); - spatialSortTriangles(mesh); - - if (path) - processDeinterleaved(path); -} - -void processDev(const char* path) -{ - Mesh mesh; - if (!loadMesh(mesh, path)) - return; - - tessellationAdjacency(mesh); -} - -int main(int argc, char** argv) -{ - void runTests(); - - meshopt_encodeVertexVersion(0); - meshopt_encodeIndexVersion(1); - - if (argc == 1) - { - runTests(); - } - else - { - if (strcmp(argv[1], "-d") == 0) - { - for (int i = 2; i < argc; ++i) - { - processDev(argv[i]); - } - } - else - { - for (int i = 1; i < argc; ++i) - { - process(argv[i]); - } - - runTests(); - } - } -} diff --git a/Dependencies/meshoptimizer/demo/pirate.glb b/Dependencies/meshoptimizer/demo/pirate.glb deleted file mode 100644 index 261f086367f766ec821ffc499dbd62f62353966a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24696 zcmb@t1$5TBl$ z)UGLNDJv^&Y|;ee!RIl}rHQ3=<;@kE0JEpj)2GAr-xGbje2reNn$p(h(z>R~`Z`#w zT@#g^kzZTdR8im1oL^jD+t66rWP4|ORaRJ2Q&d=7tqEvuY$@&N=os#*DQs#gZMMA} z1d!shx$O@OAnrn#&E7E~GCODlLJn)g;8Gy(gPL zvZb)Dxw5UW*;bkBe{BvV{jYYXm5ywwZ2W&+(Vw7YRhEwa3%K_nY648I?=eg+D|^qQ z@z3Z#Aoch8Gbz2fv9hF86X55nX)bImw*lhmG?Qnt;EE{1=)gldqTW zf6%P0w-MKbWn`qqg=c0&r9<3`>s#tzGt7Q|zObWg9sj>G1qI^+7atfinY_IqCpPi< zz!;R0|DPGNaq<_D4|KhK%>MsD_J8B8tg@!L)FxNS5gB2zQITn3iT@8c_Vn`cg8ckb zJ3Ng>9|-(ES@it>-ghQnPrv^mGu}Sl|3hS;jQqtS?7Xm~n8bhfp2^$jY5Ysiii@El z)_+j#_nm2z*#AvpY-(FyS6bJcF{S|uhPTNKx%r1cKu+Jk&}2nLctYfAE1`Pl8C~H8 z)vC6z6~;`SMn99Q$;Z>r)78`8%k>scf_9?R$gr zx0L=#0YYQzq<^Twe^U4tWACy0o3r$Yu*9%5jm<*U7D8dEEUfucw;PHYhmXz3+{r10+kI~!DH`gLU#-g1n%%5c=DQO4gb1M#=qccDmQ~+ra5p z!-S5rp6oxp3qNfBZt=PwZvI~8SaI9S`gq7ql`zJ2k6WARsY5hkimyW6SnzJ2B9{`%W1D@Y43DclO?H|Fo& zwAy#eibuxdqbpaoOkz3Qx*UDv!6jl^c>Q2{b=21{V_uY{W@arS5EU?60KlmhmBmfI zI+BS4(BI1q6rvcuHvU8ehGV#fZ`7$UW3Qge1*k@4yQ@?tKu!sZBCE}Qqta1CjhVxO zaF`b(1ZYH|23YeiTfrbFNmNXn0ejzrdwL)!aK^O2py^3N5|paMBvozF5HKw~v3ht= zcp?}GgDB5<4{Oqp&)&nDkv^ot(+#}Vg`Ngr{({S2NVjX=jb9QMFSQHyn(w#W`UWsl z7ACZ2jZ-au#I!yX@VSm0ik78vj5eoH^M^82uEq@Z@SJ>_Cjg%Lg+&=g%xV?UUW~t0 zbN1SIw(H#4&u*>!E!X#4x6EMk20E~$$>@WZnGL}Nl&c!U5G3?#^Glya!|R z<{j(l7;LGzFnwQ2^{tCd=Y(&_-zF|lm%e%Gzofd=HGJCL;H`C^a62yTSC=z90#k}v z^;ji{aOSR3O|AfAzXmU3cFtLJNdQJdK{GbhUEVq`K6XRJ)`Ngm4j#iEc@ndYy#UN_ z`}|HbiCGi7fbU3NvefOfw!V-VE3Vh&@Q+UU`RdPoGx`E@1tk|5>qGa6hsFt>o(2nZ z7vU$`m{49YACu8Ys0%D#&-rGXO?uNR8E@}@G;2iK#BFu?<1Rg- zKYDWS?1PV5hsb*yO6!YaI}@Wwvn-Ex^geph$8hBZ-zRO=l|EITwPr%g=ycRZ~O!Q+5{JZNQr6Hy=*#Z`w1qeyp`sQ=!cI zEaZjiF}_K@BBDCKp(UkSqvSDv8zf zWoXvzi^E4o;zAMxGh)?_aE(r7<^q#8Dke(s*r@eNIV2Sz~!3K3D`XvtyY8MHne+vk57{AbQ4=YB|iCs!h3D~{q>`l*5`Zw zn0@oqx7U74cw=}Wcw>AfU_BO`m1;Tc5DP!ffYL`Wo3@VGlIU>x3%zV%*Ga{Jw#1B^ zt$lYg6mMpR+}qTb9yew>4ZZVXdEj%|Pe-5pT0H)h&y5zYaZouid~)rHXXl5W|K{`` zyNwS{RLt_M^y-%VVlXC;oxEhGZ|cQ4x($I9qqAnPPRa%NZ?c>jAz!PmfCutZ!5%{g zDnB31Yk%Cby4r8(zTmyeQ{b6uAjBbUe94<;OqyiG0$dmzq0g?`&t{4F7 z$drOKE}(&(WD*kDs4gun6+CtrXTo@Vgo_9;+)JP?EDpy70z|`0HJOZ1$}MPd6I1}r zvhIa97jn6n$rSk~*surQqkVtjQ|G4A%(iDj~bdzjJU; z#!f}!xChU+SZehz#3K@FWR)UP)y)V@IQH3z>1Ul2r_MYIc%U_OURIcUH~09OHHw$P`wmf2TQ20FOSHo9nvy&%$An7)ipzZ3x2Bn9(k>JZ27ui zNNZ5%owj`qDP^>@0|B2YZXzeOpB+3G`P^YX_R_xJkCzfy@%yN{&hGxxeNQU{;b!}D z*jx3+P{YvfYVQ`jy=?Rs_58V&nVGM<`-?C*MnPdDEQqrXK48WuWFUU=n*S|TF{F>DNhYA~&{8Q{2-8)$;SKQXV+K*>jCCQlf{ zV8yDxO-k2W>#yHPr%ctDSqBa;ubRyJ!hEz?vtj6jAd6OUTR6;rOep0*)s<5kyB!bh z8X6mK>4hU+okU}wX^ltlPX@EsEo^oy`Q!Snw_dj$H8+PW<~aW(G8rE``s!=+)7kSG z!yTTQ+B|;D1kP_>?*?(;D(Lg{GzYC(Jd=0&+wabE_MbnMK+10@>j@|!;6Cdf?eiHOWuIrZ1 zd0f#f*eQHcPM`gI*W3wtu}xFf&OKLNEIiFWQ{gesXR0+eZwlcz2Yd{!aeRfJ{=9PJ z1R!Y0%-XQw)T-RWV(279hPjZE)HwdFWAhe>KW0+tRGvVf0vH!lP5fMgV@813<2Wz@ z9WSMzm832$A&hWzv{(=muY}+m$$X;3;uVsTVk9gD@FKhm3V<;MUZ|xUTLx1QV@k@6 z7uL*2WcOFvH$^9UJpPL#0EfzyJFRZ2o0~OPYH(fBVSC`o@%i-nQ+>zaE|deS+-=9zk*O%!lg#ek=+~PVszd^-NCr z`^rQz<{m}``eK`h(9jJIlgZ$i%65mGKo0>HQ~^Xq!Jy9>h>f@tGj zSVRPlS)nwZ*f)PheeX{*zF+rWd;w&T|3metGo8Ih*H6EDw)xB3HOcp0`;}?tCtQ6U z@Jt-$GP*{(E#&8^oxGjft6%EdLHQ)*N}f|BsKOiACCNcX=Hx5jAS>(j-@ZW5zkGq% z^PD&5A8$B#Ahu%7`~~vkwLgs{+dP3O`(9iPo_b7>Tl@X;fnf29FRx}Vx2$XUW&hmS z!uw%EpALEU8hQn%zC1p5+Q@=s2V;Xst@TJxZ_l%Ogwzq6J0I)tO!U{7G2g6}|1*_x{w8QSA={!d@owH=EGntuKF( zXJ}`Vb2#7Zm@_ptylmydTequMmaNKt?7D7X+dRdAE3^YG@238AT-*6k^6x394YX}D zRviAAvP3_JIk$HXZAN2TQ|s(y*dE4Mey&4!RQ#Br^`@D5anZl0I^MhO>@Xy*<=O7c zfG_U|hI>bS5kzT-9s4LS@`#HnQ!|g0OBlDuFqYTyTCLN5_${e#BX=V!q&6-s)XjpQ z)$dIf*PQHnyQ}ql=8?R$c;GpYZSKI$?S~)TJq|pONIb+F+q}PZI%2Bx&8L8u6DZ{TlW4q`*qWs^!r0LcHL$lZ@PVId>B9X+12M8 z>QCRwPeGagFVV zh8*=;ks5x(k;f#q{nWY$y zKy8>V-*~?0(F4!qYqehuJsNc>?rPOvM!aojv}ns>HT8afzRO(KBk)l0EK$XY=>JITIkOo`2SdWIE;}$`Nfg9CNz= zQ^=wE;8Xf<9Y6UcYuCBHyPhte*0`T^n;iP7SGdbIADy;E*|oYa``?LQ-@AC_2l~vS z5A=}_)VI(1i|WL?OKZ*yjk8fmZ@bG5xcimm2>GnX<;w}jf}j2Rz^%kFwt!#zNdarf zCQ>*p^Nv5sq89U7M;=L9x`15OcqHoJFYOT1%o{XXH%=vWWF+K+FAIoofV|0X0`K zRXa>j%4cNP#P1w^A?s-F1}x}|>%xSUp51pM7RUav)O$fnyW`Z;?9)q#D%Mm?NNL8t z=g!>IKi9H=JZ?pt)0okVq8FD2f8&&)EpylZ zG_*7Om!X|0l`*M4)a1i)|In0Jb;r5QZn6%&tX9}OEe?;d%Ihs;*dH~ugqDw za#?pAY5DdJ)%mTPn;bgF5w*f%JQ_IbR$sFH7| zx0IbUt^IE7=%+1VPO^XnHu(eu06sXi3_lDXFw_E<62dJIADcM^7=qJak}BKg6)=Ij z1ddbPINlGP4l2YF1{y#m_8}C&8v7n~yqP z;hJ{a?>D#}a6pX%@^Sl#m514%sy8@~R}aj1mgOFJ^Yy3af#Y!}Kv8`rqwH}#z#KTV zLQHQt>7LO3-9NN??AHaR8DkInLTa_`)?XIm*u6@XZVPYbU*#^O@t{bw`#F z^~I!YV}h@cLekY0lmzkX^AlXeLLnLO+c#y$G<~)3UWz@zk-5}9?0d8C*9H5XqwDIJ z4#{Dhi5)UNKv7I6WD!zR`I!}qvgQw8X0{hNqV~pOJoYp-HGE8}3?c5=z%0<|Ccxna zx>#`OMFr96fuP!=xJ?96i>^ERux-i)*aR7hA_Nu6C8?ikgBNfe0m#*%@acW9-mqHH zQ$@cYuYb8I|6x5Afq)2<73?;OpF5&2{>$yxO6B~O~c>1S>MbgpXiMa zpn2yN=X+)6c$w#ygEMjG%K2ZbNAqjw(Kj=bI!Ib5beojNM>2 zqvOEEKDyod^Iwf)M=)G?Y5`N|PGm4A)*;n#0H^81-L|i zHxxe!4Onm$d;n^IpuiEP0R-Lfk+nNumoyLfGU3P`Oh!T{xI=*{f>FEdp*5B z-5H-3KjGbK5?NG#n{l|}WZ=^_=T%1|=I7>LMAxq2ZEZF0o9l*zX6xtA@EJXG$hV7n zf_nz0d|uF1xqs~UgHs3QWrU~t2Rr5X#+7c4?F-Q9oU_>BK|V&K#4A$Kx{AA^A}Bo5 zSXOayW9e$<#RA`NYRvm*svSX;Tl+|_g3YFee596)hxf1PX$*-h?_p0t7}L^a_~QEg z73UAtOmN5^VHn>s$@j{I$B9n8_t)*2mmwhpEDnW&qYT1SI?VmuEteHH4Av)tsFu-v zFP?w-MWn~azcz#r1VLjWoFw3+)vBemks?={JT5EX(!d&OO1L8ri3A{wtP-enHiL-T zu6}hY1P@w62wHYG2m=KW$8cycZMGE#tuROkoyuKKGa|e}=qaqnYQ*1WIkV*0b zjUju_k6Sjnc8n%tUcYDFr}Vr1KCTf_1`4--SkL@*=7!kVJGW21-T&sw)fW^c% zLs5^+Bf%S@LkfGmMlHxpn#&0-*5;ccLWIMKFrT`y_G$4W3J!1CbD?XiXIxWGb#C*l zRaIw>&I+5Hl{K2e6F4Sm1C@Y6r3z#s0SJ*cnud8hm`8paS}$~%8X~XfB(T*K_@3^_ z4br(9S!S)rWJ6P-i9>hxOE6_dWoB}VR1gn=h0t_oz=!_6$o^gk4@yvpOjYcAsDKJZ zKseeDPz!+g6~sc7hA_g&UjYi^AsI8gbrx^=?qr%SsdK}HFTsiB)ow_A__7sOBG+9& zB@wH^+qwI`eK%+K)r~8ku6_3Htf`w;JxjPcb4q>Fmnm=KeisTE3(ytUmMjqU^!)He z+OeD2RlV_gqXzY0v6(U%pDLkn>3ktG%9rCCm1{@l+ea1%Miv)RX@&?VAat1Gbltbp z-{sn_lQ%YgGkD{~L$?>#!K;_ge|mG}tuZ7ZAZ4ZH)7(uJK?016cvp8VuZ{E=NR5f| zcbKjBam_kvP73iWYp$uQAv=3iqV+KmXlnKJcpk8i1{I~DWI15!J-(koqz5bMejGO` z=b_c=L)~~XUn1rxlx{X8K_5^NWGZSYf-4@~7K??divCmDY*6*i;nAc8pa9}F0AW)> z$%OxZfrvl^twFKzetwBgzB9&@4$<}nij$k7TbHcrxqqrUcho3-^5>(dGsk-bW(zz5 z`Fz(UD{f73(=%O{gidi|NX?z5BR-!`UACj=U~(0O9zeLqWUs3?7Hb)1A0Em>a5fbP z4bB1~h0pL1dDxNY5&<0b9K`Wey);($ranMr3JQ`&T|Ybi#6>^e9J{S6hNbhP3X%k5 zam%RT-gz9!ka3?rS@7=HQjaJDm&KOoC3XzU7sqQa%&KsAS;Y#?oBcyx`;@mFGg;)M zRkJK!h*qjp%jH2ngi7QV%5=AfMsi3dsb*DHTI`RZD?`q;?K)wPRhpl$R|3Fx~NofGM#2=`?9 zdl&>pqqC1fXGilaE*#e~+ySL33fdfJRQ_}lu;oe`+`o{BSUINVTgctJ$LDn)z52rX4!Qf_uCXF32-9j+4nm`f1Pbaz`+?{Pu^HH;LGbNe>Dk8^X?Fc&n6;bDCR|59fgj$}YGVNtRXejz*(*8lA3 zwX>D|`|h1UFaL7st7n53Z(n)!S&qS%tQb|b|H0Fj;4=Nx4cZlUzlcILdT~r94%skq zm>FMZAV;Ryr#xOYdLW{8itMXLn)~ z9b9k$iHS1V9C^n_N5?mn*Vn|BbnZWJI*6!*BjQH3c|p`KJI1Qpd%j+_r_r_O&U_cq z*VA4o7rr}lZ&znQ-=%x^zQ6bNxqWkP_S~r2)`U*_Uie(J=jqX|ooCjL9x6_&n!IJx zyq^{&j<&m(x4I{5YHq^B&+gngcu&CCGOsnsZj!h*;oGTYzl=)UKZQ^4 z;)>d?cJZzsyw@U?&{yIjs^g6*U7gcAB{lo3Kke_IkaSEMb7k)4LlcLnM^2d=c);DtcZHetdm;#EGp<6}KIK zL%;iVe{l7%**!=13_G!-@Jhky(LLke+5HexeJb{fxGaR>_NC%M&gQR(#e$s7lZy&8 zbbwuOpy2$_>aL!`!qf$~Cg~(}4(XG}kDu>gDjfaJZCpJk=)8xoam#aDDE<>kbeW)6 zjfV}ORGNbYbX#yBK~*>eDH7F$BLX;5uhFG~l1X>TI5ifTnPIb_1h9~8-cfI)#RlZT z_apDo&%Cy`^;V3fCkY@5o+q^Kr(nTP|I^``yQ-GNb*Xv@y@C zFV)ofQ9})$Ja@GH)85~H-2xXlW$_LvRgrs#l@*5Z(YP;veR5;5x|FObX}k9G<-1ea zDU|+A%|Cy4Q{A)s!ta_4F@z)<;j+MKWOpVJ4vYRF38?NGFedvk)<3*1~ zMlNU;Ax0lJl&PY`6f$;gde`^vD{pr_PwgLrWi)K!hBhq^w|exC9n3G~4Q$A5u$jlq zZUl;1CF3aQ2ISaWEy-m;^Cr^gXxy^HMv!X49LSs*TXXk%muFq=S8NnQ78eYu6urUAMXBi}94S-iBFfgfY0|YW0$D_DfwZMC_`Z=<`#^l#9pDT|mB({N(ev zB4%^l>wyC|f4}khq1`{frES}@fA2=-vJJG;#BUdpF0kKlb}n7Ac=o6G7TITeqqfI& z1YF#{ww4#<=jYHVUEjT6#kgBjn;QdkAJ2836mur;2>$f#p(jec)+wIFoWPzqghI&) z(NPQ&r0EGjE^!S>6Z4~l+60035jvQw2w0?Kn?uPf zD2VIsh6N?>T}T$`6-vej?a?!$wV&N*vn7s?)iUwT$GqRZy8Xr1l2cC(+@AkQVIo`s z<6Ngbi+a2E{)Q7hYYwjYbPh4){%XbpgH2*-3WmX0CS+U91eSuUl$d znrKeb{bE;fAHd3itC`zT|rp}t0q55{{vZd)x0!KQHoA0wQEctT& z{hoWfgU{JbPyFaw^rE`19+B?Mk2clJZCw2kzEOQ<%;cpaD$nP;iq?TuYd5Xvo-%24 zwabEr#_r7V4z;6{T{HX~X$>B&)9t*65%K|2q6RHhmx$`v8->|L77^Rio=cK zCa$8l2ihmDHaV-*s%eJCiieBbR*D_$v#kkUA69{#v(7e z9Veo)+}B)H<3|oFuBaT$cQw0lXqcNq;h|>~jqoy*q_g9U3EqWt4TfV=p`+oAiDTg+VoY?QbwetJV_Lff*JQO)yNljz1 z;$pUyXRm-R?~FE})8&Z8UC5x})*!N%c z%M}ynu$if83|877O(VxoXl$M^`O>AW+rIf`Tg$_NfrpP~%oy@}TH5c=Q&XF7-q^qY z`|F>**%efM>(>p(&Q^IuWv*X@+TegU7W^S~+wf7rMY80ZJdBK`*8Ch{J3g_1^D)`> z8(u&%K^v(n@Z{++ue3;uMMW5W!ue!%s% zT7O|`1OsseLX<`oQ8)X-Yc2aDJ^F+i^(&qkkttz4$fR$ zlvGo=qXR0?;Zi5(Aq&sDCn=|g?RS})JLuwCQFZc=^SpC@=uGFbGTO<p{a({AKEub{$=w#UdeL0yvA;(e-YtjIt zpK6TJ2z=nSyQeWM9FGPd9WR5u;Z;xo{8+yMoJ7XGYyrCZhmF8UB9R7+z>JYVN}m}6 z2-A-CXlpAE;%v2YzAW(?qHrg{^c)+Ak2zNZ|DdUDZ6(zUN2Es?X*4RNt`RC9cfFjFlNU#4FVh~i?dJ`Q04 zzuYf7IbCYqr8&3q;-+nLi1R*^%TBpzG=tw<`eW7Q(G#~lI-Bug^;#Y`(KTY&$z7_p z+j7M%|C_6S_+k9Goj<9=c1`GacAwzHX|1J?PRqo1_#ZyCc@CO zk*O3C4-GF+0q^wiXf@oB`%9vs8!^mb%TTx*$_rQbYPc?QY!%vq4eo2z$nxrmlUu9? z*JQ4+y}-#EoEX^qw)1J_vq^);Hx$>H?&V!5cfVTIJ1asUsz3{O=(|=ERg-E{Wz zJNngQVh)Fl88KwS5y|;HFnsqfBSu}=y?gxee7Ezz9w~q8aP^0~9jnK_xwLRUq_=;e zx~KQ^ig)@e%S=NIy=yp?Q?6dwc<#tu!#_1btBy(nr zSd48eUl+9LH}mJnuWrZWQ!lwc9=^$&^Tm!!5|(|Tg`WHJvC(%Z%^A^57a#%%#0YgAU2m#`A3R5~(++Cv6d@xl~<5RnBs zTs3rZ0T9BIX<6CL=F(-T0C1bTHCUKNB{(wvFpjUfpgM-*@pl&v3&WOWIPa=o*v% z(KGF%z6%|T=U2IX`BP-u_Sr-C=FiUSS`S#VE`QftTXvJ%j&Zi^zkJ>@ zXv>Z~xc`kUdjYa+&beuyo8y8!tAiraS}#nzd}wxARZdo?qq>FbKxnA#W+_glYHMjR zcTB`1$zyUxErpJu7BRI+#pR(ID)ogrOvfq0(l9kDNRc1vlj1m)n)=E1dltv9pZ`dB9dpO-GXTucZJUQ+~j~>r~x($WD8cLeb}rOeE7uo zp{KDgk~Pwbug2!hp5pU@GmyA){S4gg%UH&Q!!-@>4p38no*+KhHt_wRrs~K9M)a31 z=!~_IaO49Meomzxc-OlDNwKM>TCLRyNclQ`M3_@pL{tgj@;u^Ag;^P10)gjTXjXA6 za(eC$Pt$)a?8*4S@1*!ofrLrfZ_kWdIYu`5-Z!$mabLxG_62-0xPIT_(}<9{>h|#8 z#(Bkm-8ans(r=Si*r=XbvUkIooe_%=jmFCu5fu>?#?x?cEGkO2aB|)WD5Vx7f~eKS zCBe2i8q|7($7DHPk@_&+-VCQUtKQU^x=xVCj;jsAuIkWl8$k$W?<-)NtD z&j_MR&M_=W0e?7DZyLRBz(!T~dT7?Z#(iZ-W(IpPd;bR;3mfr>2#ZG!)m@7nd|N@YAV7`n0*xLo&NSEt}R% zn(HG(+U#XOA&7c+YEEYVmtsONs@NPF%DF#q)%M+^FcGN5rLp znq>0Cz$Lg}XKh8!BB@Rh!x2)`hJ6!m?}D;v^?7Zv7@QL|mA1u`fxVwfrv+?vh8l6gz6!Nnh~3IKuy?T4S(B9 zj6~NF@L=vNVC~Av%+(kaE*c}hG=LIS7n@#MFg!OsJ0@*-S!G#iRe2pgTw~!m7};>K zUJqniC5^>_`;;6KLoIVtvU4{#)$2qkL6-wZA&H?fXqd9xeCj0umB!z>i=`cg*VDqb zFqeCk<&_N0Ei5RGTJ`kJ;JH>NT__SraFOQsr;iRFdGhp;Lg&oW=uZxw8|0DMgh*w7 z7?4%o5dcVW08geKfg17x)6O>&Han-{_56+f#^Lz448YUb= zF`R66nfnax#_|SgAI*3owQ`QQWws*TIoDXx`6K>tA79$D>;4Vr%AO zYkWuXme2Zl)cByFj(I}ou1sIXQhT*3b!KT4N+r{f=(2%C6@&T+ww!Su-a_l~^1(NQ zi3eSCu?i|B_fY%u%y)ER9MT+QG9>>O`R~>_4Gp`Lmr4=2xL`43teXcyt&h%Ahc(2F zDQlLs^=6J?D&>Gz*PB{}ve=aRX#;-%z!9qC0xpSN2?7@_<&qpYC}2YI5B?zj`QGP^ zOlDN4@BVkM$?!vkV!f8><;(N*NiNB3ZVF;eBFKV_aCqc`LYLU*y3!0oqztGEAz?ON zLaJ;#*YFQ`{z2*<92~?+<*A-tF#bNgv%WjjFr&-*>P`HTmc(&w%_16=%o|3;2Za{4 zZ~?}bmGYf}%O_H(6s40#k(oyl4;1()NKz-cmxl17R{5~uY7*v)PGX2fm333?QjQ1p zGdl9;(&+M`Q##NugXn63q_VA}?c8W`g?W0&8hSrEIcUxLO>-ttdlqj@oMp9s{PE63 z`&za?>GiJ^a`QNGG+*x^OVKDEzyc~;X*U*J_$N_EWf<4f=Y^F&yL`xW#+CB+THA5!QMFfVop}s*#&Wh!By4 zI=k>iLIf_G-s5fCxMh`XFY0};VL{!n0s+gFGuO|T&);ZFTkPiNmCN|P{2k-F=)1)M zr(C-WdYjQzMZKf8tZ(T;4mT_a8ohk@F!IoeU3n1?eIJj*CMj=KTKv(Tmh~vUW>hb! zV8f{3`tyDlC(fR;1_b$ToIRe>w`fzs%pH%fqV)BLQQ7Jh(99v57AO{M@@Vr|+`n_j zHFW9^L2J5^Ue)r^6Z1&f{oTVNANf5*X>^X0f+Cb7Oru1r5|Tw2ODN^=IRQ+S-asJ} zI)#UUk&2Aq>2(6WRLZ3xJbSThI7pI_M9?i`5gYjkry^V6>L?=PT(O4eVg#Rtd^Lw z4x9Ort>ZuSeBbefd+7Zq@hy`fHH^lYy?(1Srl+UvB1U?Gto&~FlpK7craODXpLKV& zjnx5Odc20k5zs@p{^?QSX*h)?XWLOnV3kl$NjQUoOC)ejp?vrO`jZ>EnR0<%!PH>M zF=1v$R%d~;y@#9i_Xk6c)&FkIo8s;wPY`Euy<#~mx;>r~$MaV4T>qAZ*M|;KjdZ<_ zn?cDk0JZ}F1_y>P#14LjARz2juC6@zg$QnMTkaDp)>~pn370RIiQsp0AXXw4X$bF};fNG*)tW_QHiu5q z>FqQ@W=G6-AMcEitO<>DBOJ&QA2bhovGG)dp;k~Oa#{jGVf#_3Zc>Rtjwo_5Sjx`8|iQFpm$HzM!Vt|%43q0N(Mp_@RV$gBNOA& zMO1|AVc>^v8W-4*b;DOfLlF`KrE@R=E|vN)#K4|Tw&Tc|dXbb*!%&)lCdV*JBHi98 zkQ`uAN>uS0C6I;%M*Es9X*!9Dpre@7)8I5VMK!lW4(!;lfC^}081@8H#l?_iHo^uheWdL$|{m>=WiL@UDQi1h==Ry9kgElJM@`p@&7IUp?mMp10}^ zI!h|s#3A8SA$_OQc8l$UD&k(CUeAO(BL3&R7_R2%4 zc_-wllpLy5B8qNg#% z45dAvNB@3uHO6D-Rt}3o4mj`!)!_`*r=b1OG8)gVi z3ZabfW>Rs5tG&fR3X}?Gv?L;x!7=zSGsqb+EVZMV5Pa$v;i;6%+^3Sn(MmwnW5}0F+B4U2Bk)(<#0J1 zE`yQoNfUzj&UViPLE4Nz-c8={nBS#{^4u4uw z8(Qn{WqoQLr84h4-x{CEYj}5g5;|+pbZCS%w0Qn3D~C@I#zHllp#V}xdu+%GvX+u(CSeo-2WR=Q zToMb(29HSTi{#&@=fAQ}?a^@v*CBAT1o{ukegYh&3zS?Ai$|wws29#FM1U$*urW8G z)CEU4T3i_&nGmyzu4B(f)YN?ANVM@-HUGFSpb5=m?jlj&wVvbVqb@z~d)c3Z7u zIee+Y%(sn)W}8ZXXPn~}ev{g0bXI9yoHXuw70^4#lq!3@L2I~Ah)^|8#+QkeCSX97 zN|FQFqY$~q*CEmXSV*%RRnHEhY2Xwca? zy7I+*1zVsesz<7_iyS@UqEpDQ=@d1G6{tv-gbSEJjmn-=yJ&(zuiClS( z1pLHI&4J&dA?g@D)ENPj&_*l$8El@H9S<19j1N}e*VnJ16aD=8qeUD$g`WpMKx7zv zWPbEW<$Xx*=*WXB@f~s6FQ3W<27y#82U-+<5Qy;D5<3A70x4=T>cWu2T}Nk)fU^-i zPPRV!^l8{5g7e7%(L7=$f?*uq1_pVh0N=3}Q29${v&p1B5kL26w$4*26dH|!aEo@7kZ4@s8_ktFi5)w{d@r6Dh3jKLK~{1x21TGk z954Y*p$ewb>H2#+gkEcT-M9h~v54U!zMp`MkxAZ?L^Gi)63Xla^}`Y(WEMou3U*{u z6asDai$hk1(4Jrmm{M8@D@cz5^46W6YhK=ey@to(9et9lk24G6u&zI!Kk}-n(1H5@U zZytlCqsmc77MxNts1&$3K(|+5cDMxNLd!}}nRJCv<;vr7OzyUBQ`>QMOvokA)B5V= zn}P)eBA#1$tb|R|j$rplWOMii)-)0~b=2frc1+1wcLSLe$_`*^`Ff#DBZAX7YAz@( z62>~^x)}oEf@v;lD1U5M^M`)7>42D~6sYQ?8TrGNTxaJwP2MVQE~u9esxYQfqs3!P zQXr#?njOh}CzF@Kn@Tsd|%|@TVfd{G28Pr?iv3g)VJqTSJ;8T8yBet&MLF zRt-3j@KLp8X0$y0J%&+ zQUV_mgU7*JSl)Zm#x&235^*p^8KScD*L)Ny9nmX6&=v`8bnt5auH9N+=D5KPXx zon-D*oMPXQt5YM%w4q`O18O1P6~3o5_OhH1x;?$UqasCwDu~AEby-yC8;$9x$v~)x zM$kF7FMHjn;ngNL9Fngao*5Wi$H!=7E|c#S{y`kT1BgZJ$5v~ZUSQ1%@Gj<-k3jM% zJQOF(==qp8>f>RwT9a3H8MKcK?uHSBWH7K5Mcm9D7OlI|oWN_H{7^esqo*^o1T&UV)m*5*0JU*0zOH;k0f~SPixzbb* z_&|1=GRUo3VDfUP>@au^V zL3QY5mLY&llIi8~3_lv3iPCwfGCxe3%&sX+$6SD(A=T5dY+8-bXJM*TSv9v+kPr9L6&TuEqg|I3S!GH z$$(p%6-7FgBxA35DkH(mO#(lY{3}=yPY2FGb_9sXo;G#&venb4wTG%RRy^2+_M{e zvU+M$#h;X5Quur|N#~5%lPcKGv@}Wq17xN$#`6sZBc@G?sb}U>Q|V5ua%8)wynN@Te=svCRz^&YQz{>s?az#V>lHCr6+SMSrm;GTB4Dtt8&_jHzgSzvM1E|h(_^v7P^ z(#h0BWhQ(A(TZ1&K8Lt^m^ZJrj|_XO>~E#{4p8my*IQ}6D}dc+g;Ma!*xT3@oa!DT z9BT^iT@wn~?mqJiG<95PTa&KgQ^hOcd=SmrT-XH6+*(dJXTVpIHpSr32-kCBSfI-e zd!0yiC|XFwXxC$j6&BOPwli^zEA7avn!IgnW@sV{cZH*eZI>)($6;eDV>{1H+d=L^Kev6aIp6n}yE4lgdU|D$e!|o75tsSMr%c)I=;c44nH1T!UN|4MAV+N*g zdNgM2ZrEPb+@)XHCrSz=1DP6xB3Y_v4(QX}Er7C}4{iUr6%y;QQ8#Ux<$`dt z3OfK-mhoR-hD*Y1n+>KGsvt*gz?GvWI0X;z&!oq+mUeJm1Qt}{Nq?H0>mMxyW#)9$ zIoMOOh_Ib0z;FQbcJDD2Xsr2v*U|c9f+nD4NS))4~`Gp!jp9me zo&*Bd>LV2-qOm>>gr1s(FPV%P3fOsIk<^Yi3vo%N6dC0$Je3q6{!<9JY(O57&}3~D zz%8Hi`B;X77^+cxREF4gmO-PMuKU8`KcK)oLMP#`NXkK6m8zgxU`52pUE&onnOJag zqHt9Ulx^+lh#<(+X_{@B6R>+aHKeLub+vj$J;>7Z#89P3)FG?`9{aHsMbZ+(wry|I zkQe)PRvYO!rYn;9t6olcsl^N( z5NJ512jE^ZEtkZ+0zEko@5Zyof;&Z-I4}Ug3`W5Iy^uu6?J^3fYyB-O!HScjH5N&$ zIJ_WXTHzDZ^DZtI+wKXvu+Yeik6d2byl5^Hc&A0bAP*~{N(tiN%V0vQ!_>{d5jJ$Y zI#BKH&97Na9E_`e<*GU-pS8%K;Dl7{VG61~E*FRs zR7D8Gz?8~RylGj~;4NV#S*Ny7_Thu>FKl;Ib}idXr&1=7}OWK zsjz13f|1Z!3|b?tXe-?qP?s@k*>VVpWUNLnwF>~1CMN4ix)M#wiew->CJ*5IX{Te7 zav`S@$g^w|&$&BTba6sR43fG98Cg@oceXS$Db%IN5>V6?)RAHZK|Re5iVn%Zlp~bi z(^}+rd!n{bx+=~z0=h~@7MCWvk75x4E|17G-6HLZe0wd|Qk^SegpI9|_gxsNf{1&m zo_PToJ6=EyEe@fHfrkPME>rOrr|0t>6gXd})S{9Q zCFl!#8xmM!1(LHdQDw}fa(*g96|>wHO^9oh%`Fjz;aFg>nc3wCZ9_6{k!n!p17o2o zl&vrnxa6G1G-j!3&B}$cOZ?G>CGs#ctu3a2{}88`~bNkpfg2Y$%BZJRG45<%0|mQ$JvUkHdAXk zqqz|7Uu)K6TFOMn?MkyW?KBalsLz|>VJ{srO*>C1^D>(crl_NwDZFx`UTZ>0?^m*l z$lUVA8Z^bVV9dfAI)`(cPYZ9xA*m+A?UGY&=j`D&2OSSdpp#$4Q zc|O_ArXMY|V%7xcpDu+NM3)|gp{gi!6(>TeW#z0U3di?w3#=rgR*_p>-VpwYZ;P5W zq?E|)2tlt~Z2MbiEIQ9-g_q@dMio{{#8j3_1|^+B5OSJ6Nd<4Ev???Vl_POU=akh8 z+Buag0bJ_9_nX^gEO3CMMl303sz6K5;*vvwMO8y_E!X&R?+q|d4%09rV_c-rlL4uz z+>nHfn6T$sY+Ia$x3JeX5heM>qY*bH@3j8w> z4r2^qTE&$u+0a;M3yE|Cj9Yrww8)awtIW68=!PGYh2(+*XH7DPwfqJF@l(LSEm@^5 z23#qi0a1dQlrc1&gJpCbaTcNoX2M2PP7JRayP!sBq)1a!$ul)(ib>LT%ybx7{Dae! z4z$O9rcog)gyX6b1*2;TH2VLsl`|F>Ni{{5 z*@5<&`#Xw>>Z(*5ZyLmM#8#gU5qE1j)k5)xQMc z{b9d%t~@gw=D~+2=_jvw-us8Q^o{U*vt2&cJP|(94Eh(EYv=j7hah;@Pn-Ys{vJ;L z9e%a3zI@01-sRKj)6skH^8-E};BWO_FIfAE_h2J@zIo}<;MN-jgy5gvd7mFR-2S!y zqTgu0@#FUd-}E2!ANHRJ<4uSBn2+s#=Rf8D#1A{S_a5^f_1)uzX7|~+yL5)p--;fJ zei;3LN02|!dix*a-zLwrX7Bu5a#tdc&nM3$XTFy_oP_gVH2t%0eao9yRvxvOp|Li< z$NN`w*{`C -#include -#include -#include - -#include - -// This file uses assert() to verify algorithm correctness -#undef NDEBUG -#include - -struct PV -{ - unsigned short px, py, pz; - unsigned char nu, nv; // octahedron encoded normal, aliases .pw - unsigned short tx, ty; -}; - -// note: 4 6 5 triangle here is a combo-breaker: -// we encode it without rotating, a=next, c=next - this means we do *not* bump next to 6 -// which means that the next triangle can't be encoded via next sequencing! -static const unsigned int kIndexBuffer[] = {0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9}; - -static const unsigned char kIndexDataV0[] = { - 0xe0, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67, - 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/ -}; - -// note: this exercises two features of v1 format, restarts (0 1 2) and last -static const unsigned int kIndexBufferTricky[] = {0, 1, 2, 2, 1, 3, 0, 1, 2, 2, 1, 5, 2, 1, 4}; - -static const unsigned char kIndexDataV1[] = { - 0xe1, 0xf0, 0x10, 0xfe, 0x1f, 0x3d, 0x00, 0x0a, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, - 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/ -}; - -static const unsigned int kIndexSequence[] = {0, 1, 51, 2, 49, 1000}; - -static const unsigned char kIndexSequenceV1[] = { - 0xd1, 0x00, 0x04, 0xcd, 0x01, 0x04, 0x07, 0x98, 0x1f, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ -}; - -static const PV kVertexBuffer[] = { - {0, 0, 0, 0, 0, 0, 0}, - {300, 0, 0, 0, 0, 500, 0}, - {0, 300, 0, 0, 0, 0, 500}, - {300, 300, 0, 0, 0, 500, 500}, -}; - -static const unsigned char kVertexDataV0[] = { - 0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, - 0x0c, 0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x3f, 0x00, 0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, - 0x00, 0x00, 0x17, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/ -}; - -static void decodeIndexV0() -{ - const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); - - std::vector buffer(kIndexDataV0, kIndexDataV0 + sizeof(kIndexDataV0)); - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, kIndexBuffer, sizeof(kIndexBuffer)) == 0); -} - -static void decodeIndexV1() -{ - const size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]); - - std::vector buffer(kIndexDataV1, kIndexDataV1 + sizeof(kIndexDataV1)); - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0); -} - -static void decodeIndex16() -{ - const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); - const size_t vertex_count = 10; - - std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); - - unsigned short decoded[index_count]; - assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); - - for (size_t i = 0; i < index_count; ++i) - assert(decoded[i] == kIndexBuffer[i]); -} - -static void encodeIndexMemorySafe() -{ - const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); - const size_t vertex_count = 10; - - std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); - - // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access - for (size_t i = 0; i <= buffer.size(); ++i) - { - std::vector shortbuffer(i); - size_t result = meshopt_encodeIndexBuffer(i == 0 ? 0 : &shortbuffer[0], i, kIndexBuffer, index_count); - - if (i == buffer.size()) - assert(result == buffer.size()); - else - assert(result == 0); - } -} - -static void decodeIndexMemorySafe() -{ - const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); - const size_t vertex_count = 10; - - std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); - - // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access - unsigned int decoded[index_count]; - - for (size_t i = 0; i <= buffer.size(); ++i) - { - std::vector shortbuffer(buffer.begin(), buffer.begin() + i); - int result = meshopt_decodeIndexBuffer(decoded, index_count, i == 0 ? 0 : &shortbuffer[0], i); - - if (i == buffer.size()) - assert(result == 0); - else - assert(result < 0); - } -} - -static void decodeIndexRejectExtraBytes() -{ - const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); - const size_t vertex_count = 10; - - std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); - - // check that decoder doesn't accept extra bytes after a valid stream - std::vector largebuffer(buffer); - largebuffer.push_back(0); - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexBuffer(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0); -} - -static void decodeIndexRejectMalformedHeaders() -{ - const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); - const size_t vertex_count = 10; - - std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); - - // check that decoder doesn't accept malformed headers - std::vector brokenbuffer(buffer); - brokenbuffer[0] = 0; - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); -} - -static void decodeIndexRejectInvalidVersion() -{ - const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]); - const size_t vertex_count = 10; - - std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count)); - - // check that decoder doesn't accept invalid version - std::vector brokenbuffer(buffer); - brokenbuffer[0] |= 0x0f; - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); -} - -static void decodeIndexMalformedVByte() -{ - const unsigned char input[] = { - 0xe1, 0x20, 0x20, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0xff, 0xff, 0xff, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, // clang-format :-/ - }; - - unsigned int decoded[66]; - assert(meshopt_decodeIndexBuffer(decoded, 66, input, sizeof(input)) < 0); -} - -static void roundtripIndexTricky() -{ - const size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]); - const size_t vertex_count = 6; - - std::vector buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBufferTricky, index_count)); - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0); -} - -static void encodeIndexEmpty() -{ - std::vector buffer(meshopt_encodeIndexBufferBound(0, 0)); - buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), NULL, 0)); - - assert(meshopt_decodeIndexBuffer(static_cast(NULL), 0, &buffer[0], buffer.size()) == 0); -} - -static void decodeIndexSequence() -{ - const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); - - std::vector buffer(kIndexSequenceV1, kIndexSequenceV1 + sizeof(kIndexSequenceV1)); - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, kIndexSequence, sizeof(kIndexSequence)) == 0); -} - -static void decodeIndexSequence16() -{ - const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); - const size_t vertex_count = 1001; - - std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); - - unsigned short decoded[index_count]; - assert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0); - - for (size_t i = 0; i < index_count; ++i) - assert(decoded[i] == kIndexSequence[i]); -} - -static void encodeIndexSequenceMemorySafe() -{ - const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); - const size_t vertex_count = 1001; - - std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); - - // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access - for (size_t i = 0; i <= buffer.size(); ++i) - { - std::vector shortbuffer(i); - size_t result = meshopt_encodeIndexSequence(i == 0 ? 0 : &shortbuffer[0], i, kIndexSequence, index_count); - - if (i == buffer.size()) - assert(result == buffer.size()); - else - assert(result == 0); - } -} - -static void decodeIndexSequenceMemorySafe() -{ - const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); - const size_t vertex_count = 1001; - - std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); - - // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access - unsigned int decoded[index_count]; - - for (size_t i = 0; i <= buffer.size(); ++i) - { - std::vector shortbuffer(buffer.begin(), buffer.begin() + i); - int result = meshopt_decodeIndexSequence(decoded, index_count, i == 0 ? 0 : &shortbuffer[0], i); - - if (i == buffer.size()) - assert(result == 0); - else - assert(result < 0); - } -} - -static void decodeIndexSequenceRejectExtraBytes() -{ - const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); - const size_t vertex_count = 1001; - - std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); - - // check that decoder doesn't accept extra bytes after a valid stream - std::vector largebuffer(buffer); - largebuffer.push_back(0); - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexSequence(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0); -} - -static void decodeIndexSequenceRejectMalformedHeaders() -{ - const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); - const size_t vertex_count = 1001; - - std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); - - // check that decoder doesn't accept malformed headers - std::vector brokenbuffer(buffer); - brokenbuffer[0] = 0; - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); -} - -static void decodeIndexSequenceRejectInvalidVersion() -{ - const size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]); - const size_t vertex_count = 1001; - - std::vector buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count)); - - // check that decoder doesn't accept invalid version - std::vector brokenbuffer(buffer); - brokenbuffer[0] |= 0x0f; - - unsigned int decoded[index_count]; - assert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0); -} - -static void encodeIndexSequenceEmpty() -{ - std::vector buffer(meshopt_encodeIndexSequenceBound(0, 0)); - buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), NULL, 0)); - - assert(meshopt_decodeIndexSequence(static_cast(NULL), 0, &buffer[0], buffer.size()) == 0); -} - -static void decodeVertexV0() -{ - const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); - - std::vector buffer(kVertexDataV0, kVertexDataV0 + sizeof(kVertexDataV0)); - - PV decoded[vertex_count]; - assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0); -} - -static void encodeVertexMemorySafe() -{ - const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); - - std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); - - // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access - for (size_t i = 0; i <= buffer.size(); ++i) - { - std::vector shortbuffer(i); - size_t result = meshopt_encodeVertexBuffer(i == 0 ? 0 : &shortbuffer[0], i, kVertexBuffer, vertex_count, sizeof(PV)); - - if (i == buffer.size()) - assert(result == buffer.size()); - else - assert(result == 0); - } -} - -static void decodeVertexMemorySafe() -{ - const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); - - std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); - - // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access - PV decoded[vertex_count]; - - for (size_t i = 0; i <= buffer.size(); ++i) - { - std::vector shortbuffer(buffer.begin(), buffer.begin() + i); - int result = meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), i == 0 ? 0 : &shortbuffer[0], i); - (void)result; - - if (i == buffer.size()) - assert(result == 0); - else - assert(result < 0); - } -} - -static void decodeVertexRejectExtraBytes() -{ - const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); - - std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); - - // check that decoder doesn't accept extra bytes after a valid stream - std::vector largebuffer(buffer); - largebuffer.push_back(0); - - PV decoded[vertex_count]; - assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &largebuffer[0], largebuffer.size()) < 0); -} - -static void decodeVertexRejectMalformedHeaders() -{ - const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]); - - std::vector buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV))); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV))); - - // check that decoder doesn't accept malformed headers - std::vector brokenbuffer(buffer); - brokenbuffer[0] = 0; - - PV decoded[vertex_count]; - assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &brokenbuffer[0], brokenbuffer.size()) < 0); -} - -static void decodeVertexBitGroups() -{ - unsigned char data[16 * 4]; - - // this tests 0/2/4/8 bit groups in one stream - for (size_t i = 0; i < 16; ++i) - { - data[i * 4 + 0] = 0; - data[i * 4 + 1] = (unsigned char)(i * 1); - data[i * 4 + 2] = (unsigned char)(i * 2); - data[i * 4 + 3] = (unsigned char)(i * 8); - } - - std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); - - unsigned char decoded[16 * 4]; - assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, data, sizeof(data)) == 0); -} - -static void decodeVertexBitGroupSentinels() -{ - unsigned char data[16 * 4]; - - // this tests 0/2/4/8 bit groups and sentinels in one stream - for (size_t i = 0; i < 16; ++i) - { - if (i == 7 || i == 13) - { - data[i * 4 + 0] = 42; - data[i * 4 + 1] = 42; - data[i * 4 + 2] = 42; - data[i * 4 + 3] = 42; - } - else - { - data[i * 4 + 0] = 0; - data[i * 4 + 1] = (unsigned char)(i * 1); - data[i * 4 + 2] = (unsigned char)(i * 2); - data[i * 4 + 3] = (unsigned char)(i * 8); - } - } - - std::vector buffer(meshopt_encodeVertexBufferBound(16, 4)); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4)); - - unsigned char decoded[16 * 4]; - assert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, data, sizeof(data)) == 0); -} - -static void decodeVertexLarge() -{ - unsigned char data[128 * 4]; - - // this tests 0/2/4/8 bit groups in one stream - for (size_t i = 0; i < 128; ++i) - { - data[i * 4 + 0] = 0; - data[i * 4 + 1] = (unsigned char)(i * 1); - data[i * 4 + 2] = (unsigned char)(i * 2); - data[i * 4 + 3] = (unsigned char)(i * 8); - } - - std::vector buffer(meshopt_encodeVertexBufferBound(128, 4)); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 128, 4)); - - unsigned char decoded[128 * 4]; - assert(meshopt_decodeVertexBuffer(decoded, 128, 4, &buffer[0], buffer.size()) == 0); - assert(memcmp(decoded, data, sizeof(data)) == 0); -} - -static void encodeVertexEmpty() -{ - std::vector buffer(meshopt_encodeVertexBufferBound(0, 16)); - buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), NULL, 0, 16)); - - assert(meshopt_decodeVertexBuffer(NULL, 0, 16, &buffer[0], buffer.size()) == 0); -} - -static void decodeFilterOct8() -{ - const unsigned char data[4 * 4] = { - 0, 1, 127, 0, - 0, 187, 127, 1, - 255, 1, 127, 0, - 14, 130, 127, 1, // clang-format :-/ - }; - - const unsigned char expected[4 * 4] = { - 0, 1, 127, 0, - 0, 159, 82, 1, - 255, 1, 127, 0, - 1, 130, 241, 1, // clang-format :-/ - }; - - // Aligned by 4 - unsigned char full[4 * 4]; - memcpy(full, data, sizeof(full)); - meshopt_decodeFilterOct(full, 4, 4); - assert(memcmp(full, expected, sizeof(full)) == 0); - - // Tail processing for unaligned data - unsigned char tail[3 * 4]; - memcpy(tail, data, sizeof(tail)); - meshopt_decodeFilterOct(tail, 3, 4); - assert(memcmp(tail, expected, sizeof(tail)) == 0); -} - -static void decodeFilterOct12() -{ - const unsigned short data[4 * 4] = { - 0, 1, 2047, 0, - 0, 1870, 2047, 1, - 2017, 1, 2047, 0, - 14, 1300, 2047, 1, // clang-format :-/ - }; - - const unsigned short expected[4 * 4] = { - 0, 16, 32767, 0, - 0, 32621, 3088, 1, - 32764, 16, 471, 0, - 307, 28541, 16093, 1, // clang-format :-/ - }; - - // Aligned by 4 - unsigned short full[4 * 4]; - memcpy(full, data, sizeof(full)); - meshopt_decodeFilterOct(full, 4, 8); - assert(memcmp(full, expected, sizeof(full)) == 0); - - // Tail processing for unaligned data - unsigned short tail[3 * 4]; - memcpy(tail, data, sizeof(tail)); - meshopt_decodeFilterOct(tail, 3, 8); - assert(memcmp(tail, expected, sizeof(tail)) == 0); -} - -static void decodeFilterQuat12() -{ - const unsigned short data[4 * 4] = { - 0, 1, 0, 0x7fc, - 0, 1870, 0, 0x7fd, - 2017, 1, 0, 0x7fe, - 14, 1300, 0, 0x7ff, // clang-format :-/ - }; - - const unsigned short expected[4 * 4] = { - 32767, 0, 11, 0, - 0, 25013, 0, 21166, - 11, 0, 23504, 22830, - 158, 14715, 0, 29277, // clang-format :-/ - }; - - // Aligned by 4 - unsigned short full[4 * 4]; - memcpy(full, data, sizeof(full)); - meshopt_decodeFilterQuat(full, 4, 8); - assert(memcmp(full, expected, sizeof(full)) == 0); - - // Tail processing for unaligned data - unsigned short tail[3 * 4]; - memcpy(tail, data, sizeof(tail)); - meshopt_decodeFilterQuat(tail, 3, 8); - assert(memcmp(tail, expected, sizeof(tail)) == 0); -} - -static void decodeFilterExp() -{ - const unsigned int data[4] = { - 0, - 0xff000003, - 0x02fffff7, - 0xfe7fffff, // clang-format :-/ - }; - - const unsigned int expected[4] = { - 0, - 0x3fc00000, - 0xc2100000, - 0x49fffffe, // clang-format :-/ - }; - - // Aligned by 4 - unsigned int full[4]; - memcpy(full, data, sizeof(full)); - meshopt_decodeFilterExp(full, 4, 4); - assert(memcmp(full, expected, sizeof(full)) == 0); - - // Tail processing for unaligned data - unsigned int tail[3]; - memcpy(tail, data, sizeof(tail)); - meshopt_decodeFilterExp(tail, 3, 4); - assert(memcmp(tail, expected, sizeof(tail)) == 0); -} - -void encodeFilterOct8() -{ - const float data[4 * 4] = { - 1, 0, 0, 0, - 0, -1, 0, 0, - 0.7071068f, 0, 0.707168f, 1, - -0.7071068f, 0, -0.707168f, 1, // clang-format :-/ - }; - - const unsigned char expected[4 * 4] = { - 0x7f, 0, 0x7f, 0, - 0, 0x81, 0x7f, 0, - 0x3f, 0, 0x7f, 0x7f, - 0x81, 0x40, 0x7f, 0x7f, // clang-format :-/ - }; - - unsigned char encoded[4 * 4]; - meshopt_encodeFilterOct(encoded, 4, 4, 8, data); - - assert(memcmp(encoded, expected, sizeof(expected)) == 0); - - signed char decoded[4 * 4]; - memcpy(decoded, encoded, sizeof(decoded)); - meshopt_decodeFilterOct(decoded, 4, 4); - - for (size_t i = 0; i < 4 * 4; ++i) - assert(fabsf(decoded[i] / 127.f - data[i]) < 1e-2f); -} - -void encodeFilterOct12() -{ - const float data[4 * 4] = { - 1, 0, 0, 0, - 0, -1, 0, 0, - 0.7071068f, 0, 0.707168f, 1, - -0.7071068f, 0, -0.707168f, 1, // clang-format :-/ - }; - - const unsigned short expected[4 * 4] = { - 0x7ff, 0, 0x7ff, 0, - 0x0, 0xf801, 0x7ff, 0, - 0x3ff, 0, 0x7ff, 0x7fff, - 0xf801, 0x400, 0x7ff, 0x7fff, // clang-format :-/ - }; - - unsigned short encoded[4 * 4]; - meshopt_encodeFilterOct(encoded, 4, 8, 12, data); - - assert(memcmp(encoded, expected, sizeof(expected)) == 0); - - short decoded[4 * 4]; - memcpy(decoded, encoded, sizeof(decoded)); - meshopt_decodeFilterOct(decoded, 4, 8); - - for (size_t i = 0; i < 4 * 4; ++i) - assert(fabsf(decoded[i] / 32767.f - data[i]) < 1e-3f); -} - -void encodeFilterQuat12() -{ - const float data[4 * 4] = { - 1, 0, 0, 0, - 0, -1, 0, 0, - 0.7071068f, 0, 0, 0.707168f, - -0.7071068f, 0, 0, -0.707168f, // clang-format :-/ - }; - - const unsigned short expected[4 * 4] = { - 0, 0, 0, 0x7fc, - 0, 0, 0, 0x7fd, - 0x7ff, 0, 0, 0x7ff, - 0x7ff, 0, 0, 0x7ff, // clang-format :-/ - }; - - unsigned short encoded[4 * 4]; - meshopt_encodeFilterQuat(encoded, 4, 8, 12, data); - - assert(memcmp(encoded, expected, sizeof(expected)) == 0); - - short decoded[4 * 4]; - memcpy(decoded, encoded, sizeof(decoded)); - meshopt_decodeFilterQuat(decoded, 4, 8); - - for (size_t i = 0; i < 4; ++i) - { - float dx = decoded[i * 4 + 0] / 32767.f; - float dy = decoded[i * 4 + 1] / 32767.f; - float dz = decoded[i * 4 + 2] / 32767.f; - float dw = decoded[i * 4 + 3] / 32767.f; - - float dp = - data[i * 4 + 0] * dx + - data[i * 4 + 1] * dy + - data[i * 4 + 2] * dz + - data[i * 4 + 3] * dw; - - assert(fabsf(fabsf(dp) - 1.f) < 1e-4f); - } -} - -void encodeFilterExp() -{ - const float data[3] = { - 1, - -23.4f, - -0.1f, - }; - - const unsigned int expected[3] = { - 0xf7000200, - 0xf7ffd133, - 0xf7ffffcd, - }; - - unsigned int encoded[3]; - meshopt_encodeFilterExp(encoded, 1, 12, 15, data); - - assert(memcmp(encoded, expected, sizeof(expected)) == 0); - - float decoded[3]; - memcpy(decoded, encoded, sizeof(decoded)); - meshopt_decodeFilterExp(decoded, 3, 4); - - for (size_t i = 0; i < 3; ++i) - assert(fabsf(decoded[i] - data[i]) < 1e-3f); -} - -static void clusterBoundsDegenerate() -{ - const float vbd[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; - const unsigned int ibd[] = {0, 0, 0}; - const unsigned int ib1[] = {0, 1, 2}; - - // all of the bounds below are degenerate as they use 0 triangles, one topology-degenerate triangle and one position-degenerate triangle respectively - meshopt_Bounds bounds0 = meshopt_computeClusterBounds(0, 0, 0, 0, 12); - meshopt_Bounds boundsd = meshopt_computeClusterBounds(ibd, 3, vbd, 3, 12); - meshopt_Bounds bounds1 = meshopt_computeClusterBounds(ib1, 3, vbd, 3, 12); - - assert(bounds0.center[0] == 0 && bounds0.center[1] == 0 && bounds0.center[2] == 0 && bounds0.radius == 0); - assert(boundsd.center[0] == 0 && boundsd.center[1] == 0 && boundsd.center[2] == 0 && boundsd.radius == 0); - assert(bounds1.center[0] == 0 && bounds1.center[1] == 0 && bounds1.center[2] == 0 && bounds1.radius == 0); - - const float vb1[] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; - const unsigned int ib2[] = {0, 1, 2, 0, 2, 1}; - - // these bounds have a degenerate cone since the cluster has two triangles with opposite normals - meshopt_Bounds bounds2 = meshopt_computeClusterBounds(ib2, 6, vb1, 3, 12); - - assert(bounds2.cone_apex[0] == 0 && bounds2.cone_apex[1] == 0 && bounds2.cone_apex[2] == 0); - assert(bounds2.cone_axis[0] == 0 && bounds2.cone_axis[1] == 0 && bounds2.cone_axis[2] == 0); - assert(bounds2.cone_cutoff == 1); - assert(bounds2.cone_axis_s8[0] == 0 && bounds2.cone_axis_s8[1] == 0 && bounds2.cone_axis_s8[2] == 0); - assert(bounds2.cone_cutoff_s8 == 127); - - // however, the bounding sphere needs to be in tact (here we only check bbox for simplicity) - assert(bounds2.center[0] - bounds2.radius <= 0 && bounds2.center[0] + bounds2.radius >= 1); - assert(bounds2.center[1] - bounds2.radius <= 0 && bounds2.center[1] + bounds2.radius >= 1); - assert(bounds2.center[2] - bounds2.radius <= 0 && bounds2.center[2] + bounds2.radius >= 1); -} - -static size_t allocCount; -static size_t freeCount; - -static void* customAlloc(size_t size) -{ - allocCount++; - - return malloc(size); -} - -static void customFree(void* ptr) -{ - freeCount++; - - free(ptr); -} - -static void customAllocator() -{ - meshopt_setAllocator(customAlloc, customFree); - - assert(allocCount == 0 && freeCount == 0); - - float vb[] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; - unsigned int ib[] = {0, 1, 2}; - unsigned short ibs[] = {0, 1, 2}; - - // meshopt_computeClusterBounds doesn't allocate - meshopt_computeClusterBounds(ib, 3, vb, 3, 12); - assert(allocCount == 0 && freeCount == 0); - - // ... unless IndexAdapter is used - meshopt_computeClusterBounds(ibs, 3, vb, 3, 12); - assert(allocCount == 1 && freeCount == 1); - - // meshopt_optimizeVertexFetch allocates internal remap table and temporary storage for in-place remaps - meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12); - assert(allocCount == 3 && freeCount == 3); - - // ... plus one for IndexAdapter - meshopt_optimizeVertexFetch(vb, ibs, 3, vb, 3, 12); - assert(allocCount == 6 && freeCount == 6); - - meshopt_setAllocator(operator new, operator delete); - - // customAlloc & customFree should not get called anymore - meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12); - assert(allocCount == 6 && freeCount == 6); - - allocCount = freeCount = 0; -} - -static void emptyMesh() -{ - meshopt_optimizeVertexCache(0, 0, 0, 0); - meshopt_optimizeVertexCacheFifo(0, 0, 0, 0, 16); - meshopt_optimizeOverdraw(0, 0, 0, 0, 0, 12, 1.f); -} - -static void simplifyStuck() -{ - // tetrahedron can't be simplified due to collapse error restrictions - float vb1[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}; - unsigned int ib1[] = {0, 1, 2, 0, 2, 3, 0, 3, 1, 2, 1, 3}; - - assert(meshopt_simplify(ib1, ib1, 12, vb1, 4, 12, 6, 1e-3f) == 12); - - // 5-vertex strip can't be simplified due to topology restriction since middle triangle has flipped winding - float vb2[] = {0, 0, 0, 1, 0, 0, 2, 0, 0, 0.5f, 1, 0, 1.5f, 1, 0}; - unsigned int ib2[] = {0, 1, 3, 3, 1, 4, 1, 2, 4}; // ok - unsigned int ib3[] = {0, 1, 3, 1, 3, 4, 1, 2, 4}; // flipped - - assert(meshopt_simplify(ib2, ib2, 9, vb2, 5, 12, 6, 1e-3f) == 6); - assert(meshopt_simplify(ib3, ib3, 9, vb2, 5, 12, 6, 1e-3f) == 9); - - // 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction - float vb4[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0}; - unsigned int ib4[] = {0, 1, 3, 0, 3, 2}; - - assert(meshopt_simplify(ib4, ib4, 6, vb4, 4, 12, 3, 1e-3f) == 6); - - // 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction - float vb5[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0}; - unsigned int ib5[] = {0, 1, 4, 0, 3, 2}; - - assert(meshopt_simplify(ib5, ib5, 6, vb5, 5, 12, 3, 1e-3f) == 6); -} - -static void simplifySloppyStuck() -{ - const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; - const unsigned int ib[] = {0, 1, 2, 0, 1, 2}; - - unsigned int* target = NULL; - - // simplifying down to 0 triangles results in 0 immediately - assert(meshopt_simplifySloppy(target, ib, 3, vb, 3, 12, 0, 0.f) == 0); - - // simplifying down to 2 triangles given that all triangles are degenerate results in 0 as well - assert(meshopt_simplifySloppy(target, ib, 6, vb, 3, 12, 6, 0.f) == 0); -} - -static void simplifyPointsStuck() -{ - const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; - - // simplifying down to 0 points results in 0 immediately - assert(meshopt_simplifyPoints(0, vb, 3, 12, 0) == 0); -} - -static void simplifyFlip() -{ - // this mesh has been constructed by taking a tessellated irregular grid with a square cutout - // and progressively collapsing edges until the only ones left violate border or flip constraints. - // there is only one valid non-flip collapse, so we validate that we take it; when flips are allowed, - // the wrong collapse is picked instead. - float vb[] = { - 1.000000f, 1.000000f, -1.000000f, - 1.000000f, 1.000000f, 1.000000f, - 1.000000f, -1.000000f, 1.000000f, - 1.000000f, -0.200000f, -0.200000f, - 1.000000f, 0.200000f, -0.200000f, - 1.000000f, -0.200000f, 0.200000f, - 1.000000f, 0.200000f, 0.200000f, - 1.000000f, 0.500000f, -0.500000f, - 1.000000f, -1.000000f, 0.000000f, // clang-format :-/ - }; - - // the collapse we expect is 7 -> 0 - unsigned int ib[] = { - 7, 4, 3, - 1, 2, 5, - 7, 1, 6, - 7, 8, 0, // gets removed - 7, 6, 4, - 8, 5, 2, - 8, 7, 3, - 8, 3, 5, - 5, 6, 1, - 7, 0, 1, // gets removed - }; - - unsigned int expected[] = { - 0, 4, 3, - 1, 2, 5, - 0, 1, 6, - 0, 6, 4, - 8, 5, 2, - 8, 0, 3, - 8, 3, 5, - 5, 6, 1, // clang-format :-/ - }; - - assert(meshopt_simplify(ib, ib, 30, vb, 9, 12, 3, 1e-3f) == 24); - assert(memcmp(ib, expected, sizeof(expected)) == 0); -} - -static void simplifyScale() -{ - const float vb[] = {0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}; - - assert(meshopt_simplifyScale(vb, 4, 12) == 3.f); -} - -static void simplifyDegenerate() -{ - float vb[] = { - 0.000000f, 0.000000f, 0.000000f, - 0.000000f, 1.000000f, 0.000000f, - 0.000000f, 2.000000f, 0.000000f, - 1.000000f, 0.000000f, 0.000000f, - 2.000000f, 0.000000f, 0.000000f, - 1.000000f, 1.000000f, 0.000000f, // clang-format :-/ - }; - - // 0 1 2 - // 3 5 - // 4 - - unsigned int ib[] = { - 0, 1, 3, - 3, 1, 5, - 1, 2, 5, - 3, 5, 4, - 1, 0, 1, // these two degenerate triangles create a fake reverse edge - 0, 3, 0, // which breaks border classification - }; - - unsigned int expected[] = { - 0, 1, 4, - 4, 1, 2, // clang-format :-/ - }; - - assert(meshopt_simplify(ib, ib, 18, vb, 6, 12, 3, 1e-3f) == 6); - assert(memcmp(ib, expected, sizeof(expected)) == 0); -} - -static void simplifyLockBorder() -{ - float vb[] = { - 0.000000f, 0.000000f, 0.000000f, - 0.000000f, 1.000000f, 0.000000f, - 0.000000f, 2.000000f, 0.000000f, - 1.000000f, 0.000000f, 0.000000f, - 1.000000f, 1.000000f, 0.000000f, - 1.000000f, 2.000000f, 0.000000f, - 2.000000f, 0.000000f, 0.000000f, - 2.000000f, 1.000000f, 0.000000f, - 2.000000f, 2.000000f, 0.000000f, // clang-format :-/ - }; - - // 0 1 2 - // 3 4 5 - // 6 7 8 - - unsigned int ib[] = { - 0, 1, 3, - 3, 1, 4, - 1, 2, 4, - 4, 2, 5, - 3, 4, 6, - 6, 4, 7, - 4, 5, 7, - 7, 5, 8, // clang-format :-/ - }; - - unsigned int expected[] = { - 0, 1, 3, - 1, 2, 3, - 3, 2, 5, - 6, 3, 7, - 3, 5, 7, - 7, 5, 8, // clang-format :-/ - }; - - assert(meshopt_simplify(ib, ib, 24, vb, 9, 12, 3, 1e-3f, meshopt_SimplifyLockBorder) == 18); - assert(memcmp(ib, expected, sizeof(expected)) == 0); -} - -static void adjacency() -{ - // 0 1/4 - // 2/5 3 - const float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0}; - const unsigned int ib[] = {0, 1, 2, 5, 4, 3}; - - unsigned int adjib[12]; - meshopt_generateAdjacencyIndexBuffer(adjib, ib, 6, vb, 6, 12); - - unsigned int expected[] = { - // patch 0 - 0, 0, - 1, 3, - 2, 2, - - // patch 1 - 5, 0, - 4, 4, - 3, 3, - - // clang-format :-/ - }; - - assert(memcmp(adjib, expected, sizeof(expected)) == 0); -} - -static void tessellation() -{ - // 0 1/4 - // 2/5 3 - const float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0}; - const unsigned int ib[] = {0, 1, 2, 5, 4, 3}; - - unsigned int tessib[24]; - meshopt_generateTessellationIndexBuffer(tessib, ib, 6, vb, 6, 12); - - unsigned int expected[] = { - // patch 0 - 0, 1, 2, - 0, 1, - 4, 5, - 2, 0, - 0, 1, 2, - - // patch 1 - 5, 4, 3, - 2, 1, - 4, 3, - 3, 5, - 2, 1, 3, - - // clang-format :-/ - }; - - assert(memcmp(tessib, expected, sizeof(expected)) == 0); -} - -void runTests() -{ - decodeIndexV0(); - decodeIndexV1(); - decodeIndex16(); - encodeIndexMemorySafe(); - decodeIndexMemorySafe(); - decodeIndexRejectExtraBytes(); - decodeIndexRejectMalformedHeaders(); - decodeIndexRejectInvalidVersion(); - decodeIndexMalformedVByte(); - roundtripIndexTricky(); - encodeIndexEmpty(); - - decodeIndexSequence(); - decodeIndexSequence16(); - encodeIndexSequenceMemorySafe(); - decodeIndexSequenceMemorySafe(); - decodeIndexSequenceRejectExtraBytes(); - decodeIndexSequenceRejectMalformedHeaders(); - decodeIndexSequenceRejectInvalidVersion(); - encodeIndexSequenceEmpty(); - - decodeVertexV0(); - encodeVertexMemorySafe(); - decodeVertexMemorySafe(); - decodeVertexRejectExtraBytes(); - decodeVertexRejectMalformedHeaders(); - decodeVertexBitGroups(); - decodeVertexBitGroupSentinels(); - decodeVertexLarge(); - encodeVertexEmpty(); - - decodeFilterOct8(); - decodeFilterOct12(); - decodeFilterQuat12(); - decodeFilterExp(); - - encodeFilterOct8(); - encodeFilterOct12(); - encodeFilterQuat12(); - encodeFilterExp(); - - clusterBoundsDegenerate(); - - customAllocator(); - - emptyMesh(); - - simplifyStuck(); - simplifySloppyStuck(); - simplifyPointsStuck(); - simplifyFlip(); - simplifyScale(); - simplifyDegenerate(); - simplifyLockBorder(); - - adjacency(); - tessellation(); -} diff --git a/Dependencies/meshoptimizer/extern/cgltf.h b/Dependencies/meshoptimizer/extern/cgltf.h deleted file mode 100644 index 485a95ec..00000000 --- a/Dependencies/meshoptimizer/extern/cgltf.h +++ /dev/null @@ -1,6638 +0,0 @@ -/** - * cgltf - a single-file glTF 2.0 parser written in C99. - * - * Version: 1.11 - * - * Website: https://github.com/jkuhlmann/cgltf - * - * Distributed under the MIT License, see notice at the end of this file. - * - * Building: - * Include this file where you need the struct and function - * declarations. Have exactly one source file where you define - * `CGLTF_IMPLEMENTATION` before including this file to get the - * function definitions. - * - * Reference: - * `cgltf_result cgltf_parse(const cgltf_options*, const void*, - * cgltf_size, cgltf_data**)` parses both glTF and GLB data. If - * this function returns `cgltf_result_success`, you have to call - * `cgltf_free()` on the created `cgltf_data*` variable. - * Note that contents of external files for buffers and images are not - * automatically loaded. You'll need to read these files yourself using - * URIs in the `cgltf_data` structure. - * - * `cgltf_options` is the struct passed to `cgltf_parse()` to control - * parts of the parsing process. You can use it to force the file type - * and provide memory allocation as well as file operation callbacks. - * Should be zero-initialized to trigger default behavior. - * - * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`. - * It generally mirrors the glTF format as described by the spec (see - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0). - * - * `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data` - * variable. - * - * `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*, - * const char* gltf_path)` can be optionally called to open and read buffer - * files using the `FILE*` APIs. The `gltf_path` argument is the path to - * the original glTF file, which allows the parser to resolve the path to - * buffer files. - * - * `cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, - * cgltf_size size, const char* base64, void** out_data)` decodes - * base64-encoded data content. Used internally by `cgltf_load_buffers()`. - * This is useful when decoding data URIs in images. - * - * `cgltf_result cgltf_parse_file(const cgltf_options* options, const - * char* path, cgltf_data** out_data)` can be used to open the given - * file using `FILE*` APIs and parse the data using `cgltf_parse()`. - * - * `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional - * checks to make sure the parsed glTF data is valid. - * - * `cgltf_node_transform_local` converts the translation / rotation / scale properties of a node - * into a mat4. - * - * `cgltf_node_transform_world` calls `cgltf_node_transform_local` on every ancestor in order - * to compute the root-to-node transformation. - * - * `cgltf_accessor_unpack_floats` reads in the data from an accessor, applies sparse data (if any), - * and converts them to floating point. Assumes that `cgltf_load_buffers` has already been called. - * By passing null for the output pointer, users can find out how many floats are required in the - * output buffer. - * - * `cgltf_accessor_num_components` is a tiny utility that tells you the dimensionality of - * a certain accessor type. This can be used before `cgltf_accessor_unpack_floats` to help allocate - * the necessary amount of memory. - * - * `cgltf_accessor_read_float` reads a certain element from a non-sparse accessor and converts it to - * floating point, assuming that `cgltf_load_buffers` has already been called. The passed-in element - * size is the number of floats in the output buffer, which should be in the range [1, 16]. Returns - * false if the passed-in element_size is too small, or if the accessor is sparse. - * - * `cgltf_accessor_read_uint` is similar to its floating-point counterpart, but limited to reading - * vector types and does not support matrix types. The passed-in element size is the number of uints - * in the output buffer, which should be in the range [1, 4]. Returns false if the passed-in - * element_size is too small, or if the accessor is sparse. - * - * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t - * and only works with single-component data types. - * - * `cgltf_result cgltf_copy_extras_json(const cgltf_data*, const cgltf_extras*, - * char* dest, cgltf_size* dest_size)` allows users to retrieve the "extras" data that - * can be attached to many glTF objects (which can be arbitrary JSON data). The - * `cgltf_extras` struct stores the offsets of the start and end of the extras JSON data - * as it appears in the complete glTF JSON data. This function copies the extras data - * into the provided buffer. If `dest` is NULL, the length of the data is written into - * `dest_size`. You can then parse this data using your own JSON parser - * or, if you've included the cgltf implementation using the integrated JSMN JSON parser. - */ -#ifndef CGLTF_H_INCLUDED__ -#define CGLTF_H_INCLUDED__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef size_t cgltf_size; -typedef float cgltf_float; -typedef int cgltf_int; -typedef unsigned int cgltf_uint; -typedef int cgltf_bool; - -typedef enum cgltf_file_type -{ - cgltf_file_type_invalid, - cgltf_file_type_gltf, - cgltf_file_type_glb, -} cgltf_file_type; - -typedef enum cgltf_result -{ - cgltf_result_success, - cgltf_result_data_too_short, - cgltf_result_unknown_format, - cgltf_result_invalid_json, - cgltf_result_invalid_gltf, - cgltf_result_invalid_options, - cgltf_result_file_not_found, - cgltf_result_io_error, - cgltf_result_out_of_memory, - cgltf_result_legacy_gltf, -} cgltf_result; - -typedef struct cgltf_memory_options -{ - void* (*alloc)(void* user, cgltf_size size); - void (*free) (void* user, void* ptr); - void* user_data; -} cgltf_memory_options; - -typedef struct cgltf_file_options -{ - cgltf_result(*read)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data); - void (*release)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data); - void* user_data; -} cgltf_file_options; - -typedef struct cgltf_options -{ - cgltf_file_type type; /* invalid == auto detect */ - cgltf_size json_token_count; /* 0 == auto */ - cgltf_memory_options memory; - cgltf_file_options file; -} cgltf_options; - -typedef enum cgltf_buffer_view_type -{ - cgltf_buffer_view_type_invalid, - cgltf_buffer_view_type_indices, - cgltf_buffer_view_type_vertices, -} cgltf_buffer_view_type; - -typedef enum cgltf_attribute_type -{ - cgltf_attribute_type_invalid, - cgltf_attribute_type_position, - cgltf_attribute_type_normal, - cgltf_attribute_type_tangent, - cgltf_attribute_type_texcoord, - cgltf_attribute_type_color, - cgltf_attribute_type_joints, - cgltf_attribute_type_weights, -} cgltf_attribute_type; - -typedef enum cgltf_component_type -{ - cgltf_component_type_invalid, - cgltf_component_type_r_8, /* BYTE */ - cgltf_component_type_r_8u, /* UNSIGNED_BYTE */ - cgltf_component_type_r_16, /* SHORT */ - cgltf_component_type_r_16u, /* UNSIGNED_SHORT */ - cgltf_component_type_r_32u, /* UNSIGNED_INT */ - cgltf_component_type_r_32f, /* FLOAT */ -} cgltf_component_type; - -typedef enum cgltf_type -{ - cgltf_type_invalid, - cgltf_type_scalar, - cgltf_type_vec2, - cgltf_type_vec3, - cgltf_type_vec4, - cgltf_type_mat2, - cgltf_type_mat3, - cgltf_type_mat4, -} cgltf_type; - -typedef enum cgltf_primitive_type -{ - cgltf_primitive_type_points, - cgltf_primitive_type_lines, - cgltf_primitive_type_line_loop, - cgltf_primitive_type_line_strip, - cgltf_primitive_type_triangles, - cgltf_primitive_type_triangle_strip, - cgltf_primitive_type_triangle_fan, -} cgltf_primitive_type; - -typedef enum cgltf_alpha_mode -{ - cgltf_alpha_mode_opaque, - cgltf_alpha_mode_mask, - cgltf_alpha_mode_blend, -} cgltf_alpha_mode; - -typedef enum cgltf_animation_path_type { - cgltf_animation_path_type_invalid, - cgltf_animation_path_type_translation, - cgltf_animation_path_type_rotation, - cgltf_animation_path_type_scale, - cgltf_animation_path_type_weights, -} cgltf_animation_path_type; - -typedef enum cgltf_interpolation_type { - cgltf_interpolation_type_linear, - cgltf_interpolation_type_step, - cgltf_interpolation_type_cubic_spline, -} cgltf_interpolation_type; - -typedef enum cgltf_camera_type { - cgltf_camera_type_invalid, - cgltf_camera_type_perspective, - cgltf_camera_type_orthographic, -} cgltf_camera_type; - -typedef enum cgltf_light_type { - cgltf_light_type_invalid, - cgltf_light_type_directional, - cgltf_light_type_point, - cgltf_light_type_spot, -} cgltf_light_type; - -typedef enum cgltf_data_free_method { - cgltf_data_free_method_none, - cgltf_data_free_method_file_release, - cgltf_data_free_method_memory_free, -} cgltf_data_free_method; - -typedef struct cgltf_extras { - cgltf_size start_offset; - cgltf_size end_offset; -} cgltf_extras; - -typedef struct cgltf_extension { - char* name; - char* data; -} cgltf_extension; - -typedef struct cgltf_buffer -{ - char* name; - cgltf_size size; - char* uri; - void* data; /* loaded by cgltf_load_buffers */ - cgltf_data_free_method data_free_method; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_buffer; - -typedef enum cgltf_meshopt_compression_mode { - cgltf_meshopt_compression_mode_invalid, - cgltf_meshopt_compression_mode_attributes, - cgltf_meshopt_compression_mode_triangles, - cgltf_meshopt_compression_mode_indices, -} cgltf_meshopt_compression_mode; - -typedef enum cgltf_meshopt_compression_filter { - cgltf_meshopt_compression_filter_none, - cgltf_meshopt_compression_filter_octahedral, - cgltf_meshopt_compression_filter_quaternion, - cgltf_meshopt_compression_filter_exponential, -} cgltf_meshopt_compression_filter; - -typedef struct cgltf_meshopt_compression -{ - cgltf_buffer* buffer; - cgltf_size offset; - cgltf_size size; - cgltf_size stride; - cgltf_size count; - cgltf_meshopt_compression_mode mode; - cgltf_meshopt_compression_filter filter; -} cgltf_meshopt_compression; - -typedef struct cgltf_buffer_view -{ - char *name; - cgltf_buffer* buffer; - cgltf_size offset; - cgltf_size size; - cgltf_size stride; /* 0 == automatically determined by accessor */ - cgltf_buffer_view_type type; - void* data; /* overrides buffer->data if present, filled by extensions */ - cgltf_bool has_meshopt_compression; - cgltf_meshopt_compression meshopt_compression; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_buffer_view; - -typedef struct cgltf_accessor_sparse -{ - cgltf_size count; - cgltf_buffer_view* indices_buffer_view; - cgltf_size indices_byte_offset; - cgltf_component_type indices_component_type; - cgltf_buffer_view* values_buffer_view; - cgltf_size values_byte_offset; - cgltf_extras extras; - cgltf_extras indices_extras; - cgltf_extras values_extras; - cgltf_size extensions_count; - cgltf_extension* extensions; - cgltf_size indices_extensions_count; - cgltf_extension* indices_extensions; - cgltf_size values_extensions_count; - cgltf_extension* values_extensions; -} cgltf_accessor_sparse; - -typedef struct cgltf_accessor -{ - char* name; - cgltf_component_type component_type; - cgltf_bool normalized; - cgltf_type type; - cgltf_size offset; - cgltf_size count; - cgltf_size stride; - cgltf_buffer_view* buffer_view; - cgltf_bool has_min; - cgltf_float min[16]; - cgltf_bool has_max; - cgltf_float max[16]; - cgltf_bool is_sparse; - cgltf_accessor_sparse sparse; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_accessor; - -typedef struct cgltf_attribute -{ - char* name; - cgltf_attribute_type type; - cgltf_int index; - cgltf_accessor* data; -} cgltf_attribute; - -typedef struct cgltf_image -{ - char* name; - char* uri; - cgltf_buffer_view* buffer_view; - char* mime_type; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_image; - -typedef struct cgltf_sampler -{ - char* name; - cgltf_int mag_filter; - cgltf_int min_filter; - cgltf_int wrap_s; - cgltf_int wrap_t; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_sampler; - -typedef struct cgltf_texture -{ - char* name; - cgltf_image* image; - cgltf_sampler* sampler; - cgltf_bool has_basisu; - cgltf_image* basisu_image; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_texture; - -typedef struct cgltf_texture_transform -{ - cgltf_float offset[2]; - cgltf_float rotation; - cgltf_float scale[2]; - cgltf_bool has_texcoord; - cgltf_int texcoord; -} cgltf_texture_transform; - -typedef struct cgltf_texture_view -{ - cgltf_texture* texture; - cgltf_int texcoord; - cgltf_float scale; /* equivalent to strength for occlusion_texture */ - cgltf_bool has_transform; - cgltf_texture_transform transform; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_texture_view; - -typedef struct cgltf_pbr_metallic_roughness -{ - cgltf_texture_view base_color_texture; - cgltf_texture_view metallic_roughness_texture; - - cgltf_float base_color_factor[4]; - cgltf_float metallic_factor; - cgltf_float roughness_factor; - - cgltf_extras extras; -} cgltf_pbr_metallic_roughness; - -typedef struct cgltf_pbr_specular_glossiness -{ - cgltf_texture_view diffuse_texture; - cgltf_texture_view specular_glossiness_texture; - - cgltf_float diffuse_factor[4]; - cgltf_float specular_factor[3]; - cgltf_float glossiness_factor; -} cgltf_pbr_specular_glossiness; - -typedef struct cgltf_clearcoat -{ - cgltf_texture_view clearcoat_texture; - cgltf_texture_view clearcoat_roughness_texture; - cgltf_texture_view clearcoat_normal_texture; - - cgltf_float clearcoat_factor; - cgltf_float clearcoat_roughness_factor; -} cgltf_clearcoat; - -typedef struct cgltf_transmission -{ - cgltf_texture_view transmission_texture; - cgltf_float transmission_factor; -} cgltf_transmission; - -typedef struct cgltf_ior -{ - cgltf_float ior; -} cgltf_ior; - -typedef struct cgltf_specular -{ - cgltf_texture_view specular_texture; - cgltf_texture_view specular_color_texture; - cgltf_float specular_color_factor[3]; - cgltf_float specular_factor; -} cgltf_specular; - -typedef struct cgltf_volume -{ - cgltf_texture_view thickness_texture; - cgltf_float thickness_factor; - cgltf_float attenuation_color[3]; - cgltf_float attenuation_distance; -} cgltf_volume; - -typedef struct cgltf_sheen -{ - cgltf_texture_view sheen_color_texture; - cgltf_float sheen_color_factor[3]; - cgltf_texture_view sheen_roughness_texture; - cgltf_float sheen_roughness_factor; -} cgltf_sheen; - -typedef struct cgltf_emissive_strength -{ - cgltf_float emissive_strength; -} cgltf_emissive_strength; - -typedef struct cgltf_iridescence -{ - cgltf_float iridescence_factor; - cgltf_texture_view iridescence_texture; - cgltf_float iridescence_ior; - cgltf_float iridescence_thickness_min; - cgltf_float iridescence_thickness_max; - cgltf_texture_view iridescence_thickness_texture; -} cgltf_iridescence; - -typedef struct cgltf_material -{ - char* name; - cgltf_bool has_pbr_metallic_roughness; - cgltf_bool has_pbr_specular_glossiness; - cgltf_bool has_clearcoat; - cgltf_bool has_transmission; - cgltf_bool has_volume; - cgltf_bool has_ior; - cgltf_bool has_specular; - cgltf_bool has_sheen; - cgltf_bool has_emissive_strength; - cgltf_bool has_iridescence; - cgltf_pbr_metallic_roughness pbr_metallic_roughness; - cgltf_pbr_specular_glossiness pbr_specular_glossiness; - cgltf_clearcoat clearcoat; - cgltf_ior ior; - cgltf_specular specular; - cgltf_sheen sheen; - cgltf_transmission transmission; - cgltf_volume volume; - cgltf_emissive_strength emissive_strength; - cgltf_iridescence iridescence; - cgltf_texture_view normal_texture; - cgltf_texture_view occlusion_texture; - cgltf_texture_view emissive_texture; - cgltf_float emissive_factor[3]; - cgltf_alpha_mode alpha_mode; - cgltf_float alpha_cutoff; - cgltf_bool double_sided; - cgltf_bool unlit; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_material; - -typedef struct cgltf_material_mapping -{ - cgltf_size variant; - cgltf_material* material; - cgltf_extras extras; -} cgltf_material_mapping; - -typedef struct cgltf_morph_target { - cgltf_attribute* attributes; - cgltf_size attributes_count; -} cgltf_morph_target; - -typedef struct cgltf_draco_mesh_compression { - cgltf_buffer_view* buffer_view; - cgltf_attribute* attributes; - cgltf_size attributes_count; -} cgltf_draco_mesh_compression; - -typedef struct cgltf_primitive { - cgltf_primitive_type type; - cgltf_accessor* indices; - cgltf_material* material; - cgltf_attribute* attributes; - cgltf_size attributes_count; - cgltf_morph_target* targets; - cgltf_size targets_count; - cgltf_extras extras; - cgltf_bool has_draco_mesh_compression; - cgltf_draco_mesh_compression draco_mesh_compression; - cgltf_material_mapping* mappings; - cgltf_size mappings_count; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_primitive; - -typedef struct cgltf_mesh { - char* name; - cgltf_primitive* primitives; - cgltf_size primitives_count; - cgltf_float* weights; - cgltf_size weights_count; - char** target_names; - cgltf_size target_names_count; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_mesh; - -typedef struct cgltf_node cgltf_node; - -typedef struct cgltf_skin { - char* name; - cgltf_node** joints; - cgltf_size joints_count; - cgltf_node* skeleton; - cgltf_accessor* inverse_bind_matrices; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_skin; - -typedef struct cgltf_camera_perspective { - cgltf_bool has_aspect_ratio; - cgltf_float aspect_ratio; - cgltf_float yfov; - cgltf_bool has_zfar; - cgltf_float zfar; - cgltf_float znear; - cgltf_extras extras; -} cgltf_camera_perspective; - -typedef struct cgltf_camera_orthographic { - cgltf_float xmag; - cgltf_float ymag; - cgltf_float zfar; - cgltf_float znear; - cgltf_extras extras; -} cgltf_camera_orthographic; - -typedef struct cgltf_camera { - char* name; - cgltf_camera_type type; - union { - cgltf_camera_perspective perspective; - cgltf_camera_orthographic orthographic; - } data; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_camera; - -typedef struct cgltf_light { - char* name; - cgltf_float color[3]; - cgltf_float intensity; - cgltf_light_type type; - cgltf_float range; - cgltf_float spot_inner_cone_angle; - cgltf_float spot_outer_cone_angle; - cgltf_extras extras; -} cgltf_light; - -struct cgltf_node { - char* name; - cgltf_node* parent; - cgltf_node** children; - cgltf_size children_count; - cgltf_skin* skin; - cgltf_mesh* mesh; - cgltf_camera* camera; - cgltf_light* light; - cgltf_float* weights; - cgltf_size weights_count; - cgltf_bool has_translation; - cgltf_bool has_rotation; - cgltf_bool has_scale; - cgltf_bool has_matrix; - cgltf_float translation[3]; - cgltf_float rotation[4]; - cgltf_float scale[3]; - cgltf_float matrix[16]; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -}; - -typedef struct cgltf_scene { - char* name; - cgltf_node** nodes; - cgltf_size nodes_count; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_scene; - -typedef struct cgltf_animation_sampler { - cgltf_accessor* input; - cgltf_accessor* output; - cgltf_interpolation_type interpolation; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_animation_sampler; - -typedef struct cgltf_animation_channel { - cgltf_animation_sampler* sampler; - cgltf_node* target_node; - cgltf_animation_path_type target_path; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_animation_channel; - -typedef struct cgltf_animation { - char* name; - cgltf_animation_sampler* samplers; - cgltf_size samplers_count; - cgltf_animation_channel* channels; - cgltf_size channels_count; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_animation; - -typedef struct cgltf_material_variant -{ - char* name; - cgltf_extras extras; -} cgltf_material_variant; - -typedef struct cgltf_asset { - char* copyright; - char* generator; - char* version; - char* min_version; - cgltf_extras extras; - cgltf_size extensions_count; - cgltf_extension* extensions; -} cgltf_asset; - -typedef struct cgltf_data -{ - cgltf_file_type file_type; - void* file_data; - - cgltf_asset asset; - - cgltf_mesh* meshes; - cgltf_size meshes_count; - - cgltf_material* materials; - cgltf_size materials_count; - - cgltf_accessor* accessors; - cgltf_size accessors_count; - - cgltf_buffer_view* buffer_views; - cgltf_size buffer_views_count; - - cgltf_buffer* buffers; - cgltf_size buffers_count; - - cgltf_image* images; - cgltf_size images_count; - - cgltf_texture* textures; - cgltf_size textures_count; - - cgltf_sampler* samplers; - cgltf_size samplers_count; - - cgltf_skin* skins; - cgltf_size skins_count; - - cgltf_camera* cameras; - cgltf_size cameras_count; - - cgltf_light* lights; - cgltf_size lights_count; - - cgltf_node* nodes; - cgltf_size nodes_count; - - cgltf_scene* scenes; - cgltf_size scenes_count; - - cgltf_scene* scene; - - cgltf_animation* animations; - cgltf_size animations_count; - - cgltf_material_variant* variants; - cgltf_size variants_count; - - cgltf_extras extras; - - cgltf_size data_extensions_count; - cgltf_extension* data_extensions; - - char** extensions_used; - cgltf_size extensions_used_count; - - char** extensions_required; - cgltf_size extensions_required_count; - - const char* json; - cgltf_size json_size; - - const void* bin; - cgltf_size bin_size; - - cgltf_memory_options memory; - cgltf_file_options file; -} cgltf_data; - -cgltf_result cgltf_parse( - const cgltf_options* options, - const void* data, - cgltf_size size, - cgltf_data** out_data); - -cgltf_result cgltf_parse_file( - const cgltf_options* options, - const char* path, - cgltf_data** out_data); - -cgltf_result cgltf_load_buffers( - const cgltf_options* options, - cgltf_data* data, - const char* gltf_path); - -cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data); - -cgltf_size cgltf_decode_string(char* string); -cgltf_size cgltf_decode_uri(char* uri); - -cgltf_result cgltf_validate(cgltf_data* data); - -void cgltf_free(cgltf_data* data); - -void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix); -void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix); - -cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size); -cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size); -cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index); - -cgltf_size cgltf_num_components(cgltf_type type); - -cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count); - -cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size); - -#ifdef __cplusplus -} -#endif - -#endif /* #ifndef CGLTF_H_INCLUDED__ */ - -/* - * - * Stop now, if you are only interested in the API. - * Below, you find the implementation. - * - */ - -#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) -/* This makes MSVC/CLion intellisense work. */ -#define CGLTF_IMPLEMENTATION -#endif - -#ifdef CGLTF_IMPLEMENTATION - -#include /* For uint8_t, uint32_t */ -#include /* For strncpy */ -#include /* For fopen */ -#include /* For UINT_MAX etc */ -#include /* For FLT_MAX */ - -#if !defined(CGLTF_MALLOC) || !defined(CGLTF_FREE) || !defined(CGLTF_ATOI) || !defined(CGLTF_ATOF) || !defined(CGLTF_ATOLL) -#include /* For malloc, free, atoi, atof */ -#endif - -/* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */ -#define JSMN_PARENT_LINKS - -/* JSMN_STRICT is necessary to reject invalid JSON documents */ -#define JSMN_STRICT - -/* - * -- jsmn.h start -- - * Source: https://github.com/zserge/jsmn - * License: MIT - */ -typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1, - JSMN_ARRAY = 2, - JSMN_STRING = 3, - JSMN_PRIMITIVE = 4 -} jsmntype_t; -enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 -}; -typedef struct { - jsmntype_t type; - int start; - int end; - int size; -#ifdef JSMN_PARENT_LINKS - int parent; -#endif -} jsmntok_t; -typedef struct { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g parent object or array */ -} jsmn_parser; -static void jsmn_init(jsmn_parser *parser); -static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens); -/* - * -- jsmn.h end -- - */ - - -static const cgltf_size GlbHeaderSize = 12; -static const cgltf_size GlbChunkHeaderSize = 8; -static const uint32_t GlbVersion = 2; -static const uint32_t GlbMagic = 0x46546C67; -static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; -static const uint32_t GlbMagicBinChunk = 0x004E4942; - -#ifndef CGLTF_MALLOC -#define CGLTF_MALLOC(size) malloc(size) -#endif -#ifndef CGLTF_FREE -#define CGLTF_FREE(ptr) free(ptr) -#endif -#ifndef CGLTF_ATOI -#define CGLTF_ATOI(str) atoi(str) -#endif -#ifndef CGLTF_ATOF -#define CGLTF_ATOF(str) atof(str) -#endif -#ifndef CGLTF_ATOLL -#define CGLTF_ATOLL(str) atoll(str) -#endif -#ifndef CGLTF_VALIDATE_ENABLE_ASSERTS -#define CGLTF_VALIDATE_ENABLE_ASSERTS 0 -#endif - -static void* cgltf_default_alloc(void* user, cgltf_size size) -{ - (void)user; - return CGLTF_MALLOC(size); -} - -static void cgltf_default_free(void* user, void* ptr) -{ - (void)user; - CGLTF_FREE(ptr); -} - -static void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count) -{ - if (SIZE_MAX / element_size < count) - { - return NULL; - } - void* result = options->memory.alloc(options->memory.user_data, element_size * count); - if (!result) - { - return NULL; - } - memset(result, 0, element_size * count); - return result; -} - -static cgltf_result cgltf_default_file_read(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data) -{ - (void)file_options; - void* (*memory_alloc)(void*, cgltf_size) = memory_options->alloc ? memory_options->alloc : &cgltf_default_alloc; - void (*memory_free)(void*, void*) = memory_options->free ? memory_options->free : &cgltf_default_free; - - FILE* file = fopen(path, "rb"); - if (!file) - { - return cgltf_result_file_not_found; - } - - cgltf_size file_size = size ? *size : 0; - - if (file_size == 0) - { - fseek(file, 0, SEEK_END); - -#ifdef _WIN32 - __int64 length = _ftelli64(file); -#else - long length = ftell(file); -#endif - - if (length < 0) - { - fclose(file); - return cgltf_result_io_error; - } - - fseek(file, 0, SEEK_SET); - file_size = (cgltf_size)length; - } - - char* file_data = (char*)memory_alloc(memory_options->user_data, file_size); - if (!file_data) - { - fclose(file); - return cgltf_result_out_of_memory; - } - - cgltf_size read_size = fread(file_data, 1, file_size, file); - - fclose(file); - - if (read_size != file_size) - { - memory_free(memory_options->user_data, file_data); - return cgltf_result_io_error; - } - - if (size) - { - *size = file_size; - } - if (data) - { - *data = file_data; - } - - return cgltf_result_success; -} - -static void cgltf_default_file_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data) -{ - (void)file_options; - void (*memfree)(void*, void*) = memory_options->free ? memory_options->free : &cgltf_default_free; - memfree(memory_options->user_data, data); -} - -static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data); - -cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data) -{ - if (size < GlbHeaderSize) - { - return cgltf_result_data_too_short; - } - - if (options == NULL) - { - return cgltf_result_invalid_options; - } - - cgltf_options fixed_options = *options; - if (fixed_options.memory.alloc == NULL) - { - fixed_options.memory.alloc = &cgltf_default_alloc; - } - if (fixed_options.memory.free == NULL) - { - fixed_options.memory.free = &cgltf_default_free; - } - - uint32_t tmp; - // Magic - memcpy(&tmp, data, 4); - if (tmp != GlbMagic) - { - if (fixed_options.type == cgltf_file_type_invalid) - { - fixed_options.type = cgltf_file_type_gltf; - } - else if (fixed_options.type == cgltf_file_type_glb) - { - return cgltf_result_unknown_format; - } - } - - if (fixed_options.type == cgltf_file_type_gltf) - { - cgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data); - if (json_result != cgltf_result_success) - { - return json_result; - } - - (*out_data)->file_type = cgltf_file_type_gltf; - - return cgltf_result_success; - } - - const uint8_t* ptr = (const uint8_t*)data; - // Version - memcpy(&tmp, ptr + 4, 4); - uint32_t version = tmp; - if (version != GlbVersion) - { - return version < GlbVersion ? cgltf_result_legacy_gltf : cgltf_result_unknown_format; - } - - // Total length - memcpy(&tmp, ptr + 8, 4); - if (tmp > size) - { - return cgltf_result_data_too_short; - } - - const uint8_t* json_chunk = ptr + GlbHeaderSize; - - if (GlbHeaderSize + GlbChunkHeaderSize > size) - { - return cgltf_result_data_too_short; - } - - // JSON chunk: length - uint32_t json_length; - memcpy(&json_length, json_chunk, 4); - if (GlbHeaderSize + GlbChunkHeaderSize + json_length > size) - { - return cgltf_result_data_too_short; - } - - // JSON chunk: magic - memcpy(&tmp, json_chunk + 4, 4); - if (tmp != GlbMagicJsonChunk) - { - return cgltf_result_unknown_format; - } - - json_chunk += GlbChunkHeaderSize; - - const void* bin = 0; - cgltf_size bin_size = 0; - - if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize <= size) - { - // We can read another chunk - const uint8_t* bin_chunk = json_chunk + json_length; - - // Bin chunk: length - uint32_t bin_length; - memcpy(&bin_length, bin_chunk, 4); - if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize + bin_length > size) - { - return cgltf_result_data_too_short; - } - - // Bin chunk: magic - memcpy(&tmp, bin_chunk + 4, 4); - if (tmp != GlbMagicBinChunk) - { - return cgltf_result_unknown_format; - } - - bin_chunk += GlbChunkHeaderSize; - - bin = bin_chunk; - bin_size = bin_length; - } - - cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data); - if (json_result != cgltf_result_success) - { - return json_result; - } - - (*out_data)->file_type = cgltf_file_type_glb; - (*out_data)->bin = bin; - (*out_data)->bin_size = bin_size; - - return cgltf_result_success; -} - -cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data) -{ - if (options == NULL) - { - return cgltf_result_invalid_options; - } - - cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; - void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data) = options->file.release ? options->file.release : cgltf_default_file_release; - - void* file_data = NULL; - cgltf_size file_size = 0; - cgltf_result result = file_read(&options->memory, &options->file, path, &file_size, &file_data); - if (result != cgltf_result_success) - { - return result; - } - - result = cgltf_parse(options, file_data, file_size, out_data); - - if (result != cgltf_result_success) - { - file_release(&options->memory, &options->file, file_data); - return result; - } - - (*out_data)->file_data = file_data; - - return cgltf_result_success; -} - -static void cgltf_combine_paths(char* path, const char* base, const char* uri) -{ - const char* s0 = strrchr(base, '/'); - const char* s1 = strrchr(base, '\\'); - const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1; - - if (slash) - { - size_t prefix = slash - base + 1; - - strncpy(path, base, prefix); - strcpy(path + prefix, uri); - } - else - { - strcpy(path, uri); - } -} - -static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data) -{ - void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc ? options->memory.alloc : &cgltf_default_alloc; - void (*memory_free)(void*, void*) = options->memory.free ? options->memory.free : &cgltf_default_free; - cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; - - char* path = (char*)memory_alloc(options->memory.user_data, strlen(uri) + strlen(gltf_path) + 1); - if (!path) - { - return cgltf_result_out_of_memory; - } - - cgltf_combine_paths(path, gltf_path, uri); - - // after combining, the tail of the resulting path is a uri; decode_uri converts it into path - cgltf_decode_uri(path + strlen(path) - strlen(uri)); - - void* file_data = NULL; - cgltf_result result = file_read(&options->memory, &options->file, path, &size, &file_data); - - memory_free(options->memory.user_data, path); - - *out_data = (result == cgltf_result_success) ? file_data : NULL; - - return result; -} - -cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data) -{ - void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc ? options->memory.alloc : &cgltf_default_alloc; - void (*memory_free)(void*, void*) = options->memory.free ? options->memory.free : &cgltf_default_free; - - unsigned char* data = (unsigned char*)memory_alloc(options->memory.user_data, size); - if (!data) - { - return cgltf_result_out_of_memory; - } - - unsigned int buffer = 0; - unsigned int buffer_bits = 0; - - for (cgltf_size i = 0; i < size; ++i) - { - while (buffer_bits < 8) - { - char ch = *base64++; - - int index = - (unsigned)(ch - 'A') < 26 ? (ch - 'A') : - (unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 : - (unsigned)(ch - '0') < 10 ? (ch - '0') + 52 : - ch == '+' ? 62 : - ch == '/' ? 63 : - -1; - - if (index < 0) - { - memory_free(options->memory.user_data, data); - return cgltf_result_io_error; - } - - buffer = (buffer << 6) | index; - buffer_bits += 6; - } - - data[i] = (unsigned char)(buffer >> (buffer_bits - 8)); - buffer_bits -= 8; - } - - *out_data = data; - - return cgltf_result_success; -} - -static int cgltf_unhex(char ch) -{ - return - (unsigned)(ch - '0') < 10 ? (ch - '0') : - (unsigned)(ch - 'A') < 6 ? (ch - 'A') + 10 : - (unsigned)(ch - 'a') < 6 ? (ch - 'a') + 10 : - -1; -} - -cgltf_size cgltf_decode_string(char* string) -{ - char* read = string + strcspn(string, "\\"); - if (*read == 0) - { - return read - string; - } - char* write = string; - char* last = string; - - for (;;) - { - // Copy characters since last escaped sequence - cgltf_size written = read - last; - memmove(write, last, written); - write += written; - - if (*read++ == 0) - { - break; - } - - // jsmn already checked that all escape sequences are valid - switch (*read++) - { - case '\"': *write++ = '\"'; break; - case '/': *write++ = '/'; break; - case '\\': *write++ = '\\'; break; - case 'b': *write++ = '\b'; break; - case 'f': *write++ = '\f'; break; - case 'r': *write++ = '\r'; break; - case 'n': *write++ = '\n'; break; - case 't': *write++ = '\t'; break; - case 'u': - { - // UCS-2 codepoint \uXXXX to UTF-8 - int character = 0; - for (cgltf_size i = 0; i < 4; ++i) - { - character = (character << 4) + cgltf_unhex(*read++); - } - - if (character <= 0x7F) - { - *write++ = character & 0xFF; - } - else if (character <= 0x7FF) - { - *write++ = 0xC0 | ((character >> 6) & 0xFF); - *write++ = 0x80 | (character & 0x3F); - } - else - { - *write++ = 0xE0 | ((character >> 12) & 0xFF); - *write++ = 0x80 | ((character >> 6) & 0x3F); - *write++ = 0x80 | (character & 0x3F); - } - break; - } - default: - break; - } - - last = read; - read += strcspn(read, "\\"); - } - - *write = 0; - return write - string; -} - -cgltf_size cgltf_decode_uri(char* uri) -{ - char* write = uri; - char* i = uri; - - while (*i) - { - if (*i == '%') - { - int ch1 = cgltf_unhex(i[1]); - - if (ch1 >= 0) - { - int ch2 = cgltf_unhex(i[2]); - - if (ch2 >= 0) - { - *write++ = (char)(ch1 * 16 + ch2); - i += 3; - continue; - } - } - } - - *write++ = *i++; - } - - *write = 0; - return write - uri; -} - -cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path) -{ - if (options == NULL) - { - return cgltf_result_invalid_options; - } - - if (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin) - { - if (data->bin_size < data->buffers[0].size) - { - return cgltf_result_data_too_short; - } - - data->buffers[0].data = (void*)data->bin; - data->buffers[0].data_free_method = cgltf_data_free_method_none; - } - - for (cgltf_size i = 0; i < data->buffers_count; ++i) - { - if (data->buffers[i].data) - { - continue; - } - - const char* uri = data->buffers[i].uri; - - if (uri == NULL) - { - continue; - } - - if (strncmp(uri, "data:", 5) == 0) - { - const char* comma = strchr(uri, ','); - - if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) - { - cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data); - data->buffers[i].data_free_method = cgltf_data_free_method_memory_free; - - if (res != cgltf_result_success) - { - return res; - } - } - else - { - return cgltf_result_unknown_format; - } - } - else if (strstr(uri, "://") == NULL && gltf_path) - { - cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); - data->buffers[i].data_free_method = cgltf_data_free_method_file_release; - - if (res != cgltf_result_success) - { - return res; - } - } - else - { - return cgltf_result_unknown_format; - } - } - - return cgltf_result_success; -} - -static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type); - -static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count) -{ - char* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset; - cgltf_size bound = 0; - - switch (component_type) - { - case cgltf_component_type_r_8u: - for (size_t i = 0; i < count; ++i) - { - cgltf_size v = ((unsigned char*)data)[i]; - bound = bound > v ? bound : v; - } - break; - - case cgltf_component_type_r_16u: - for (size_t i = 0; i < count; ++i) - { - cgltf_size v = ((unsigned short*)data)[i]; - bound = bound > v ? bound : v; - } - break; - - case cgltf_component_type_r_32u: - for (size_t i = 0; i < count; ++i) - { - cgltf_size v = ((unsigned int*)data)[i]; - bound = bound > v ? bound : v; - } - break; - - default: - ; - } - - return bound; -} - -#if CGLTF_VALIDATE_ENABLE_ASSERTS -#define CGLTF_ASSERT_IF(cond, result) assert(!(cond)); if (cond) return result; -#else -#define CGLTF_ASSERT_IF(cond, result) if (cond) return result; -#endif - -cgltf_result cgltf_validate(cgltf_data* data) -{ - for (cgltf_size i = 0; i < data->accessors_count; ++i) - { - cgltf_accessor* accessor = &data->accessors[i]; - - cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); - - if (accessor->buffer_view) - { - cgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size; - - CGLTF_ASSERT_IF(accessor->buffer_view->size < req_size, cgltf_result_data_too_short); - } - - if (accessor->is_sparse) - { - cgltf_accessor_sparse* sparse = &accessor->sparse; - - cgltf_size indices_component_size = cgltf_calc_size(cgltf_type_scalar, sparse->indices_component_type); - cgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count; - cgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count; - - CGLTF_ASSERT_IF(sparse->indices_buffer_view->size < indices_req_size || - sparse->values_buffer_view->size < values_req_size, cgltf_result_data_too_short); - - CGLTF_ASSERT_IF(sparse->indices_component_type != cgltf_component_type_r_8u && - sparse->indices_component_type != cgltf_component_type_r_16u && - sparse->indices_component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); - - if (sparse->indices_buffer_view->buffer->data) - { - cgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count); - - CGLTF_ASSERT_IF(index_bound >= accessor->count, cgltf_result_data_too_short); - } - } - } - - for (cgltf_size i = 0; i < data->buffer_views_count; ++i) - { - cgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size; - - CGLTF_ASSERT_IF(data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size, cgltf_result_data_too_short); - - if (data->buffer_views[i].has_meshopt_compression) - { - cgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression; - - CGLTF_ASSERT_IF(mc->buffer == NULL || mc->buffer->size < mc->offset + mc->size, cgltf_result_data_too_short); - - CGLTF_ASSERT_IF(data->buffer_views[i].stride && mc->stride != data->buffer_views[i].stride, cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF(data->buffer_views[i].size != mc->stride * mc->count, cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_invalid, cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_attributes && !(mc->stride % 4 == 0 && mc->stride <= 256), cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_triangles && mc->count % 3 != 0, cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->stride != 2 && mc->stride != 4, cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->filter != cgltf_meshopt_compression_filter_none, cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_octahedral && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf); - - CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_quaternion && mc->stride != 8, cgltf_result_invalid_gltf); - } - } - - for (cgltf_size i = 0; i < data->meshes_count; ++i) - { - if (data->meshes[i].weights) - { - CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count, cgltf_result_invalid_gltf); - } - - if (data->meshes[i].target_names) - { - CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count, cgltf_result_invalid_gltf); - } - - for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) - { - CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count, cgltf_result_invalid_gltf); - - if (data->meshes[i].primitives[j].attributes_count) - { - cgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].data; - - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) - { - CGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes[k].data->count != first->count, cgltf_result_invalid_gltf); - } - - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) - { - for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) - { - CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count, cgltf_result_invalid_gltf); - } - } - - cgltf_accessor* indices = data->meshes[i].primitives[j].indices; - - CGLTF_ASSERT_IF(indices && - indices->component_type != cgltf_component_type_r_8u && - indices->component_type != cgltf_component_type_r_16u && - indices->component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); - - if (indices && indices->buffer_view && indices->buffer_view->buffer->data) - { - cgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count); - - CGLTF_ASSERT_IF(index_bound >= first->count, cgltf_result_data_too_short); - } - - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) - { - CGLTF_ASSERT_IF(data->meshes[i].primitives[j].mappings[k].variant >= data->variants_count, cgltf_result_invalid_gltf); - } - } - } - } - - for (cgltf_size i = 0; i < data->nodes_count; ++i) - { - if (data->nodes[i].weights && data->nodes[i].mesh) - { - CGLTF_ASSERT_IF (data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count, cgltf_result_invalid_gltf); - } - } - - for (cgltf_size i = 0; i < data->nodes_count; ++i) - { - cgltf_node* p1 = data->nodes[i].parent; - cgltf_node* p2 = p1 ? p1->parent : NULL; - - while (p1 && p2) - { - CGLTF_ASSERT_IF(p1 == p2, cgltf_result_invalid_gltf); - - p1 = p1->parent; - p2 = p2->parent ? p2->parent->parent : NULL; - } - } - - for (cgltf_size i = 0; i < data->scenes_count; ++i) - { - for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) - { - CGLTF_ASSERT_IF(data->scenes[i].nodes[j]->parent, cgltf_result_invalid_gltf); - } - } - - for (cgltf_size i = 0; i < data->animations_count; ++i) - { - for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) - { - cgltf_animation_channel* channel = &data->animations[i].channels[j]; - - if (!channel->target_node) - { - continue; - } - - cgltf_size components = 1; - - if (channel->target_path == cgltf_animation_path_type_weights) - { - CGLTF_ASSERT_IF(!channel->target_node->mesh || !channel->target_node->mesh->primitives_count, cgltf_result_invalid_gltf); - - components = channel->target_node->mesh->primitives[0].targets_count; - } - - cgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1; - - CGLTF_ASSERT_IF(channel->sampler->input->count * components * values != channel->sampler->output->count, cgltf_result_data_too_short); - } - } - - return cgltf_result_success; -} - -cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size) -{ - cgltf_size json_size = extras->end_offset - extras->start_offset; - - if (!dest) - { - if (dest_size) - { - *dest_size = json_size + 1; - return cgltf_result_success; - } - return cgltf_result_invalid_options; - } - - if (*dest_size + 1 < json_size) - { - strncpy(dest, data->json + extras->start_offset, *dest_size - 1); - dest[*dest_size - 1] = 0; - } - else - { - strncpy(dest, data->json + extras->start_offset, json_size); - dest[json_size] = 0; - } - - return cgltf_result_success; -} - -void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count) -{ - for (cgltf_size i = 0; i < extensions_count; ++i) - { - data->memory.free(data->memory.user_data, extensions[i].name); - data->memory.free(data->memory.user_data, extensions[i].data); - } - data->memory.free(data->memory.user_data, extensions); -} - -void cgltf_free(cgltf_data* data) -{ - if (!data) - { - return; - } - - void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data) = data->file.release ? data->file.release : cgltf_default_file_release; - - data->memory.free(data->memory.user_data, data->asset.copyright); - data->memory.free(data->memory.user_data, data->asset.generator); - data->memory.free(data->memory.user_data, data->asset.version); - data->memory.free(data->memory.user_data, data->asset.min_version); - - cgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count); - - for (cgltf_size i = 0; i < data->accessors_count; ++i) - { - data->memory.free(data->memory.user_data, data->accessors[i].name); - - if(data->accessors[i].is_sparse) - { - cgltf_free_extensions(data, data->accessors[i].sparse.extensions, data->accessors[i].sparse.extensions_count); - cgltf_free_extensions(data, data->accessors[i].sparse.indices_extensions, data->accessors[i].sparse.indices_extensions_count); - cgltf_free_extensions(data, data->accessors[i].sparse.values_extensions, data->accessors[i].sparse.values_extensions_count); - } - cgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count); - } - data->memory.free(data->memory.user_data, data->accessors); - - for (cgltf_size i = 0; i < data->buffer_views_count; ++i) - { - data->memory.free(data->memory.user_data, data->buffer_views[i].name); - data->memory.free(data->memory.user_data, data->buffer_views[i].data); - - cgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count); - } - data->memory.free(data->memory.user_data, data->buffer_views); - - for (cgltf_size i = 0; i < data->buffers_count; ++i) - { - data->memory.free(data->memory.user_data, data->buffers[i].name); - - if (data->buffers[i].data_free_method == cgltf_data_free_method_file_release) - { - file_release(&data->memory, &data->file, data->buffers[i].data); - } - else if (data->buffers[i].data_free_method == cgltf_data_free_method_memory_free) - { - data->memory.free(data->memory.user_data, data->buffers[i].data); - } - - data->memory.free(data->memory.user_data, data->buffers[i].uri); - - cgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->buffers); - - for (cgltf_size i = 0; i < data->meshes_count; ++i) - { - data->memory.free(data->memory.user_data, data->meshes[i].name); - - for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) - { - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) - { - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].attributes[k].name); - } - - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].attributes); - - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) - { - for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) - { - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name); - } - - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes); - } - - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].targets); - - if (data->meshes[i].primitives[j].has_draco_mesh_compression) - { - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++k) - { - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes[k].name); - } - - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes); - } - - data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].mappings); - - cgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count); - } - - data->memory.free(data->memory.user_data, data->meshes[i].primitives); - data->memory.free(data->memory.user_data, data->meshes[i].weights); - - for (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j) - { - data->memory.free(data->memory.user_data, data->meshes[i].target_names[j]); - } - - cgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count); - - data->memory.free(data->memory.user_data, data->meshes[i].target_names); - } - - data->memory.free(data->memory.user_data, data->meshes); - - for (cgltf_size i = 0; i < data->materials_count; ++i) - { - data->memory.free(data->memory.user_data, data->materials[i].name); - - if(data->materials[i].has_pbr_metallic_roughness) - { - cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions_count); - } - if(data->materials[i].has_pbr_specular_glossiness) - { - cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions_count); - } - if(data->materials[i].has_clearcoat) - { - cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_texture.extensions, data->materials[i].clearcoat.clearcoat_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_normal_texture.extensions, data->materials[i].clearcoat.clearcoat_normal_texture.extensions_count); - } - if(data->materials[i].has_specular) - { - cgltf_free_extensions(data, data->materials[i].specular.specular_texture.extensions, data->materials[i].specular.specular_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].specular.specular_color_texture.extensions, data->materials[i].specular.specular_color_texture.extensions_count); - } - if(data->materials[i].has_transmission) - { - cgltf_free_extensions(data, data->materials[i].transmission.transmission_texture.extensions, data->materials[i].transmission.transmission_texture.extensions_count); - } - if (data->materials[i].has_volume) - { - cgltf_free_extensions(data, data->materials[i].volume.thickness_texture.extensions, data->materials[i].volume.thickness_texture.extensions_count); - } - if(data->materials[i].has_sheen) - { - cgltf_free_extensions(data, data->materials[i].sheen.sheen_color_texture.extensions, data->materials[i].sheen.sheen_color_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].sheen.sheen_roughness_texture.extensions, data->materials[i].sheen.sheen_roughness_texture.extensions_count); - } - if(data->materials[i].has_iridescence) - { - cgltf_free_extensions(data, data->materials[i].iridescence.iridescence_texture.extensions, data->materials[i].iridescence.iridescence_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].iridescence.iridescence_thickness_texture.extensions, data->materials[i].iridescence.iridescence_thickness_texture.extensions_count); - } - - cgltf_free_extensions(data, data->materials[i].normal_texture.extensions, data->materials[i].normal_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].occlusion_texture.extensions, data->materials[i].occlusion_texture.extensions_count); - cgltf_free_extensions(data, data->materials[i].emissive_texture.extensions, data->materials[i].emissive_texture.extensions_count); - - cgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->materials); - - for (cgltf_size i = 0; i < data->images_count; ++i) - { - data->memory.free(data->memory.user_data, data->images[i].name); - data->memory.free(data->memory.user_data, data->images[i].uri); - data->memory.free(data->memory.user_data, data->images[i].mime_type); - - cgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->images); - - for (cgltf_size i = 0; i < data->textures_count; ++i) - { - data->memory.free(data->memory.user_data, data->textures[i].name); - cgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->textures); - - for (cgltf_size i = 0; i < data->samplers_count; ++i) - { - data->memory.free(data->memory.user_data, data->samplers[i].name); - cgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->samplers); - - for (cgltf_size i = 0; i < data->skins_count; ++i) - { - data->memory.free(data->memory.user_data, data->skins[i].name); - data->memory.free(data->memory.user_data, data->skins[i].joints); - - cgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->skins); - - for (cgltf_size i = 0; i < data->cameras_count; ++i) - { - data->memory.free(data->memory.user_data, data->cameras[i].name); - cgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->cameras); - - for (cgltf_size i = 0; i < data->lights_count; ++i) - { - data->memory.free(data->memory.user_data, data->lights[i].name); - } - - data->memory.free(data->memory.user_data, data->lights); - - for (cgltf_size i = 0; i < data->nodes_count; ++i) - { - data->memory.free(data->memory.user_data, data->nodes[i].name); - data->memory.free(data->memory.user_data, data->nodes[i].children); - data->memory.free(data->memory.user_data, data->nodes[i].weights); - cgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->nodes); - - for (cgltf_size i = 0; i < data->scenes_count; ++i) - { - data->memory.free(data->memory.user_data, data->scenes[i].name); - data->memory.free(data->memory.user_data, data->scenes[i].nodes); - - cgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->scenes); - - for (cgltf_size i = 0; i < data->animations_count; ++i) - { - data->memory.free(data->memory.user_data, data->animations[i].name); - for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) - { - cgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count); - } - data->memory.free(data->memory.user_data, data->animations[i].samplers); - - for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) - { - cgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count); - } - data->memory.free(data->memory.user_data, data->animations[i].channels); - - cgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count); - } - - data->memory.free(data->memory.user_data, data->animations); - - for (cgltf_size i = 0; i < data->variants_count; ++i) - { - data->memory.free(data->memory.user_data, data->variants[i].name); - } - - data->memory.free(data->memory.user_data, data->variants); - - cgltf_free_extensions(data, data->data_extensions, data->data_extensions_count); - - for (cgltf_size i = 0; i < data->extensions_used_count; ++i) - { - data->memory.free(data->memory.user_data, data->extensions_used[i]); - } - - data->memory.free(data->memory.user_data, data->extensions_used); - - for (cgltf_size i = 0; i < data->extensions_required_count; ++i) - { - data->memory.free(data->memory.user_data, data->extensions_required[i]); - } - - data->memory.free(data->memory.user_data, data->extensions_required); - - file_release(&data->memory, &data->file, data->file_data); - - data->memory.free(data->memory.user_data, data); -} - -void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix) -{ - cgltf_float* lm = out_matrix; - - if (node->has_matrix) - { - memcpy(lm, node->matrix, sizeof(float) * 16); - } - else - { - float tx = node->translation[0]; - float ty = node->translation[1]; - float tz = node->translation[2]; - - float qx = node->rotation[0]; - float qy = node->rotation[1]; - float qz = node->rotation[2]; - float qw = node->rotation[3]; - - float sx = node->scale[0]; - float sy = node->scale[1]; - float sz = node->scale[2]; - - lm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx; - lm[1] = (2 * qx*qy + 2 * qz*qw) * sx; - lm[2] = (2 * qx*qz - 2 * qy*qw) * sx; - lm[3] = 0.f; - - lm[4] = (2 * qx*qy - 2 * qz*qw) * sy; - lm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy; - lm[6] = (2 * qy*qz + 2 * qx*qw) * sy; - lm[7] = 0.f; - - lm[8] = (2 * qx*qz + 2 * qy*qw) * sz; - lm[9] = (2 * qy*qz - 2 * qx*qw) * sz; - lm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz; - lm[11] = 0.f; - - lm[12] = tx; - lm[13] = ty; - lm[14] = tz; - lm[15] = 1.f; - } -} - -void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix) -{ - cgltf_float* lm = out_matrix; - cgltf_node_transform_local(node, lm); - - const cgltf_node* parent = node->parent; - - while (parent) - { - float pm[16]; - cgltf_node_transform_local(parent, pm); - - for (int i = 0; i < 4; ++i) - { - float l0 = lm[i * 4 + 0]; - float l1 = lm[i * 4 + 1]; - float l2 = lm[i * 4 + 2]; - - float r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8]; - float r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9]; - float r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10]; - - lm[i * 4 + 0] = r0; - lm[i * 4 + 1] = r1; - lm[i * 4 + 2] = r2; - } - - lm[12] += pm[12]; - lm[13] += pm[13]; - lm[14] += pm[14]; - - parent = parent->parent; - } -} - -static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type) -{ - switch (component_type) - { - case cgltf_component_type_r_16: - return *((const int16_t*) in); - case cgltf_component_type_r_16u: - return *((const uint16_t*) in); - case cgltf_component_type_r_32u: - return *((const uint32_t*) in); - case cgltf_component_type_r_32f: - return (cgltf_size)*((const float*) in); - case cgltf_component_type_r_8: - return *((const int8_t*) in); - case cgltf_component_type_r_8u: - return *((const uint8_t*) in); - default: - return 0; - } -} - -static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized) -{ - if (component_type == cgltf_component_type_r_32f) - { - return *((const float*) in); - } - - if (normalized) - { - switch (component_type) - { - // note: glTF spec doesn't currently define normalized conversions for 32-bit integers - case cgltf_component_type_r_16: - return *((const int16_t*) in) / (cgltf_float)32767; - case cgltf_component_type_r_16u: - return *((const uint16_t*) in) / (cgltf_float)65535; - case cgltf_component_type_r_8: - return *((const int8_t*) in) / (cgltf_float)127; - case cgltf_component_type_r_8u: - return *((const uint8_t*) in) / (cgltf_float)255; - default: - return 0; - } - } - - return (cgltf_float)cgltf_component_read_index(in, component_type); -} - -static cgltf_size cgltf_component_size(cgltf_component_type component_type); - -static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, cgltf_size element_size) -{ - cgltf_size num_components = cgltf_num_components(type); - - if (element_size < num_components) { - return 0; - } - - // There are three special cases for component extraction, see #data-alignment in the 2.0 spec. - - cgltf_size component_size = cgltf_component_size(component_type); - - if (type == cgltf_type_mat2 && component_size == 1) - { - out[0] = cgltf_component_read_float(element, component_type, normalized); - out[1] = cgltf_component_read_float(element + 1, component_type, normalized); - out[2] = cgltf_component_read_float(element + 4, component_type, normalized); - out[3] = cgltf_component_read_float(element + 5, component_type, normalized); - return 1; - } - - if (type == cgltf_type_mat3 && component_size == 1) - { - out[0] = cgltf_component_read_float(element, component_type, normalized); - out[1] = cgltf_component_read_float(element + 1, component_type, normalized); - out[2] = cgltf_component_read_float(element + 2, component_type, normalized); - out[3] = cgltf_component_read_float(element + 4, component_type, normalized); - out[4] = cgltf_component_read_float(element + 5, component_type, normalized); - out[5] = cgltf_component_read_float(element + 6, component_type, normalized); - out[6] = cgltf_component_read_float(element + 8, component_type, normalized); - out[7] = cgltf_component_read_float(element + 9, component_type, normalized); - out[8] = cgltf_component_read_float(element + 10, component_type, normalized); - return 1; - } - - if (type == cgltf_type_mat3 && component_size == 2) - { - out[0] = cgltf_component_read_float(element, component_type, normalized); - out[1] = cgltf_component_read_float(element + 2, component_type, normalized); - out[2] = cgltf_component_read_float(element + 4, component_type, normalized); - out[3] = cgltf_component_read_float(element + 8, component_type, normalized); - out[4] = cgltf_component_read_float(element + 10, component_type, normalized); - out[5] = cgltf_component_read_float(element + 12, component_type, normalized); - out[6] = cgltf_component_read_float(element + 16, component_type, normalized); - out[7] = cgltf_component_read_float(element + 18, component_type, normalized); - out[8] = cgltf_component_read_float(element + 20, component_type, normalized); - return 1; - } - - for (cgltf_size i = 0; i < num_components; ++i) - { - out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized); - } - return 1; -} - -const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view) -{ - if (view->data) - return (const uint8_t*)view->data; - - if (!view->buffer->data) - return NULL; - - const uint8_t* result = (const uint8_t*)view->buffer->data; - result += view->offset; - return result; -} - -cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size) -{ - if (accessor->is_sparse) - { - return 0; - } - if (accessor->buffer_view == NULL) - { - memset(out, 0, element_size * sizeof(cgltf_float)); - return 1; - } - const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); - if (element == NULL) - { - return 0; - } - element += accessor->offset + accessor->stride * index; - return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); -} - -cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count) -{ - cgltf_size floats_per_element = cgltf_num_components(accessor->type); - cgltf_size available_floats = accessor->count * floats_per_element; - if (out == NULL) - { - return available_floats; - } - - float_count = available_floats < float_count ? available_floats : float_count; - cgltf_size element_count = float_count / floats_per_element; - - // First pass: convert each element in the base accessor. - cgltf_float* dest = out; - cgltf_accessor dense = *accessor; - dense.is_sparse = 0; - for (cgltf_size index = 0; index < element_count; index++, dest += floats_per_element) - { - if (!cgltf_accessor_read_float(&dense, index, dest, floats_per_element)) - { - return 0; - } - } - - // Second pass: write out each element in the sparse accessor. - if (accessor->is_sparse) - { - const cgltf_accessor_sparse* sparse = &dense.sparse; - - const uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view); - const uint8_t* reader_head = cgltf_buffer_view_data(sparse->values_buffer_view); - - if (index_data == NULL || reader_head == NULL) - { - return 0; - } - - index_data += sparse->indices_byte_offset; - reader_head += sparse->values_byte_offset; - - cgltf_size index_stride = cgltf_component_size(sparse->indices_component_type); - for (cgltf_size reader_index = 0; reader_index < sparse->count; reader_index++, index_data += index_stride) - { - size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type); - float* writer_head = out + writer_index * floats_per_element; - - if (!cgltf_element_read_float(reader_head, dense.type, dense.component_type, dense.normalized, writer_head, floats_per_element)) - { - return 0; - } - - reader_head += dense.stride; - } - } - - return element_count * floats_per_element; -} - -static cgltf_uint cgltf_component_read_uint(const void* in, cgltf_component_type component_type) -{ - switch (component_type) - { - case cgltf_component_type_r_8: - return *((const int8_t*) in); - - case cgltf_component_type_r_8u: - return *((const uint8_t*) in); - - case cgltf_component_type_r_16: - return *((const int16_t*) in); - - case cgltf_component_type_r_16u: - return *((const uint16_t*) in); - - case cgltf_component_type_r_32u: - return *((const uint32_t*) in); - - default: - return 0; - } -} - -static cgltf_bool cgltf_element_read_uint(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_uint* out, cgltf_size element_size) -{ - cgltf_size num_components = cgltf_num_components(type); - - if (element_size < num_components) - { - return 0; - } - - // Reading integer matrices is not a valid use case - if (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4) - { - return 0; - } - - cgltf_size component_size = cgltf_component_size(component_type); - - for (cgltf_size i = 0; i < num_components; ++i) - { - out[i] = cgltf_component_read_uint(element + component_size * i, component_type); - } - return 1; -} - -cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size) -{ - if (accessor->is_sparse) - { - return 0; - } - if (accessor->buffer_view == NULL) - { - memset(out, 0, element_size * sizeof( cgltf_uint )); - return 1; - } - const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); - if (element == NULL) - { - return 0; - } - element += accessor->offset + accessor->stride * index; - return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size); -} - -cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index) -{ - if (accessor->is_sparse) - { - return 0; // This is an error case, but we can't communicate the error with existing interface. - } - if (accessor->buffer_view == NULL) - { - return 0; - } - const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); - if (element == NULL) - { - return 0; // This is an error case, but we can't communicate the error with existing interface. - } - element += accessor->offset + accessor->stride * index; - return cgltf_component_read_index(element, accessor->component_type); -} - -#define CGLTF_ERROR_JSON -1 -#define CGLTF_ERROR_NOMEM -2 -#define CGLTF_ERROR_LEGACY -3 - -#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; } -#define CGLTF_CHECK_TOKTYPE_RETTYPE(tok_, type_, ret_) if ((tok_).type != (type_)) { return (ret_)CGLTF_ERROR_JSON; } -#define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */ - -#define CGLTF_PTRINDEX(type, idx) (type*)((cgltf_size)idx + 1) -#define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; } -#define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; - -static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) -{ - CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); - size_t const str_len = strlen(str); - size_t const name_length = tok->end - tok->start; - return (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128; -} - -static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) -{ - CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); - char tmp[128]; - int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); - strncpy(tmp, (const char*)json_chunk + tok->start, size); - tmp[size] = 0; - return CGLTF_ATOI(tmp); -} - -static cgltf_size cgltf_json_to_size(jsmntok_t const* tok, const uint8_t* json_chunk) -{ - CGLTF_CHECK_TOKTYPE_RETTYPE(*tok, JSMN_PRIMITIVE, cgltf_size); - char tmp[128]; - int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); - strncpy(tmp, (const char*)json_chunk + tok->start, size); - tmp[size] = 0; - return (cgltf_size)CGLTF_ATOLL(tmp); -} - -static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk) -{ - CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); - char tmp[128]; - int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); - strncpy(tmp, (const char*)json_chunk + tok->start, size); - tmp[size] = 0; - return (cgltf_float)CGLTF_ATOF(tmp); -} - -static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) -{ - int size = tok->end - tok->start; - return size == 4 && memcmp(json_chunk + tok->start, "true", 4) == 0; -} - -static int cgltf_skip_json(jsmntok_t const* tokens, int i) -{ - int end = i + 1; - - while (i < end) - { - switch (tokens[i].type) - { - case JSMN_OBJECT: - end += tokens[i].size * 2; - break; - - case JSMN_ARRAY: - end += tokens[i].size; - break; - - case JSMN_PRIMITIVE: - case JSMN_STRING: - break; - - default: - return -1; - } - - i++; - } - - return i; -} - -static void cgltf_fill_float_array(float* out_array, int size, float value) -{ - for (int j = 0; j < size; ++j) - { - out_array[j] = value; - } -} - -static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - if (tokens[i].size != size) - { - return CGLTF_ERROR_JSON; - } - ++i; - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); - out_array[j] = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - return i; -} - -static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); - if (*out_string) - { - return CGLTF_ERROR_JSON; - } - int size = tokens[i].end - tokens[i].start; - char* result = (char*)options->memory.alloc(options->memory.user_data, size + 1); - if (!result) - { - return CGLTF_ERROR_NOMEM; - } - strncpy(result, (const char*)json_chunk + tokens[i].start, size); - result[size] = 0; - *out_string = result; - return i + 1; -} - -static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size) -{ - (void)json_chunk; - if (tokens[i].type != JSMN_ARRAY) - { - return tokens[i].type == JSMN_OBJECT ? CGLTF_ERROR_LEGACY : CGLTF_ERROR_JSON; - } - if (*out_array) - { - return CGLTF_ERROR_JSON; - } - int size = tokens[i].size; - void* result = cgltf_calloc(options, element_size, size); - if (!result) - { - return CGLTF_ERROR_NOMEM; - } - *out_array = result; - *out_size = size; - return i + 1; -} - -static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char*** out_array, cgltf_size* out_size) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < *out_size; ++j) - { - i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array)); - if (i < 0) - { - return i; - } - } - return i; -} - -static void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index) -{ - const char* us = strchr(name, '_'); - size_t len = us ? (size_t)(us - name) : strlen(name); - - if (len == 8 && strncmp(name, "POSITION", 8) == 0) - { - *out_type = cgltf_attribute_type_position; - } - else if (len == 6 && strncmp(name, "NORMAL", 6) == 0) - { - *out_type = cgltf_attribute_type_normal; - } - else if (len == 7 && strncmp(name, "TANGENT", 7) == 0) - { - *out_type = cgltf_attribute_type_tangent; - } - else if (len == 8 && strncmp(name, "TEXCOORD", 8) == 0) - { - *out_type = cgltf_attribute_type_texcoord; - } - else if (len == 5 && strncmp(name, "COLOR", 5) == 0) - { - *out_type = cgltf_attribute_type_color; - } - else if (len == 6 && strncmp(name, "JOINTS", 6) == 0) - { - *out_type = cgltf_attribute_type_joints; - } - else if (len == 7 && strncmp(name, "WEIGHTS", 7) == 0) - { - *out_type = cgltf_attribute_type_weights; - } - else - { - *out_type = cgltf_attribute_type_invalid; - } - - if (us && *out_type != cgltf_attribute_type_invalid) - { - *out_index = CGLTF_ATOI(us + 1); - } -} - -static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_attribute** out_attributes, cgltf_size* out_attributes_count) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - if (*out_attributes) - { - return CGLTF_ERROR_JSON; - } - - *out_attributes_count = tokens[i].size; - *out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count); - ++i; - - if (!*out_attributes) - { - return CGLTF_ERROR_NOMEM; - } - - for (cgltf_size j = 0; j < *out_attributes_count; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - i = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name); - if (i < 0) - { - return CGLTF_ERROR_JSON; - } - - cgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index); - - (*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - - return i; -} - -static int cgltf_parse_json_extras(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) -{ - (void)json_chunk; - out_extras->start_offset = tokens[i].start; - out_extras->end_offset = tokens[i].end; - i = cgltf_skip_json(tokens, i); - return i; -} - -static int cgltf_parse_json_unprocessed_extension(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extension* out_extension) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); - CGLTF_CHECK_TOKTYPE(tokens[i+1], JSMN_OBJECT); - if (out_extension->name) - { - return CGLTF_ERROR_JSON; - } - - cgltf_size name_length = tokens[i].end - tokens[i].start; - out_extension->name = (char*)options->memory.alloc(options->memory.user_data, name_length + 1); - if (!out_extension->name) - { - return CGLTF_ERROR_NOMEM; - } - strncpy(out_extension->name, (const char*)json_chunk + tokens[i].start, name_length); - out_extension->name[name_length] = 0; - i++; - - size_t start = tokens[i].start; - size_t size = tokens[i].end - start; - out_extension->data = (char*)options->memory.alloc(options->memory.user_data, size + 1); - if (!out_extension->data) - { - return CGLTF_ERROR_NOMEM; - } - strncpy(out_extension->data, (const char*)json_chunk + start, size); - out_extension->data[size] = '\0'; - - i = cgltf_skip_json(tokens, i); - - return i; -} - -static int cgltf_parse_json_unprocessed_extensions(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_size* out_extensions_count, cgltf_extension** out_extensions) -{ - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if(*out_extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - *out_extensions_count = 0; - *out_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - - if (!*out_extensions) - { - return CGLTF_ERROR_NOMEM; - } - - ++i; - - for (int j = 0; j < extensions_size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - cgltf_size extension_index = (*out_extensions_count)++; - cgltf_extension* extension = &((*out_extensions)[extension_index]); - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, extension); - - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_draco_mesh_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_draco_mesh_compression* out_draco_mesh_compression) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0) - { - i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_draco_mesh_compression->attributes, &out_draco_mesh_compression->attributes_count); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0) - { - ++i; - out_draco_mesh_compression->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_mapping* out_mappings, cgltf_size* offset) -{ - (void)options; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int obj_size = tokens[i].size; - ++i; - - int material = -1; - int variants_tok = -1; - cgltf_extras extras = {0, 0}; - - for (int k = 0; k < obj_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "material") == 0) - { - ++i; - material = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) - { - variants_tok = i+1; - CGLTF_CHECK_TOKTYPE(tokens[variants_tok], JSMN_ARRAY); - - i = cgltf_skip_json(tokens, i+1); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &extras); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - if (material < 0 || variants_tok < 0) - { - return CGLTF_ERROR_JSON; - } - - if (out_mappings) - { - for (int k = 0; k < tokens[variants_tok].size; ++k) - { - int variant = cgltf_json_to_int(&tokens[variants_tok + 1 + k], json_chunk); - if (variant < 0) - return variant; - - out_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material); - out_mappings[*offset].variant = variant; - out_mappings[*offset].extras = extras; - - (*offset)++; - } - } - else - { - (*offset) += tokens[variants_tok].size; - } - } - - return i; -} - -static int cgltf_parse_json_material_mappings(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "mappings") == 0) - { - if (out_prim->mappings) - { - return CGLTF_ERROR_JSON; - } - - cgltf_size mappings_offset = 0; - int k = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, NULL, &mappings_offset); - if (k < 0) - { - return k; - } - - out_prim->mappings_count = mappings_offset; - out_prim->mappings = (cgltf_material_mapping*)cgltf_calloc(options, sizeof(cgltf_material_mapping), out_prim->mappings_count); - - mappings_offset = 0; - i = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, out_prim->mappings, &mappings_offset); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - out_prim->type = cgltf_primitive_type_triangles; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) - { - ++i; - out_prim->type - = (cgltf_primitive_type) - cgltf_json_to_int(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) - { - ++i; - out_prim->indices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "material") == 0) - { - ++i; - out_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0) - { - i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "targets") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count); - if (i < 0) - { - return i; - } - - for (cgltf_size k = 0; k < out_prim->targets_count; ++k) - { - i = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count); - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_prim->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if(out_prim->extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - out_prim->extensions_count = 0; - out_prim->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - - if (!out_prim->extensions) - { - return CGLTF_ERROR_NOMEM; - } - - ++i; - for (int k = 0; k < extensions_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_draco_mesh_compression") == 0) - { - out_prim->has_draco_mesh_compression = 1; - i = cgltf_parse_json_draco_mesh_compression(options, tokens, i + 1, json_chunk, &out_prim->draco_mesh_compression); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) - { - i = cgltf_parse_json_material_mappings(options, tokens, i + 1, json_chunk, out_prim); - } - else - { - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_prim->extensions[out_prim->extensions_count++])); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count); - if (i < 0) - { - return i; - } - - for (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index) - { - i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]); - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count); - if (i < 0) - { - return i; - } - - i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - ++i; - - out_mesh->extras.start_offset = tokens[i].start; - out_mesh->extras.end_offset = tokens[i].end; - - if (tokens[i].type == JSMN_OBJECT) - { - int extras_size = tokens[i].size; - ++i; - - for (int k = 0; k < extras_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0 && tokens[i+1].type == JSMN_ARRAY) - { - i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i); - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_mesh->extensions_count, &out_mesh->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_mesh), (void**)&out_data->meshes, &out_data->meshes_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->meshes_count; ++j) - { - i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk) -{ - int type = cgltf_json_to_int(tok, json_chunk); - - switch (type) - { - case 5120: - return cgltf_component_type_r_8; - case 5121: - return cgltf_component_type_r_8u; - case 5122: - return cgltf_component_type_r_16; - case 5123: - return cgltf_component_type_r_16u; - case 5125: - return cgltf_component_type_r_32u; - case 5126: - return cgltf_component_type_r_32f; - default: - return cgltf_component_type_invalid; - } -} - -static int cgltf_parse_json_accessor_sparse(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) - { - ++i; - out_sparse->count = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int indices_size = tokens[i].size; - ++i; - - for (int k = 0; k < indices_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) - { - ++i; - out_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) - { - ++i; - out_sparse->indices_byte_offset = cgltf_json_to_size(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) - { - ++i; - out_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->indices_extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->indices_extensions_count, &out_sparse->indices_extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "values") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int values_size = tokens[i].size; - ++i; - - for (int k = 0; k < values_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) - { - ++i; - out_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) - { - ++i; - out_sparse->values_byte_offset = cgltf_json_to_size(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->values_extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->values_extensions_count, &out_sparse->values_extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->extensions_count, &out_sparse->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_accessor->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) - { - ++i; - out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) - { - ++i; - out_accessor->offset = - cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) - { - ++i; - out_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "normalized") == 0) - { - ++i; - out_accessor->normalized = cgltf_json_to_bool(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) - { - ++i; - out_accessor->count = - cgltf_json_to_int(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens+i, json_chunk, "SCALAR") == 0) - { - out_accessor->type = cgltf_type_scalar; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) - { - out_accessor->type = cgltf_type_vec2; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) - { - out_accessor->type = cgltf_type_vec3; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) - { - out_accessor->type = cgltf_type_vec4; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) - { - out_accessor->type = cgltf_type_mat2; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) - { - out_accessor->type = cgltf_type_mat3; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) - { - out_accessor->type = cgltf_type_mat4; - } - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "min") == 0) - { - ++i; - out_accessor->has_min = 1; - // note: we can't parse the precise number of elements since type may not have been computed yet - int min_size = tokens[i].size > 16 ? 16 : tokens[i].size; - i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "max") == 0) - { - ++i; - out_accessor->has_max = 1; - // note: we can't parse the precise number of elements since type may not have been computed yet - int max_size = tokens[i].size > 16 ? 16 : tokens[i].size; - i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "sparse") == 0) - { - out_accessor->is_sparse = 1; - i = cgltf_parse_json_accessor_sparse(options, tokens, i + 1, json_chunk, &out_accessor->sparse); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_accessor->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_accessor->extensions_count, &out_accessor->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "offset") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0) - { - ++i; - out_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) - { - ++i; - out_texture_transform->has_texcoord = 1; - out_texture_transform->texcoord = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_texture_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - out_texture_view->scale = 1.0f; - cgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0) - { - ++i; - out_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) - { - ++i; - out_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) - { - ++i; - out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "strength") == 0) - { - ++i; - out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture_view->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if(out_texture_view->extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - out_texture_view->extensions_count = 0; - out_texture_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - - if (!out_texture_view->extensions) - { - return CGLTF_ERROR_NOMEM; - } - - ++i; - - for (int k = 0; k < extensions_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_texture_transform") == 0) - { - out_texture_view->has_transform = 1; - i = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform); - } - else - { - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture_view->extensions[out_texture_view->extensions_count++])); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_pbr_metallic_roughness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "metallicFactor") == 0) - { - ++i; - out_pbr->metallic_factor = - cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "roughnessFactor") == 0) - { - ++i; - out_pbr->roughness_factor = - cgltf_json_to_float(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorFactor") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, - &out_pbr->base_color_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicRoughnessTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, - &out_pbr->metallic_roughness_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_pbr->extras); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_pbr_specular_glossiness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseFactor") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "glossinessFactor") == 0) - { - ++i; - out_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->diffuse_texture); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularGlossinessTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_clearcoat(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_clearcoat* out_clearcoat) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatFactor") == 0) - { - ++i; - out_clearcoat->clearcoat_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessFactor") == 0) - { - ++i; - out_clearcoat->clearcoat_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_texture); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_roughness_texture); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatNormalTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_normal_texture); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_ior(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_ior* out_ior) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - // Default values - out_ior->ior = 1.5f; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "ior") == 0) - { - ++i; - out_ior->ior = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_specular(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_specular* out_specular) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - // Default values - out_specular->specular_factor = 1.0f; - cgltf_fill_float_array(out_specular->specular_color_factor, 3, 1.0f); - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) - { - ++i; - out_specular->specular_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularColorFactor") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_specular->specular_color_factor, 3); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularColorTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_color_texture); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_transmission* out_transmission) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionFactor") == 0) - { - ++i; - out_transmission->transmission_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_transmission->transmission_texture); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_volume(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_volume* out_volume) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessFactor") == 0) - { - ++i; - out_volume->thickness_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_volume->thickness_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationColor") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_volume->attenuation_color, 3); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationDistance") == 0) - { - ++i; - out_volume->attenuation_distance = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_sheen(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sheen* out_sheen) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorFactor") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_sheen->sheen_color_factor, 3); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_color_texture); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessFactor") == 0) - { - ++i; - out_sheen->sheen_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_roughness_texture); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_emissive_strength(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_emissive_strength* out_emissive_strength) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - // Default - out_emissive_strength->emissive_strength = 1.f; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveStrength") == 0) - { - ++i; - out_emissive_strength->emissive_strength = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_iridescence(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_iridescence* out_iridescence) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int size = tokens[i].size; - ++i; - - // Default - out_iridescence->iridescence_ior = 1.3f; - out_iridescence->iridescence_thickness_min = 100.f; - out_iridescence->iridescence_thickness_max = 400.f; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceFactor") == 0) - { - ++i; - out_iridescence->iridescence_factor = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceIor") == 0) - { - ++i; - out_iridescence->iridescence_ior = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMinimum") == 0) - { - ++i; - out_iridescence->iridescence_thickness_min = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMaximum") == 0) - { - ++i; - out_iridescence->iridescence_thickness_max = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_thickness_texture); - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) - { - ++i; - out_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_image->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_image->extensions_count, &out_image->extensions); - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler) -{ - (void)options; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - out_sampler->wrap_s = 10497; - out_sampler->wrap_t = 10497; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_sampler->name); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) - { - ++i; - out_sampler->mag_filter - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) - { - ++i; - out_sampler->min_filter - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) - { - ++i; - out_sampler->wrap_s - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) - { - ++i; - out_sampler->wrap_t - = cgltf_json_to_int(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_texture->name); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) - { - ++i; - out_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) - { - ++i; - out_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if (out_texture->extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - ++i; - out_texture->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - out_texture->extensions_count = 0; - - if (!out_texture->extensions) - { - return CGLTF_ERROR_NOMEM; - } - - for (int k = 0; k < extensions_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_texture_basisu") == 0) - { - out_texture->has_basisu = 1; - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - int num_properties = tokens[i].size; - ++i; - - for (int t = 0; t < num_properties; ++t) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) - { - ++i; - out_texture->basisu_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - } - } - else - { - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture->extensions[out_texture->extensions_count++])); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - cgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f); - out_material->pbr_metallic_roughness.metallic_factor = 1.0f; - out_material->pbr_metallic_roughness.roughness_factor = 1.0f; - - cgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f); - cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f); - out_material->pbr_specular_glossiness.glossiness_factor = 1.0f; - - cgltf_fill_float_array(out_material->volume.attenuation_color, 3, 1.0f); - out_material->volume.attenuation_distance = FLT_MAX; - - out_material->alpha_cutoff = 0.5f; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_material->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) - { - out_material->has_pbr_metallic_roughness = 1; - i = cgltf_parse_json_pbr_metallic_roughness(options, tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, - &out_material->normal_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, - &out_material->occlusion_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) - { - i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, - &out_material->emissive_texture); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens + i, json_chunk, "OPAQUE") == 0) - { - out_material->alpha_mode = cgltf_alpha_mode_opaque; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "MASK") == 0) - { - out_material->alpha_mode = cgltf_alpha_mode_mask; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "BLEND") == 0) - { - out_material->alpha_mode = cgltf_alpha_mode_blend; - } - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaCutoff") == 0) - { - ++i; - out_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) - { - ++i; - out_material->double_sided = - cgltf_json_to_bool(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_material->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if(out_material->extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - ++i; - out_material->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - out_material->extensions_count= 0; - - if (!out_material->extensions) - { - return CGLTF_ERROR_NOMEM; - } - - for (int k = 0; k < extensions_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_pbrSpecularGlossiness") == 0) - { - out_material->has_pbr_specular_glossiness = 1; - i = cgltf_parse_json_pbr_specular_glossiness(options, tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_unlit") == 0) - { - out_material->unlit = 1; - i = cgltf_skip_json(tokens, i+1); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_clearcoat") == 0) - { - out_material->has_clearcoat = 1; - i = cgltf_parse_json_clearcoat(options, tokens, i + 1, json_chunk, &out_material->clearcoat); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_ior") == 0) - { - out_material->has_ior = 1; - i = cgltf_parse_json_ior(tokens, i + 1, json_chunk, &out_material->ior); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_specular") == 0) - { - out_material->has_specular = 1; - i = cgltf_parse_json_specular(options, tokens, i + 1, json_chunk, &out_material->specular); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_transmission") == 0) - { - out_material->has_transmission = 1; - i = cgltf_parse_json_transmission(options, tokens, i + 1, json_chunk, &out_material->transmission); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_volume") == 0) - { - out_material->has_volume = 1; - i = cgltf_parse_json_volume(options, tokens, i + 1, json_chunk, &out_material->volume); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_sheen") == 0) - { - out_material->has_sheen = 1; - i = cgltf_parse_json_sheen(options, tokens, i + 1, json_chunk, &out_material->sheen); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_emissive_strength") == 0) - { - out_material->has_emissive_strength = 1; - i = cgltf_parse_json_emissive_strength(tokens, i + 1, json_chunk, &out_material->emissive_strength); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_iridescence") == 0) - { - out_material->has_iridescence = 1; - i = cgltf_parse_json_iridescence(options, tokens, i + 1, json_chunk, &out_material->iridescence); - } - else - { - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_material->extensions[out_material->extensions_count++])); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->accessors_count; ++j) - { - i = cgltf_parse_json_accessor(options, tokens, i, json_chunk, &out_data->accessors[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->materials_count; ++j) - { - i = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_image), (void**)&out_data->images, &out_data->images_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->images_count; ++j) - { - i = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_texture), (void**)&out_data->textures, &out_data->textures_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->textures_count; ++j) - { - i = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_sampler), (void**)&out_data->samplers, &out_data->samplers_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->samplers_count; ++j) - { - i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_meshopt_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_meshopt_compression* out_meshopt_compression) -{ - (void)options; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) - { - ++i; - out_meshopt_compression->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) - { - ++i; - out_meshopt_compression->offset = cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) - { - ++i; - out_meshopt_compression->size = cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) - { - ++i; - out_meshopt_compression->stride = cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) - { - ++i; - out_meshopt_compression->count = cgltf_json_to_int(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens+i, json_chunk, "ATTRIBUTES") == 0) - { - out_meshopt_compression->mode = cgltf_meshopt_compression_mode_attributes; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "TRIANGLES") == 0) - { - out_meshopt_compression->mode = cgltf_meshopt_compression_mode_triangles; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "INDICES") == 0) - { - out_meshopt_compression->mode = cgltf_meshopt_compression_mode_indices; - } - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "filter") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens+i, json_chunk, "NONE") == 0) - { - out_meshopt_compression->filter = cgltf_meshopt_compression_filter_none; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "OCTAHEDRAL") == 0) - { - out_meshopt_compression->filter = cgltf_meshopt_compression_filter_octahedral; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "QUATERNION") == 0) - { - out_meshopt_compression->filter = cgltf_meshopt_compression_filter_quaternion; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "EXPONENTIAL") == 0) - { - out_meshopt_compression->filter = cgltf_meshopt_compression_filter_exponential; - } - ++i; - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_buffer_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer_view* out_buffer_view) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer_view->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) - { - ++i; - out_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) - { - ++i; - out_buffer_view->offset = - cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) - { - ++i; - out_buffer_view->size = - cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) - { - ++i; - out_buffer_view->stride = - cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) - { - ++i; - int type = cgltf_json_to_int(tokens+i, json_chunk); - switch (type) - { - case 34962: - type = cgltf_buffer_view_type_vertices; - break; - case 34963: - type = cgltf_buffer_view_type_indices; - break; - default: - type = cgltf_buffer_view_type_invalid; - break; - } - out_buffer_view->type = (cgltf_buffer_view_type)type; - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer_view->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if(out_buffer_view->extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - out_buffer_view->extensions_count = 0; - out_buffer_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - - if (!out_buffer_view->extensions) - { - return CGLTF_ERROR_NOMEM; - } - - ++i; - for (int k = 0; k < extensions_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "EXT_meshopt_compression") == 0) - { - out_buffer_view->has_meshopt_compression = 1; - i = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression); - } - else - { - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_buffer_view->extensions[out_buffer_view->extensions_count++])); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->buffer_views_count; ++j) - { - i = cgltf_parse_json_buffer_view(options, tokens, i, json_chunk, &out_data->buffer_views[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer* out_buffer) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) - { - ++i; - out_buffer->size = - cgltf_json_to_size(tokens+i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "uri") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_buffer->extensions_count, &out_buffer->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->buffers_count; ++j) - { - i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_skin->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "joints") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count); - if (i < 0) - { - return i; - } - - for (cgltf_size k = 0; k < out_skin->joints_count; ++k) - { - out_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "skeleton") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); - out_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "inverseBindMatrices") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); - out_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_skin->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_skin->extensions_count, &out_skin->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_skin), (void**)&out_data->skins, &out_data->skins_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->skins_count; ++j) - { - i = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens + i, json_chunk, "perspective") == 0) - { - out_camera->type = cgltf_camera_type_perspective; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "orthographic") == 0) - { - out_camera->type = cgltf_camera_type_orthographic; - } - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "perspective") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int data_size = tokens[i].size; - ++i; - - out_camera->type = cgltf_camera_type_perspective; - - for (int k = 0; k < data_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "aspectRatio") == 0) - { - ++i; - out_camera->data.perspective.has_aspect_ratio = 1; - out_camera->data.perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "yfov") == 0) - { - ++i; - out_camera->data.perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) - { - ++i; - out_camera->data.perspective.has_zfar = 1; - out_camera->data.perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) - { - ++i; - out_camera->data.perspective.znear = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.perspective.extras); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "orthographic") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int data_size = tokens[i].size; - ++i; - - out_camera->type = cgltf_camera_type_orthographic; - - for (int k = 0; k < data_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "xmag") == 0) - { - ++i; - out_camera->data.orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "ymag") == 0) - { - ++i; - out_camera->data.orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) - { - ++i; - out_camera->data.orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) - { - ++i; - out_camera->data.orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_camera->extensions_count, &out_camera->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_camera), (void**)&out_data->cameras, &out_data->cameras_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->cameras_count; ++j) - { - i = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - out_light->color[0] = 1.f; - out_light->color[1] = 1.f; - out_light->color[2] = 1.f; - out_light->intensity = 1.f; - - out_light->spot_inner_cone_angle = 0.f; - out_light->spot_outer_cone_angle = 3.1415926535f / 4.0f; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_light->name); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "color") == 0) - { - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "intensity") == 0) - { - ++i; - out_light->intensity = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens + i, json_chunk, "directional") == 0) - { - out_light->type = cgltf_light_type_directional; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "point") == 0) - { - out_light->type = cgltf_light_type_point; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0) - { - out_light->type = cgltf_light_type_spot; - } - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "range") == 0) - { - ++i; - out_light->range = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "spot") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int data_size = tokens[i].size; - ++i; - - for (int k = 0; k < data_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "innerConeAngle") == 0) - { - ++i; - out_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "outerConeAngle") == 0) - { - ++i; - out_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_light->extras); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_light), (void**)&out_data->lights, &out_data->lights_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->lights_count; ++j) - { - i = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - out_node->rotation[3] = 1.0f; - out_node->scale[0] = 1.0f; - out_node->scale[1] = 1.0f; - out_node->scale[2] = 1.0f; - out_node->matrix[0] = 1.0f; - out_node->matrix[5] = 1.0f; - out_node->matrix[10] = 1.0f; - out_node->matrix[15] = 1.0f; - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_node->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "children") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count); - if (i < 0) - { - return i; - } - - for (cgltf_size k = 0; k < out_node->children_count; ++k) - { - out_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "mesh") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); - out_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "skin") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); - out_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "camera") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); - out_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) - { - out_node->has_translation = 1; - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) - { - out_node->has_rotation = 1; - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) - { - out_node->has_scale = 1; - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "matrix") == 0) - { - out_node->has_matrix = 1; - i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count); - if (i < 0) - { - return i; - } - - i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_node->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if(out_node->extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - out_node->extensions_count= 0; - out_node->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - - if (!out_node->extensions) - { - return CGLTF_ERROR_NOMEM; - } - - ++i; - - for (int k = 0; k < extensions_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int data_size = tokens[i].size; - ++i; - - for (int m = 0; m < data_size; ++m) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "light") == 0) - { - ++i; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); - out_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_node->extensions[out_node->extensions_count++])); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_node), (void**)&out_data->nodes, &out_data->nodes_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->nodes_count; ++j) - { - i = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_scene->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "nodes") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count); - if (i < 0) - { - return i; - } - - for (cgltf_size k = 0; k < out_scene->nodes_count; ++k) - { - out_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_scene->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_scene->extensions_count, &out_scene->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_scene), (void**)&out_data->scenes, &out_data->scenes_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->scenes_count; ++j) - { - i = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler) -{ - (void)options; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "input") == 0) - { - ++i; - out_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "output") == 0) - { - ++i; - out_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "interpolation") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens + i, json_chunk, "LINEAR") == 0) - { - out_sampler->interpolation = cgltf_interpolation_type_linear; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "STEP") == 0) - { - out_sampler->interpolation = cgltf_interpolation_type_step; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "CUBICSPLINE") == 0) - { - out_sampler->interpolation = cgltf_interpolation_type_cubic_spline; - } - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel) -{ - (void)options; - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "sampler") == 0) - { - ++i; - out_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int target_size = tokens[i].size; - ++i; - - for (int k = 0; k < target_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "node") == 0) - { - ++i; - out_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "path") == 0) - { - ++i; - if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) - { - out_channel->target_path = cgltf_animation_path_type_translation; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) - { - out_channel->target_path = cgltf_animation_path_type_rotation; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) - { - out_channel->target_path = cgltf_animation_path_type_scale; - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "weights") == 0) - { - out_channel->target_path = cgltf_animation_path_type_weights; - } - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_channel->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_channel->extensions_count, &out_channel->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_animation->name); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "samplers") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count); - if (i < 0) - { - return i; - } - - for (cgltf_size k = 0; k < out_animation->samplers_count; ++k) - { - i = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]); - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "channels") == 0) - { - i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count); - if (i < 0) - { - return i; - } - - for (cgltf_size k = 0; k < out_animation->channels_count; ++k) - { - i = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]); - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_animation->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_animation->extensions_count, &out_animation->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_animation), (void**)&out_data->animations, &out_data->animations_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->animations_count; ++j) - { - i = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_variant* out_variant) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_variant->name); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_variant->extras); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -static int cgltf_parse_json_variants(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material_variant), (void**)&out_data->variants, &out_data->variants_count); - if (i < 0) - { - return i; - } - - for (cgltf_size j = 0; j < out_data->variants_count; ++j) - { - i = cgltf_parse_json_variant(options, tokens, i, json_chunk, &out_data->variants[j]); - if (i < 0) - { - return i; - } - } - return i; -} - -static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "copyright") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "generator") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "version") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "minVersion") == 0) - { - i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_asset->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_asset->extensions_count, &out_asset->extensions); - } - else - { - i = cgltf_skip_json(tokens, i+1); - } - - if (i < 0) - { - return i; - } - } - - if (out_asset->version && CGLTF_ATOF(out_asset->version) < 2) - { - return CGLTF_ERROR_LEGACY; - } - - return i; -} - -cgltf_size cgltf_num_components(cgltf_type type) { - switch (type) - { - case cgltf_type_vec2: - return 2; - case cgltf_type_vec3: - return 3; - case cgltf_type_vec4: - return 4; - case cgltf_type_mat2: - return 4; - case cgltf_type_mat3: - return 9; - case cgltf_type_mat4: - return 16; - case cgltf_type_invalid: - case cgltf_type_scalar: - default: - return 1; - } -} - -static cgltf_size cgltf_component_size(cgltf_component_type component_type) { - switch (component_type) - { - case cgltf_component_type_r_8: - case cgltf_component_type_r_8u: - return 1; - case cgltf_component_type_r_16: - case cgltf_component_type_r_16u: - return 2; - case cgltf_component_type_r_32u: - case cgltf_component_type_r_32f: - return 4; - case cgltf_component_type_invalid: - default: - return 0; - } -} - -static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) -{ - cgltf_size component_size = cgltf_component_size(component_type); - if (type == cgltf_type_mat2 && component_size == 1) - { - return 8 * component_size; - } - else if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2)) - { - return 12 * component_size; - } - return component_size * cgltf_num_components(type); -} - -static int cgltf_fixup_pointers(cgltf_data* out_data); - -static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) -{ - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int size = tokens[i].size; - ++i; - - for (int j = 0; j < size; ++j) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "asset") == 0) - { - i = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "meshes") == 0) - { - i = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "accessors") == 0) - { - i = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferViews") == 0) - { - i = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffers") == 0) - { - i = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "materials") == 0) - { - i = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "images") == 0) - { - i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "textures") == 0) - { - i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0) - { - i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "skins") == 0) - { - i = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "cameras") == 0) - { - i = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0) - { - i = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "scenes") == 0) - { - i = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "scene") == 0) - { - ++i; - out_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk)); - ++i; - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "animations") == 0) - { - i = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data); - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "extras") == 0) - { - i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_data->extras); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - if(out_data->data_extensions) - { - return CGLTF_ERROR_JSON; - } - - int extensions_size = tokens[i].size; - out_data->data_extensions_count = 0; - out_data->data_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); - - if (!out_data->data_extensions) - { - return CGLTF_ERROR_NOMEM; - } - - ++i; - - for (int k = 0; k < extensions_size; ++k) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int data_size = tokens[i].size; - ++i; - - for (int m = 0; m < data_size; ++m) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "lights") == 0) - { - i = cgltf_parse_json_lights(options, tokens, i + 1, json_chunk, out_data); - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) - { - ++i; - - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); - - int data_size = tokens[i].size; - ++i; - - for (int m = 0; m < data_size; ++m) - { - CGLTF_CHECK_KEY(tokens[i]); - - if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) - { - i = cgltf_parse_json_variants(options, tokens, i + 1, json_chunk, out_data); - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - } - else - { - i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_data->data_extensions[out_data->data_extensions_count++])); - } - - if (i < 0) - { - return i; - } - } - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsUsed") == 0) - { - i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count); - } - else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsRequired") == 0) - { - i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count); - } - else - { - i = cgltf_skip_json(tokens, i + 1); - } - - if (i < 0) - { - return i; - } - } - - return i; -} - -cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data) -{ - jsmn_parser parser = { 0, 0, 0 }; - - if (options->json_token_count == 0) - { - int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); - - if (token_count <= 0) - { - return cgltf_result_invalid_json; - } - - options->json_token_count = token_count; - } - - jsmntok_t* tokens = (jsmntok_t*)options->memory.alloc(options->memory.user_data, sizeof(jsmntok_t) * (options->json_token_count + 1)); - - if (!tokens) - { - return cgltf_result_out_of_memory; - } - - jsmn_init(&parser); - - int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count); - - if (token_count <= 0) - { - options->memory.free(options->memory.user_data, tokens); - return cgltf_result_invalid_json; - } - - // this makes sure that we always have an UNDEFINED token at the end of the stream - // for invalid JSON inputs this makes sure we don't perform out of bound reads of token data - tokens[token_count].type = JSMN_UNDEFINED; - - cgltf_data* data = (cgltf_data*)options->memory.alloc(options->memory.user_data, sizeof(cgltf_data)); - - if (!data) - { - options->memory.free(options->memory.user_data, tokens); - return cgltf_result_out_of_memory; - } - - memset(data, 0, sizeof(cgltf_data)); - data->memory = options->memory; - data->file = options->file; - - int i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data); - - options->memory.free(options->memory.user_data, tokens); - - if (i < 0) - { - cgltf_free(data); - - switch (i) - { - case CGLTF_ERROR_NOMEM: return cgltf_result_out_of_memory; - case CGLTF_ERROR_LEGACY: return cgltf_result_legacy_gltf; - default: return cgltf_result_invalid_gltf; - } - } - - if (cgltf_fixup_pointers(data) < 0) - { - cgltf_free(data); - return cgltf_result_invalid_gltf; - } - - data->json = (const char*)json_chunk; - data->json_size = size; - - *out_data = data; - - return cgltf_result_success; -} - -static int cgltf_fixup_pointers(cgltf_data* data) -{ - for (cgltf_size i = 0; i < data->meshes_count; ++i) - { - for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) - { - CGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count); - CGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count); - - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) - { - CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count); - } - - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) - { - for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) - { - CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count); - } - } - - if (data->meshes[i].primitives[j].has_draco_mesh_compression) - { - CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.buffer_view, data->buffer_views, data->buffer_views_count); - for (cgltf_size m = 0; m < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++m) - { - CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.attributes[m].data, data->accessors, data->accessors_count); - } - } - - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) - { - CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].mappings[k].material, data->materials, data->materials_count); - } - } - } - - for (cgltf_size i = 0; i < data->accessors_count; ++i) - { - CGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count); - - if (data->accessors[i].is_sparse) - { - CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count); - CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count); - } - - if (data->accessors[i].buffer_view) - { - data->accessors[i].stride = data->accessors[i].buffer_view->stride; - } - - if (data->accessors[i].stride == 0) - { - data->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type); - } - } - - for (cgltf_size i = 0; i < data->textures_count; ++i) - { - CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count); - CGLTF_PTRFIXUP(data->textures[i].basisu_image, data->images, data->images_count); - CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count); - } - - for (cgltf_size i = 0; i < data->images_count; ++i) - { - CGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count); - } - - for (cgltf_size i = 0; i < data->materials_count; ++i) - { - CGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_roughness_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_normal_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].specular.specular_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].specular.specular_color_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].transmission.transmission_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].volume.thickness_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_color_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_roughness_texture.texture, data->textures, data->textures_count); - - CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_texture.texture, data->textures, data->textures_count); - CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_thickness_texture.texture, data->textures, data->textures_count); - } - - for (cgltf_size i = 0; i < data->buffer_views_count; ++i) - { - CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count); - - if (data->buffer_views[i].has_meshopt_compression) - { - CGLTF_PTRFIXUP_REQ(data->buffer_views[i].meshopt_compression.buffer, data->buffers, data->buffers_count); - } - } - - for (cgltf_size i = 0; i < data->skins_count; ++i) - { - for (cgltf_size j = 0; j < data->skins[i].joints_count; ++j) - { - CGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count); - } - - CGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count); - CGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count); - } - - for (cgltf_size i = 0; i < data->nodes_count; ++i) - { - for (cgltf_size j = 0; j < data->nodes[i].children_count; ++j) - { - CGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count); - - if (data->nodes[i].children[j]->parent) - { - return CGLTF_ERROR_JSON; - } - - data->nodes[i].children[j]->parent = &data->nodes[i]; - } - - CGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count); - CGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count); - CGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count); - CGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count); - } - - for (cgltf_size i = 0; i < data->scenes_count; ++i) - { - for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) - { - CGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count); - - if (data->scenes[i].nodes[j]->parent) - { - return CGLTF_ERROR_JSON; - } - } - } - - CGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count); - - for (cgltf_size i = 0; i < data->animations_count; ++i) - { - for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) - { - CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count); - CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count); - } - - for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) - { - CGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count); - CGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count); - } - } - - return 0; -} - -/* - * -- jsmn.c start -- - * Source: https://github.com/zserge/jsmn - * License: MIT - * - * Copyright (c) 2010 Serge A. Zaitsev - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Allocates a fresh unused token from the token pull. - */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, - jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *tok; - if (parser->toknext >= num_tokens) { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; -} - -/** - * Fills token type and boundaries. - */ -static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, - int start, int end) { - token->type = type; - token->start = start; - token->end = end; - token->size = 0; -} - -/** - * Fills next available token with JSON primitive. - */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t' : case '\r' : case '\n' : case ' ' : - case ',' : case ']' : case '}' : - goto found; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; -} - -/** - * Fills next token with JSON string. - */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - - int start = parser->pos; - - parser->pos++; - - /* Skip starting quote */ - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': case '/' : case '\\' : case 'b' : - case 'f' : case 'r' : case 'n' : case 't' : - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { - /* If it isn't a hex character we have an error */ - if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; -} - -/** - * Parse JSON string and fill tokens. - */ -static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, size_t num_tokens) { - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) { - case '{': case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - return JSMN_ERROR_NOMEM; - if (parser->toksuper != -1) { - tokens[parser->toksuper].size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': case ']': - if (tokens == NULL) - break; - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if(token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) return JSMN_ERROR_INVAL; - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - case '\t' : case '\r' : case '\n' : case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': case '0': case '1' : case '2': case '3' : case '4': - case '5': case '6': case '7' : case '8': case '9': - case 't': case 'f': case 'n' : - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } - - return count; -} - -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -static void jsmn_init(jsmn_parser *parser) { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; -} -/* - * -- jsmn.c end -- - */ - -#endif /* #ifdef CGLTF_IMPLEMENTATION */ - -/* cgltf is distributed under MIT license: - * - * Copyright (c) 2018-2021 Johannes Kuhlmann - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ diff --git a/Dependencies/meshoptimizer/extern/fast_obj.h b/Dependencies/meshoptimizer/extern/fast_obj.h deleted file mode 100644 index 737ea314..00000000 --- a/Dependencies/meshoptimizer/extern/fast_obj.h +++ /dev/null @@ -1,1434 +0,0 @@ -/* - * fast_obj - * - * Version 1.1 - * - * MIT License - * - * Copyright (c) 2018-2020 Richard Knight - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -#ifndef FAST_OBJ_HDR -#define FAST_OBJ_HDR - -#define FAST_OBJ_VERSION_MAJOR 1 -#define FAST_OBJ_VERSION_MINOR 1 -#define FAST_OBJ_VERSION ((FAST_OBJ_VERSION_MAJOR << 8) | FAST_OBJ_VERSION_MINOR) - -#include - - -typedef struct -{ - /* Texture name from .mtl file */ - char* name; - - /* Resolved path to texture */ - char* path; - -} fastObjTexture; - - -typedef struct -{ - /* Material name */ - char* name; - - /* Parameters */ - float Ka[3]; /* Ambient */ - float Kd[3]; /* Diffuse */ - float Ks[3]; /* Specular */ - float Ke[3]; /* Emission */ - float Kt[3]; /* Transmittance */ - float Ns; /* Shininess */ - float Ni; /* Index of refraction */ - float Tf[3]; /* Transmission filter */ - float d; /* Disolve (alpha) */ - int illum; /* Illumination model */ - - /* Texture maps */ - fastObjTexture map_Ka; - fastObjTexture map_Kd; - fastObjTexture map_Ks; - fastObjTexture map_Ke; - fastObjTexture map_Kt; - fastObjTexture map_Ns; - fastObjTexture map_Ni; - fastObjTexture map_d; - fastObjTexture map_bump; - -} fastObjMaterial; - -/* Allows user override to bigger indexable array */ -#ifndef FAST_OBJ_UINT_TYPE -#define FAST_OBJ_UINT_TYPE unsigned int -#endif - -typedef FAST_OBJ_UINT_TYPE fastObjUInt; - -typedef struct -{ - fastObjUInt p; - fastObjUInt t; - fastObjUInt n; - -} fastObjIndex; - - -typedef struct -{ - /* Group name */ - char* name; - - /* Number of faces */ - unsigned int face_count; - - /* First face in fastObjMesh face_* arrays */ - unsigned int face_offset; - - /* First index in fastObjMesh indices array */ - unsigned int index_offset; - -} fastObjGroup; - - -typedef struct -{ - /* Vertex data */ - unsigned int position_count; - float* positions; - - unsigned int texcoord_count; - float* texcoords; - - unsigned int normal_count; - float* normals; - - /* Face data: one element for each face */ - unsigned int face_count; - unsigned int* face_vertices; - unsigned int* face_materials; - - /* Index data: one element for each face vertex */ - fastObjIndex* indices; - - /* Materials */ - unsigned int material_count; - fastObjMaterial* materials; - - /* Mesh groups */ - unsigned int group_count; - fastObjGroup* groups; - -} fastObjMesh; - -typedef struct -{ - void* (*file_open)(const char* path, void* user_data); - void (*file_close)(void* file, void* user_data); - size_t (*file_read)(void* file, void* dst, size_t bytes, void* user_data); - unsigned long (*file_size)(void* file, void* user_data); -} fastObjCallbacks; - -#ifdef __cplusplus -extern "C" { -#endif - -fastObjMesh* fast_obj_read(const char* path); -fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data); -void fast_obj_destroy(fastObjMesh* mesh); - -#ifdef __cplusplus -} -#endif - -#endif - - -#ifdef FAST_OBJ_IMPLEMENTATION - -#include -#include - -#ifndef FAST_OBJ_REALLOC -#define FAST_OBJ_REALLOC realloc -#endif - -#ifndef FAST_OBJ_FREE -#define FAST_OBJ_FREE free -#endif - -#ifdef _WIN32 -#define FAST_OBJ_SEPARATOR '\\' -#define FAST_OBJ_OTHER_SEP '/' -#else -#define FAST_OBJ_SEPARATOR '/' -#define FAST_OBJ_OTHER_SEP '\\' -#endif - - -/* Size of buffer to read into */ -#define BUFFER_SIZE 65536 - -/* Max supported power when parsing float */ -#define MAX_POWER 20 - -typedef struct -{ - /* Final mesh */ - fastObjMesh* mesh; - - /* Current group */ - fastObjGroup group; - - /* Current material index */ - unsigned int material; - - /* Current line in file */ - unsigned int line; - - /* Base path for materials/textures */ - char* base; - -} fastObjData; - - -static const -double POWER_10_POS[MAX_POWER] = -{ - 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9, - 1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19, -}; - -static const -double POWER_10_NEG[MAX_POWER] = -{ - 1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, 1.0e-7, 1.0e-8, 1.0e-9, - 1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19, -}; - - -static void* memory_realloc(void* ptr, size_t bytes) -{ - return FAST_OBJ_REALLOC(ptr, bytes); -} - - -static -void memory_dealloc(void* ptr) -{ - FAST_OBJ_FREE(ptr); -} - - -#define array_clean(_arr) ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0) -#define array_push(_arr, _val) (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0) -#define array_size(_arr) ((_arr) ? _array_size(_arr) : 0) -#define array_capacity(_arr) ((_arr) ? _array_capacity(_arr) : 0) -#define array_empty(_arr) (array_size(_arr) == 0) - -#define _array_header(_arr) ((fastObjUInt*)(_arr)-2) -#define _array_size(_arr) (_array_header(_arr)[0]) -#define _array_capacity(_arr) (_array_header(_arr)[1]) -#define _array_ngrow(_arr, _n) ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr))) -#define _array_mgrow(_arr, _n) (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1) -#define _array_grow(_arr, _n) (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr)))) - - -static void* array_realloc(void* ptr, fastObjUInt n, fastObjUInt b) -{ - fastObjUInt sz = array_size(ptr); - fastObjUInt nsz = sz + n; - fastObjUInt cap = array_capacity(ptr); - fastObjUInt ncap = 3 * cap / 2; - fastObjUInt* r; - - - if (ncap < nsz) - ncap = nsz; - ncap = (ncap + 15) & ~15u; - - r = (fastObjUInt*)(memory_realloc(ptr ? _array_header(ptr) : 0, b * ncap + 2 * sizeof(fastObjUInt))); - if (!r) - return 0; - - r[0] = sz; - r[1] = ncap; - - return (r + 2); -} - - -static -void* file_open(const char* path, void* user_data) -{ - (void)(user_data); - return fopen(path, "rb"); -} - - -static -void file_close(void* file, void* user_data) -{ - FILE* f; - (void)(user_data); - - f = (FILE*)(file); - fclose(f); -} - - -static -size_t file_read(void* file, void* dst, size_t bytes, void* user_data) -{ - FILE* f; - (void)(user_data); - - f = (FILE*)(file); - return fread(dst, 1, bytes, f); -} - - -static -unsigned long file_size(void* file, void* user_data) -{ - FILE* f; - long p; - long n; - (void)(user_data); - - f = (FILE*)(file); - - p = ftell(f); - fseek(f, 0, SEEK_END); - n = ftell(f); - fseek(f, p, SEEK_SET); - - if (n > 0) - return (unsigned long)(n); - else - return 0; -} - - -static -char* string_copy(const char* s, const char* e) -{ - size_t n; - char* p; - - n = (size_t)(e - s); - p = (char*)(memory_realloc(0, n + 1)); - if (p) - { - memcpy(p, s, n); - p[n] = '\0'; - } - - return p; -} - - -static -char* string_substr(const char* s, size_t a, size_t b) -{ - return string_copy(s + a, s + b); -} - - -static -char* string_concat(const char* a, const char* s, const char* e) -{ - size_t an; - size_t sn; - char* p; - - an = a ? strlen(a) : 0; - sn = (size_t)(e - s); - p = (char*)(memory_realloc(0, an + sn + 1)); - if (p) - { - if (a) - memcpy(p, a, an); - memcpy(p + an, s, sn); - p[an + sn] = '\0'; - } - - return p; -} - - -static -int string_equal(const char* a, const char* s, const char* e) -{ - size_t an = strlen(a); - size_t sn = (size_t)(e - s); - - return an == sn && memcmp(a, s, an) == 0; -} - - -static -void string_fix_separators(char* s) -{ - while (*s) - { - if (*s == FAST_OBJ_OTHER_SEP) - *s = FAST_OBJ_SEPARATOR; - s++; - } -} - - -static -int is_whitespace(char c) -{ - return (c == ' ' || c == '\t' || c == '\r'); -} - -static -int is_end_of_name(char c) -{ - return (c == '\t' || c == '\r' || c == '\n'); -} - -static -int is_newline(char c) -{ - return (c == '\n'); -} - - -static -int is_digit(char c) -{ - return (c >= '0' && c <= '9'); -} - - -static -int is_exponent(char c) -{ - return (c == 'e' || c == 'E'); -} - - -static -const char* skip_whitespace(const char* ptr) -{ - while (is_whitespace(*ptr)) - ptr++; - - return ptr; -} - - -static -const char* skip_line(const char* ptr) -{ - while (!is_newline(*ptr++)) - ; - - return ptr; -} - - -static -fastObjGroup group_default(void) -{ - fastObjGroup group; - - group.name = 0; - group.face_count = 0; - group.face_offset = 0; - group.index_offset = 0; - - return group; -} - - -static -void group_clean(fastObjGroup* group) -{ - memory_dealloc(group->name); -} - - -static -void flush_output(fastObjData* data) -{ - /* Add group if not empty */ - if (data->group.face_count > 0) - array_push(data->mesh->groups, data->group); - else - group_clean(&data->group); - - /* Reset for more data */ - data->group = group_default(); - data->group.face_offset = array_size(data->mesh->face_vertices); - data->group.index_offset = array_size(data->mesh->indices); -} - - -static -const char* parse_int(const char* ptr, int* val) -{ - int sign; - int num; - - - if (*ptr == '-') - { - sign = -1; - ptr++; - } - else - { - sign = +1; - } - - num = 0; - while (is_digit(*ptr)) - num = 10 * num + (*ptr++ - '0'); - - *val = sign * num; - - return ptr; -} - - -static -const char* parse_float(const char* ptr, float* val) -{ - double sign; - double num; - double fra; - double div; - int eval; - const double* powers; - - - ptr = skip_whitespace(ptr); - - switch (*ptr) - { - case '+': - sign = 1.0; - ptr++; - break; - - case '-': - sign = -1.0; - ptr++; - break; - - default: - sign = 1.0; - break; - } - - - num = 0.0; - while (is_digit(*ptr)) - num = 10.0 * num + (double)(*ptr++ - '0'); - - if (*ptr == '.') - ptr++; - - fra = 0.0; - div = 1.0; - - while (is_digit(*ptr)) - { - fra = 10.0 * fra + (double)(*ptr++ - '0'); - div *= 10.0; - } - - num += fra / div; - - if (is_exponent(*ptr)) - { - ptr++; - - switch (*ptr) - { - case '+': - powers = POWER_10_POS; - ptr++; - break; - - case '-': - powers = POWER_10_NEG; - ptr++; - break; - - default: - powers = POWER_10_POS; - break; - } - - eval = 0; - while (is_digit(*ptr)) - eval = 10 * eval + (*ptr++ - '0'); - - num *= (eval >= MAX_POWER) ? 0.0 : powers[eval]; - } - - *val = (float)(sign * num); - - return ptr; -} - - -static -const char* parse_vertex(fastObjData* data, const char* ptr) -{ - unsigned int ii; - float v; - - - for (ii = 0; ii < 3; ii++) - { - ptr = parse_float(ptr, &v); - array_push(data->mesh->positions, v); - } - - return ptr; -} - - -static -const char* parse_texcoord(fastObjData* data, const char* ptr) -{ - unsigned int ii; - float v; - - - for (ii = 0; ii < 2; ii++) - { - ptr = parse_float(ptr, &v); - array_push(data->mesh->texcoords, v); - } - - return ptr; -} - - -static -const char* parse_normal(fastObjData* data, const char* ptr) -{ - unsigned int ii; - float v; - - - for (ii = 0; ii < 3; ii++) - { - ptr = parse_float(ptr, &v); - array_push(data->mesh->normals, v); - } - - return ptr; -} - - -static -const char* parse_face(fastObjData* data, const char* ptr) -{ - unsigned int count; - fastObjIndex vn; - int v; - int t; - int n; - - - ptr = skip_whitespace(ptr); - - count = 0; - while (!is_newline(*ptr)) - { - v = 0; - t = 0; - n = 0; - - ptr = parse_int(ptr, &v); - if (*ptr == '/') - { - ptr++; - if (*ptr != '/') - ptr = parse_int(ptr, &t); - - if (*ptr == '/') - { - ptr++; - ptr = parse_int(ptr, &n); - } - } - - if (v < 0) - vn.p = (array_size(data->mesh->positions) / 3) - (fastObjUInt)(-v); - else - vn.p = (fastObjUInt)(v); - - if (t < 0) - vn.t = (array_size(data->mesh->texcoords) / 2) - (fastObjUInt)(-t); - else if (t > 0) - vn.t = (fastObjUInt)(t); - else - vn.t = 0; - - if (n < 0) - vn.n = (array_size(data->mesh->normals) / 3) - (fastObjUInt)(-n); - else if (n > 0) - vn.n = (fastObjUInt)(n); - else - vn.n = 0; - - array_push(data->mesh->indices, vn); - count++; - - ptr = skip_whitespace(ptr); - } - - array_push(data->mesh->face_vertices, count); - array_push(data->mesh->face_materials, data->material); - - data->group.face_count++; - - return ptr; -} - - -static -const char* parse_group(fastObjData* data, const char* ptr) -{ - const char* s; - const char* e; - - - ptr = skip_whitespace(ptr); - - s = ptr; - while (!is_end_of_name(*ptr)) - ptr++; - - e = ptr; - - flush_output(data); - data->group.name = string_copy(s, e); - - return ptr; -} - - -static -fastObjTexture map_default(void) -{ - fastObjTexture map; - - map.name = 0; - map.path = 0; - - return map; -} - - -static -fastObjMaterial mtl_default(void) -{ - fastObjMaterial mtl; - - mtl.name = 0; - - mtl.Ka[0] = 0.0; - mtl.Ka[1] = 0.0; - mtl.Ka[2] = 0.0; - mtl.Kd[0] = 1.0; - mtl.Kd[1] = 1.0; - mtl.Kd[2] = 1.0; - mtl.Ks[0] = 0.0; - mtl.Ks[1] = 0.0; - mtl.Ks[2] = 0.0; - mtl.Ke[0] = 0.0; - mtl.Ke[1] = 0.0; - mtl.Ke[2] = 0.0; - mtl.Kt[0] = 0.0; - mtl.Kt[1] = 0.0; - mtl.Kt[2] = 0.0; - mtl.Ns = 1.0; - mtl.Ni = 1.0; - mtl.Tf[0] = 1.0; - mtl.Tf[1] = 1.0; - mtl.Tf[2] = 1.0; - mtl.d = 1.0; - mtl.illum = 1; - - mtl.map_Ka = map_default(); - mtl.map_Kd = map_default(); - mtl.map_Ks = map_default(); - mtl.map_Ke = map_default(); - mtl.map_Kt = map_default(); - mtl.map_Ns = map_default(); - mtl.map_Ni = map_default(); - mtl.map_d = map_default(); - mtl.map_bump = map_default(); - - return mtl; -} - - -static -const char* parse_usemtl(fastObjData* data, const char* ptr) -{ - const char* s; - const char* e; - unsigned int idx; - fastObjMaterial* mtl; - - - ptr = skip_whitespace(ptr); - - /* Parse the material name */ - s = ptr; - while (!is_end_of_name(*ptr)) - ptr++; - - e = ptr; - - /* Find an existing material with the same name */ - idx = 0; - while (idx < array_size(data->mesh->materials)) - { - mtl = &data->mesh->materials[idx]; - if (mtl->name && string_equal(mtl->name, s, e)) - break; - - idx++; - } - - /* If doesn't exists, create a default one with this name - Note: this case happens when OBJ doesn't have its MTL */ - if (idx == array_size(data->mesh->materials)) - { - fastObjMaterial new_mtl = mtl_default(); - new_mtl.name = string_copy(s, e); - array_push(data->mesh->materials, new_mtl); - } - - data->material = idx; - - return ptr; -} - - -static -void map_clean(fastObjTexture* map) -{ - memory_dealloc(map->name); - memory_dealloc(map->path); -} - - -static -void mtl_clean(fastObjMaterial* mtl) -{ - map_clean(&mtl->map_Ka); - map_clean(&mtl->map_Kd); - map_clean(&mtl->map_Ks); - map_clean(&mtl->map_Ke); - map_clean(&mtl->map_Kt); - map_clean(&mtl->map_Ns); - map_clean(&mtl->map_Ni); - map_clean(&mtl->map_d); - map_clean(&mtl->map_bump); - - memory_dealloc(mtl->name); -} - - -static -const char* read_mtl_int(const char* p, int* v) -{ - return parse_int(p, v); -} - - -static -const char* read_mtl_single(const char* p, float* v) -{ - return parse_float(p, v); -} - - -static -const char* read_mtl_triple(const char* p, float v[3]) -{ - p = read_mtl_single(p, &v[0]); - p = read_mtl_single(p, &v[1]); - p = read_mtl_single(p, &v[2]); - - return p; -} - - -static -const char* read_map(fastObjData* data, const char* ptr, fastObjTexture* map) -{ - const char* s; - const char* e; - char* name; - char* path; - - ptr = skip_whitespace(ptr); - - /* Don't support options at present */ - if (*ptr == '-') - return ptr; - - - /* Read name */ - s = ptr; - while (!is_end_of_name(*ptr)) - ptr++; - - e = ptr; - - name = string_copy(s, e); - - path = string_concat(data->base, s, e); - string_fix_separators(path); - - map->name = name; - map->path = path; - - return e; -} - - -static -int read_mtllib(fastObjData* data, void* file, const fastObjCallbacks* callbacks, void* user_data) -{ - unsigned long n; - const char* s; - char* contents; - size_t l; - const char* p; - const char* e; - int found_d; - fastObjMaterial mtl; - - - /* Read entire file */ - n = callbacks->file_size(file, user_data); - - contents = (char*)(memory_realloc(0, n + 1)); - if (!contents) - return 0; - - l = callbacks->file_read(file, contents, n, user_data); - contents[l] = '\n'; - - mtl = mtl_default(); - - found_d = 0; - - p = contents; - e = contents + l; - while (p < e) - { - p = skip_whitespace(p); - - switch (*p) - { - case 'n': - p++; - if (p[0] == 'e' && - p[1] == 'w' && - p[2] == 'm' && - p[3] == 't' && - p[4] == 'l' && - is_whitespace(p[5])) - { - /* Push previous material (if there is one) */ - if (mtl.name) - { - array_push(data->mesh->materials, mtl); - mtl = mtl_default(); - } - - - /* Read name */ - p += 5; - - while (is_whitespace(*p)) - p++; - - s = p; - while (!is_end_of_name(*p)) - p++; - - mtl.name = string_copy(s, p); - } - break; - - case 'K': - if (p[1] == 'a') - p = read_mtl_triple(p + 2, mtl.Ka); - else if (p[1] == 'd') - p = read_mtl_triple(p + 2, mtl.Kd); - else if (p[1] == 's') - p = read_mtl_triple(p + 2, mtl.Ks); - else if (p[1] == 'e') - p = read_mtl_triple(p + 2, mtl.Ke); - else if (p[1] == 't') - p = read_mtl_triple(p + 2, mtl.Kt); - break; - - case 'N': - if (p[1] == 's') - p = read_mtl_single(p + 2, &mtl.Ns); - else if (p[1] == 'i') - p = read_mtl_single(p + 2, &mtl.Ni); - break; - - case 'T': - if (p[1] == 'r') - { - float Tr; - p = read_mtl_single(p + 2, &Tr); - if (!found_d) - { - /* Ignore Tr if we've already read d */ - mtl.d = 1.0f - Tr; - } - } - else if (p[1] == 'f') - p = read_mtl_triple(p + 2, mtl.Tf); - break; - - case 'd': - if (is_whitespace(p[1])) - { - p = read_mtl_single(p + 1, &mtl.d); - found_d = 1; - } - break; - - case 'i': - p++; - if (p[0] == 'l' && - p[1] == 'l' && - p[2] == 'u' && - p[3] == 'm' && - is_whitespace(p[4])) - { - p = read_mtl_int(p + 4, &mtl.illum); - } - break; - - case 'm': - p++; - if (p[0] == 'a' && - p[1] == 'p' && - p[2] == '_') - { - p += 3; - if (*p == 'K') - { - p++; - if (is_whitespace(p[1])) - { - if (*p == 'a') - p = read_map(data, p + 1, &mtl.map_Ka); - else if (*p == 'd') - p = read_map(data, p + 1, &mtl.map_Kd); - else if (*p == 's') - p = read_map(data, p + 1, &mtl.map_Ks); - else if (*p == 'e') - p = read_map(data, p + 1, &mtl.map_Ke); - else if (*p == 't') - p = read_map(data, p + 1, &mtl.map_Kt); - } - } - else if (*p == 'N') - { - p++; - if (is_whitespace(p[1])) - { - if (*p == 's') - p = read_map(data, p + 1, &mtl.map_Ns); - else if (*p == 'i') - p = read_map(data, p + 1, &mtl.map_Ni); - } - } - else if (*p == 'd') - { - p++; - if (is_whitespace(*p)) - p = read_map(data, p, &mtl.map_d); - } - else if ((p[0] == 'b' || p[0] == 'B') && - p[1] == 'u' && - p[2] == 'm' && - p[3] == 'p' && - is_whitespace(p[4])) - { - p = read_map(data, p + 4, &mtl.map_bump); - } - } - break; - - case '#': - break; - } - - p = skip_line(p); - } - - /* Push final material */ - if (mtl.name) - array_push(data->mesh->materials, mtl); - - memory_dealloc(contents); - - return 1; -} - - -static -const char* parse_mtllib(fastObjData* data, const char* ptr, const fastObjCallbacks* callbacks, void* user_data) -{ - const char* s; - const char* e; - char* lib; - void* file; - - - ptr = skip_whitespace(ptr); - - s = ptr; - while (!is_end_of_name(*ptr)) - ptr++; - - e = ptr; - - lib = string_concat(data->base, s, e); - if (lib) - { - string_fix_separators(lib); - - file = callbacks->file_open(lib, user_data); - if (file) - { - read_mtllib(data, file, callbacks, user_data); - callbacks->file_close(file, user_data); - } - - memory_dealloc(lib); - } - - return ptr; -} - - -static -void parse_buffer(fastObjData* data, const char* ptr, const char* end, const fastObjCallbacks* callbacks, void* user_data) -{ - const char* p; - - - p = ptr; - while (p != end) - { - p = skip_whitespace(p); - - switch (*p) - { - case 'v': - p++; - - switch (*p++) - { - case ' ': - case '\t': - p = parse_vertex(data, p); - break; - - case 't': - p = parse_texcoord(data, p); - break; - - case 'n': - p = parse_normal(data, p); - break; - - default: - p--; /* roll p++ back in case *p was a newline */ - } - break; - - case 'f': - p++; - - switch (*p++) - { - case ' ': - case '\t': - p = parse_face(data, p); - break; - - default: - p--; /* roll p++ back in case *p was a newline */ - } - break; - - case 'g': - p++; - - switch (*p++) - { - case ' ': - case '\t': - p = parse_group(data, p); - break; - - default: - p--; /* roll p++ back in case *p was a newline */ - } - break; - - case 'm': - p++; - if (p[0] == 't' && - p[1] == 'l' && - p[2] == 'l' && - p[3] == 'i' && - p[4] == 'b' && - is_whitespace(p[5])) - p = parse_mtllib(data, p + 5, callbacks, user_data); - break; - - case 'u': - p++; - if (p[0] == 's' && - p[1] == 'e' && - p[2] == 'm' && - p[3] == 't' && - p[4] == 'l' && - is_whitespace(p[5])) - p = parse_usemtl(data, p + 5); - break; - - case '#': - break; - } - - p = skip_line(p); - - data->line++; - } -} - - -void fast_obj_destroy(fastObjMesh* m) -{ - unsigned int ii; - - - for (ii = 0; ii < array_size(m->groups); ii++) - group_clean(&m->groups[ii]); - - for (ii = 0; ii < array_size(m->materials); ii++) - mtl_clean(&m->materials[ii]); - - array_clean(m->positions); - array_clean(m->texcoords); - array_clean(m->normals); - array_clean(m->face_vertices); - array_clean(m->face_materials); - array_clean(m->indices); - array_clean(m->groups); - array_clean(m->materials); - - memory_dealloc(m); -} - - -fastObjMesh* fast_obj_read(const char* path) -{ - fastObjCallbacks callbacks; - callbacks.file_open = file_open; - callbacks.file_close = file_close; - callbacks.file_read = file_read; - callbacks.file_size = file_size; - - return fast_obj_read_with_callbacks(path, &callbacks, 0); -} - - -fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data) -{ - fastObjData data; - fastObjMesh* m; - void* file; - char* buffer; - char* start; - char* end; - char* last; - fastObjUInt read; - fastObjUInt bytes; - - /* Check if callbacks are valid */ - if(!callbacks) - return 0; - - - /* Open file */ - file = callbacks->file_open(path, user_data); - if (!file) - return 0; - - - /* Empty mesh */ - m = (fastObjMesh*)(memory_realloc(0, sizeof(fastObjMesh))); - if (!m) - return 0; - - m->positions = 0; - m->texcoords = 0; - m->normals = 0; - m->face_vertices = 0; - m->face_materials = 0; - m->indices = 0; - m->materials = 0; - m->groups = 0; - - - /* Add dummy position/texcoord/normal */ - array_push(m->positions, 0.0f); - array_push(m->positions, 0.0f); - array_push(m->positions, 0.0f); - - array_push(m->texcoords, 0.0f); - array_push(m->texcoords, 0.0f); - - array_push(m->normals, 0.0f); - array_push(m->normals, 0.0f); - array_push(m->normals, 1.0f); - - - /* Data needed during parsing */ - data.mesh = m; - data.group = group_default(); - data.material = 0; - data.line = 1; - data.base = 0; - - - /* Find base path for materials/textures */ - { - const char* sep1 = strrchr(path, FAST_OBJ_SEPARATOR); - const char* sep2 = strrchr(path, FAST_OBJ_OTHER_SEP); - - /* Use the last separator in the path */ - const char* sep = sep2 && (!sep1 || sep1 < sep2) ? sep2 : sep1; - - if (sep) - data.base = string_substr(path, 0, sep - path + 1); - } - - - /* Create buffer for reading file */ - buffer = (char*)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char))); - if (!buffer) - return 0; - - start = buffer; - for (;;) - { - /* Read another buffer's worth from file */ - read = (fastObjUInt)(callbacks->file_read(file, start, BUFFER_SIZE, user_data)); - if (read == 0 && start == buffer) - break; - - - /* Ensure buffer ends in a newline */ - if (read < BUFFER_SIZE) - { - if (read == 0 || start[read - 1] != '\n') - start[read++] = '\n'; - } - - end = start + read; - if (end == buffer) - break; - - - /* Find last new line */ - last = end; - while (last > buffer) - { - last--; - if (*last == '\n') - break; - } - - - /* Check there actually is a new line */ - if (*last != '\n') - break; - - last++; - - - /* Process buffer */ - parse_buffer(&data, buffer, last, callbacks, user_data); - - - /* Copy overflow for next buffer */ - bytes = (fastObjUInt)(end - last); - memmove(buffer, last, bytes); - start = buffer + bytes; - } - - - /* Flush final group */ - flush_output(&data); - group_clean(&data.group); - - m->position_count = array_size(m->positions) / 3; - m->texcoord_count = array_size(m->texcoords) / 2; - m->normal_count = array_size(m->normals) / 3; - m->face_count = array_size(m->face_vertices); - m->material_count = array_size(m->materials); - m->group_count = array_size(m->groups); - - - /* Clean up */ - memory_dealloc(buffer); - memory_dealloc(data.base); - - callbacks->file_close(file, user_data); - - return m; -} - -#endif diff --git a/Dependencies/meshoptimizer/extern/sdefl.h b/Dependencies/meshoptimizer/extern/sdefl.h deleted file mode 100644 index af17e4a6..00000000 --- a/Dependencies/meshoptimizer/extern/sdefl.h +++ /dev/null @@ -1,696 +0,0 @@ -/* -# Small Deflate -`sdefl` is a small bare bone lossless compression library in ANSI C (ISO C90) -which implements the Deflate (RFC 1951) compressed data format specification standard. -It is mainly tuned to get as much speed and compression ratio from as little code -as needed to keep the implementation as concise as possible. - -## Features -- Portable single header and source file duo written in ANSI C (ISO C90) -- Dual license with either MIT or public domain -- Small implementation - - Deflate: 525 LoC - - Inflate: 320 LoC -- Webassembly: - - Deflate ~3.7 KB (~2.2KB compressed) - - Inflate ~3.6 KB (~2.2KB compressed) - -## Usage: -This file behaves differently depending on what symbols you define -before including it. - -Header-File mode: -If you do not define `SDEFL_IMPLEMENTATION` before including this file, it -will operate in header only mode. In this mode it declares all used structs -and the API of the library without including the implementation of the library. - -Implementation mode: -If you define `SDEFL_IMPLEMENTATION` before including this file, it will -compile the implementation . Make sure that you only include -this file implementation in *one* C or C++ file to prevent collisions. - -### Benchmark - -| Compressor name | Compression| Decompress.| Compr. size | Ratio | -| ------------------------| -----------| -----------| ----------- | ----- | -| sdefl 1.0 -0 | 127 MB/s | 233 MB/s | 40004116 | 39.88 | -| sdefl 1.0 -1 | 111 MB/s | 259 MB/s | 38940674 | 38.82 | -| sdefl 1.0 -5 | 45 MB/s | 275 MB/s | 36577183 | 36.46 | -| sdefl 1.0 -7 | 38 MB/s | 276 MB/s | 36523781 | 36.41 | -| zlib 1.2.11 -1 | 72 MB/s | 307 MB/s | 42298774 | 42.30 | -| zlib 1.2.11 -6 | 24 MB/s | 313 MB/s | 36548921 | 36.55 | -| zlib 1.2.11 -9 | 20 MB/s | 314 MB/s | 36475792 | 36.48 | -| miniz 1.0 -1 | 122 MB/s | 208 MB/s | 48510028 | 48.51 | -| miniz 1.0 -6 | 27 MB/s | 260 MB/s | 36513697 | 36.51 | -| miniz 1.0 -9 | 23 MB/s | 261 MB/s | 36460101 | 36.46 | -| libdeflate 1.3 -1 | 147 MB/s | 667 MB/s | 39597378 | 39.60 | -| libdeflate 1.3 -6 | 69 MB/s | 689 MB/s | 36648318 | 36.65 | -| libdeflate 1.3 -9 | 13 MB/s | 672 MB/s | 35197141 | 35.20 | -| libdeflate 1.3 -12 | 8.13 MB/s | 670 MB/s | 35100568 | 35.10 | - -### Compression -Results on the [Silesia compression corpus](http://sun.aei.polsl.pl/~sdeor/index.php?page=silesia): - -| File | Original | `sdefl 0` | `sdefl 5` | `sdefl 7` | -| :------ | ---------: | -----------------: | ---------: | ----------: | -| dickens | 10.192.446 | 4,260,187| 3,845,261| 3,833,657 | -| mozilla | 51.220.480 | 20,774,706 | 19,607,009 | 19,565,867 | -| mr | 9.970.564 | 3,860,531 | 3,673,460 | 3,665,627 | -| nci | 33.553.445 | 4,030,283 | 3,094,526 | 3,006,075 | -| ooffice | 6.152.192 | 3,320,063 | 3,186,373 | 3,183,815 | -| osdb | 10.085.684 | 3,919,646 | 3,649,510 | 3,649,477 | -| reymont | 6.627.202 | 2,263,378 | 1,857,588 | 1,827,237 | -| samba | 21.606.400 | 6,121,797 | 5,462,670 | 5,450,762 | -| sao | 7.251.944 | 5,612,421 | 5,485,380 | 5,481,765 | -| webster | 41.458.703 | 13,972,648 | 12,059,432 | 11,991,421 | -| xml | 5.345.280 | 886,620| 674,009 | 662,141 | -| x-ray | 8.474.240 | 6,304,655 | 6,244,779 | 6,244,779 | - -## License -``` ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2020 Micha Mettke -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -``` -*/ -#ifndef SDEFL_H_INCLUDED -#define SDEFL_H_INCLUDED - -#ifdef __cplusplus -extern "C" { -#endif - -#define SDEFL_MAX_OFF (1 << 15) -#define SDEFL_WIN_SIZ SDEFL_MAX_OFF -#define SDEFL_WIN_MSK (SDEFL_WIN_SIZ-1) - -#define SDEFL_HASH_BITS 15 -#define SDEFL_HASH_SIZ (1 << SDEFL_HASH_BITS) -#define SDEFL_HASH_MSK (SDEFL_HASH_SIZ-1) - -#define SDEFL_MIN_MATCH 4 -#define SDEFL_BLK_MAX (256*1024) -#define SDEFL_SEQ_SIZ ((SDEFL_BLK_MAX + SDEFL_MIN_MATCH)/SDEFL_MIN_MATCH) - -#define SDEFL_SYM_MAX (288) -#define SDEFL_OFF_MAX (32) -#define SDEFL_PRE_MAX (19) - -#define SDEFL_LVL_MIN 0 -#define SDEFL_LVL_DEF 5 -#define SDEFL_LVL_MAX 8 - -struct sdefl_freq { - unsigned lit[SDEFL_SYM_MAX]; - unsigned off[SDEFL_OFF_MAX]; -}; -struct sdefl_code_words { - unsigned lit[SDEFL_SYM_MAX]; - unsigned off[SDEFL_OFF_MAX]; -}; -struct sdefl_lens { - unsigned char lit[SDEFL_SYM_MAX]; - unsigned char off[SDEFL_OFF_MAX]; -}; -struct sdefl_codes { - struct sdefl_code_words word; - struct sdefl_lens len; -}; -struct sdefl_seqt { - int off, len; -}; -struct sdefl { - int bits, bitcnt; - int tbl[SDEFL_HASH_SIZ]; - int prv[SDEFL_WIN_SIZ]; - - int seq_cnt; - struct sdefl_seqt seq[SDEFL_SEQ_SIZ]; - struct sdefl_freq freq; - struct sdefl_codes cod; -}; -extern int sdefl_bound(int in_len); -extern int sdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl); -extern int zsdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl); - -#ifdef __cplusplus -} -#endif - -#endif /* SDEFL_H_INCLUDED */ - -#ifdef SDEFL_IMPLEMENTATION - -#include /* assert */ -#include /* memcpy */ -#include /* CHAR_BIT */ - -#define SDEFL_NIL (-1) -#define SDEFL_MAX_MATCH 258 -#define SDEFL_MAX_CODE_LEN (15) -#define SDEFL_SYM_BITS (10u) -#define SDEFL_SYM_MSK ((1u << SDEFL_SYM_BITS)-1u) -#define SDEFL_LIT_LEN_CODES (14) -#define SDEFL_OFF_CODES (15) -#define SDEFL_PRE_CODES (7) -#define SDEFL_CNT_NUM(n) ((((n)+3u/4u)+3u)&~3u) -#define SDEFL_EOB (256) - -#define sdefl_npow2(n) (1 << (sdefl_ilog2((n)-1) + 1)) - -static int -sdefl_ilog2(int n) { - if (!n) return 0; -#ifdef _MSC_VER - unsigned long msbp = 0; - _BitScanReverse(&msbp, (unsigned long)n); - return (int)msbp; -#elif defined(__GNUC__) || defined(__clang__) - return (int)sizeof(unsigned long) * CHAR_BIT - 1 - __builtin_clzl((unsigned long)n); -#else - #define lt(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n - static const char tbl[256] = { - 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,lt(4), lt(5), lt(5), lt(6), lt(6), lt(6), lt(6), - lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7)}; - int tt, t; - if ((tt = (n >> 16))) { - return (t = (tt >> 8)) ? 24 + tbl[t] : 16 + tbl[tt]; - } else { - return (t = (n >> 8)) ? 8 + tbl[t] : tbl[n]; - } - #undef lt -#endif -} -static unsigned -sdefl_uload32(const void *p) { - /* hopefully will be optimized to an unaligned read */ - unsigned n = 0; - memcpy(&n, p, sizeof(n)); - return n; -} -static unsigned -sdefl_hash32(const void *p) { - unsigned n = sdefl_uload32(p); - return (n * 0x9E377989) >> (32 - SDEFL_HASH_BITS); -} -static void -sdefl_put(unsigned char **dst, struct sdefl *s, int code, int bitcnt) { - s->bits |= (code << s->bitcnt); - s->bitcnt += bitcnt; - while (s->bitcnt >= 8) { - unsigned char *tar = *dst; - *tar = (unsigned char)(s->bits & 0xFF); - s->bits >>= 8; - s->bitcnt -= 8; - *dst = *dst + 1; - } -} -static void -sdefl_heap_sub(unsigned A[], unsigned len, unsigned sub) { - unsigned c, p = sub; - unsigned v = A[sub]; - while ((c = p << 1) <= len) { - if (c < len && A[c + 1] > A[c]) c++; - if (v >= A[c]) break; - A[p] = A[c], p = c; - } - A[p] = v; -} -static void -sdefl_heap_array(unsigned *A, unsigned len) { - unsigned sub; - for (sub = len >> 1; sub >= 1; sub--) - sdefl_heap_sub(A, len, sub); -} -static void -sdefl_heap_sort(unsigned *A, unsigned n) { - A--; - sdefl_heap_array(A, n); - while (n >= 2) { - unsigned tmp = A[n]; - A[n--] = A[1]; - A[1] = tmp; - sdefl_heap_sub(A, n, 1); - } -} -static unsigned -sdefl_sort_sym(unsigned sym_cnt, unsigned *freqs, - unsigned char *lens, unsigned *sym_out) { - unsigned cnts[SDEFL_CNT_NUM(SDEFL_SYM_MAX)] = {0}; - unsigned cnt_num = SDEFL_CNT_NUM(sym_cnt); - unsigned used_sym = 0; - unsigned sym, i; - for (sym = 0; sym < sym_cnt; sym++) - cnts[freqs[sym] < cnt_num-1 ? freqs[sym]: cnt_num-1]++; - for (i = 1; i < cnt_num; i++) { - unsigned cnt = cnts[i]; - cnts[i] = used_sym; - used_sym += cnt; - } - for (sym = 0; sym < sym_cnt; sym++) { - unsigned freq = freqs[sym]; - if (freq) { - unsigned idx = freq < cnt_num-1 ? freq : cnt_num-1; - sym_out[cnts[idx]++] = sym | (freq << SDEFL_SYM_BITS); - } else lens[sym] = 0; - } - sdefl_heap_sort(sym_out + cnts[cnt_num-2], cnts[cnt_num-1] - cnts[cnt_num-2]); - return used_sym; -} -static void -sdefl_build_tree(unsigned *A, unsigned sym_cnt) { - unsigned i = 0, b = 0, e = 0; - do { - unsigned m, n, freq_shift; - if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS))) - m = i++; - else m = b++; - if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS))) - n = i++; - else n = b++; - - freq_shift = (A[m] & ~SDEFL_SYM_MSK) + (A[n] & ~SDEFL_SYM_MSK); - A[m] = (A[m] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS); - A[n] = (A[n] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS); - A[e] = (A[e] & SDEFL_SYM_MSK) | freq_shift; - } while (sym_cnt - ++e > 1); -} -static void -sdefl_gen_len_cnt(unsigned *A, unsigned root, unsigned *len_cnt, - unsigned max_code_len) { - int n; - unsigned i; - for (i = 0; i <= max_code_len; i++) - len_cnt[i] = 0; - len_cnt[1] = 2; - - A[root] &= SDEFL_SYM_MSK; - for (n = (int)root - 1; n >= 0; n--) { - unsigned p = A[n] >> SDEFL_SYM_BITS; - unsigned pdepth = A[p] >> SDEFL_SYM_BITS; - unsigned depth = pdepth + 1; - unsigned len = depth; - - A[n] = (A[n] & SDEFL_SYM_MSK) | (depth << SDEFL_SYM_BITS); - if (len >= max_code_len) { - len = max_code_len; - do len--; while (!len_cnt[len]); - } - len_cnt[len]--; - len_cnt[len+1] += 2; - } -} -static void -sdefl_gen_codes(unsigned *A, unsigned char *lens, const unsigned *len_cnt, - unsigned max_code_word_len, unsigned sym_cnt) { - unsigned i, sym, len, nxt[SDEFL_MAX_CODE_LEN + 1]; - for (i = 0, len = max_code_word_len; len >= 1; len--) { - unsigned cnt = len_cnt[len]; - while (cnt--) lens[A[i++] & SDEFL_SYM_MSK] = (unsigned char)len; - } - nxt[0] = nxt[1] = 0; - for (len = 2; len <= max_code_word_len; len++) - nxt[len] = (nxt[len-1] + len_cnt[len-1]) << 1; - for (sym = 0; sym < sym_cnt; sym++) - A[sym] = nxt[lens[sym]]++; -} -static unsigned -sdefl_rev(unsigned c, unsigned char n) { - c = ((c & 0x5555) << 1) | ((c & 0xAAAA) >> 1); - c = ((c & 0x3333) << 2) | ((c & 0xCCCC) >> 2); - c = ((c & 0x0F0F) << 4) | ((c & 0xF0F0) >> 4); - c = ((c & 0x00FF) << 8) | ((c & 0xFF00) >> 8); - return c >> (16-n); -} -static void -sdefl_huff(unsigned char *lens, unsigned *codes, unsigned *freqs, - unsigned num_syms, unsigned max_code_len) { - unsigned c, *A = codes; - unsigned len_cnt[SDEFL_MAX_CODE_LEN + 1]; - unsigned used_syms = sdefl_sort_sym(num_syms, freqs, lens, A); - if (!used_syms) return; - if (used_syms == 1) { - unsigned s = A[0] & SDEFL_SYM_MSK; - unsigned i = s ? s : 1; - codes[0] = 0, lens[0] = 1; - codes[i] = 1, lens[i] = 1; - return; - } - sdefl_build_tree(A, used_syms); - sdefl_gen_len_cnt(A, used_syms-2, len_cnt, max_code_len); - sdefl_gen_codes(A, lens, len_cnt, max_code_len, num_syms); - for (c = 0; c < num_syms; c++) { - codes[c] = sdefl_rev(codes[c], lens[c]); - } -} -struct sdefl_symcnt { - int items; - int lit; - int off; -}; -static void -sdefl_precode(struct sdefl_symcnt *cnt, unsigned *freqs, unsigned *items, - const unsigned char *litlen, const unsigned char *offlen) { - unsigned *at = items; - unsigned run_start = 0; - - unsigned total = 0; - unsigned char lens[SDEFL_SYM_MAX + SDEFL_OFF_MAX]; - for (cnt->lit = SDEFL_SYM_MAX; cnt->lit > 257; cnt->lit--) - if (litlen[cnt->lit - 1]) break; - for (cnt->off = SDEFL_OFF_MAX; cnt->off > 1; cnt->off--) - if (offlen[cnt->off - 1]) break; - - total = (unsigned)(cnt->lit + cnt->off); - memcpy(lens, litlen, sizeof(unsigned char) * cnt->lit); - memcpy(lens + cnt->lit, offlen, sizeof(unsigned char) * cnt->off); - do { - unsigned len = lens[run_start]; - unsigned run_end = run_start; - do run_end++; while (run_end != total && len == lens[run_end]); - if (!len) { - while ((run_end - run_start) >= 11) { - unsigned n = (run_end - run_start) - 11; - unsigned xbits = n < 0x7f ? n : 0x7f; - freqs[18]++; - *at++ = 18u | (xbits << 5u); - run_start += 11 + xbits; - } - if ((run_end - run_start) >= 3) { - unsigned n = (run_end - run_start) - 3; - unsigned xbits = n < 0x7 ? n : 0x7; - freqs[17]++; - *at++ = 17u | (xbits << 5u); - run_start += 3 + xbits; - } - } else if ((run_end - run_start) >= 4) { - freqs[len]++; - *at++ = len; - run_start++; - do { - unsigned xbits = (run_end - run_start) - 3; - xbits = xbits < 0x03 ? xbits : 0x03; - *at++ = 16 | (xbits << 5); - run_start += 3 + xbits; - freqs[16]++; - } while ((run_end - run_start) >= 3); - } - while (run_start != run_end) { - freqs[len]++; - *at++ = len; - run_start++; - } - } while (run_start != total); - cnt->items = (int)(at - items); -} -struct sdefl_match_codest { - int ls, lc; - int dc, dx; -}; -static void -sdefl_match_codes(struct sdefl_match_codest *cod, int dist, int len) { - static const short dxmax[] = {0,6,12,24,48,96,192,384,768,1536,3072,6144,12288,24576}; - static const unsigned char lslot[258+1] = { - 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, - 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, - 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, - 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, - 27, 27, 28 - }; - cod->ls = lslot[len]; - cod->lc = 257 + cod->ls; - cod->dx = sdefl_ilog2(sdefl_npow2(dist) >> 2); - cod->dc = cod->dx ? ((cod->dx + 1) << 1) + (dist > dxmax[cod->dx]) : dist-1; -} -static void -sdefl_match(unsigned char **dst, struct sdefl *s, int dist, int len) { - static const char lxn[] = {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}; - static const short lmin[] = {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}; - static const short dmin[] = {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}; - - struct sdefl_match_codest cod; - sdefl_match_codes(&cod, dist, len); - sdefl_put(dst, s, (int)s->cod.word.lit[cod.lc], s->cod.len.lit[cod.lc]); - sdefl_put(dst, s, len - lmin[cod.ls], lxn[cod.ls]); - sdefl_put(dst, s, (int)s->cod.word.off[cod.dc], s->cod.len.off[cod.dc]); - sdefl_put(dst, s, dist - dmin[cod.dc], cod.dx); -} -static void -sdefl_flush(unsigned char **dst, struct sdefl *s, int is_last, - const unsigned char *in) { - int j, i = 0, item_cnt = 0; - struct sdefl_symcnt symcnt = {0}; - unsigned codes[SDEFL_PRE_MAX]; - unsigned char lens[SDEFL_PRE_MAX]; - unsigned freqs[SDEFL_PRE_MAX] = {0}; - unsigned items[SDEFL_SYM_MAX + SDEFL_OFF_MAX]; - static const unsigned char perm[SDEFL_PRE_MAX] = {16,17,18,0,8,7,9,6,10,5,11, - 4,12,3,13,2,14,1,15}; - - /* huffman codes */ - s->freq.lit[SDEFL_EOB]++; - sdefl_huff(s->cod.len.lit, s->cod.word.lit, s->freq.lit, SDEFL_SYM_MAX, SDEFL_LIT_LEN_CODES); - sdefl_huff(s->cod.len.off, s->cod.word.off, s->freq.off, SDEFL_OFF_MAX, SDEFL_OFF_CODES); - sdefl_precode(&symcnt, freqs, items, s->cod.len.lit, s->cod.len.off); - sdefl_huff(lens, codes, freqs, SDEFL_PRE_MAX, SDEFL_PRE_CODES); - for (item_cnt = SDEFL_PRE_MAX; item_cnt > 4; item_cnt--) { - if (lens[perm[item_cnt - 1]]) break; - } - /* block header */ - sdefl_put(dst, s, is_last ? 0x01 : 0x00, 1); /* block */ - sdefl_put(dst, s, 0x02, 2); /* dynamic huffman */ - sdefl_put(dst, s, symcnt.lit - 257, 5); - sdefl_put(dst, s, symcnt.off - 1, 5); - sdefl_put(dst, s, item_cnt - 4, 4); - for (i = 0; i < item_cnt; ++i) - sdefl_put(dst, s, lens[perm[i]], 3); - for (i = 0; i < symcnt.items; ++i) { - unsigned sym = items[i] & 0x1F; - sdefl_put(dst, s, (int)codes[sym], lens[sym]); - if (sym < 16) continue; - if (sym == 16) sdefl_put(dst, s, items[i] >> 5, 2); - else if(sym == 17) sdefl_put(dst, s, items[i] >> 5, 3); - else sdefl_put(dst, s, items[i] >> 5, 7); - } - /* block sequences */ - for (i = 0; i < s->seq_cnt; ++i) { - if (s->seq[i].off >= 0) - for (j = 0; j < s->seq[i].len; ++j) { - int c = in[s->seq[i].off + j]; - sdefl_put(dst, s, (int)s->cod.word.lit[c], s->cod.len.lit[c]); - } - else sdefl_match(dst, s, -s->seq[i].off, s->seq[i].len); - } - sdefl_put(dst, s, (int)(s)->cod.word.lit[SDEFL_EOB], (s)->cod.len.lit[SDEFL_EOB]); - memset(&s->freq, 0, sizeof(s->freq)); - s->seq_cnt = 0; -} -static void -sdefl_seq(struct sdefl *s, int off, int len) { - assert(s->seq_cnt + 2 < SDEFL_SEQ_SIZ); - s->seq[s->seq_cnt].off = off; - s->seq[s->seq_cnt].len = len; - s->seq_cnt++; -} -static void -sdefl_reg_match(struct sdefl *s, int off, int len) { - struct sdefl_match_codest cod; - sdefl_match_codes(&cod, off, len); - s->freq.lit[cod.lc]++; - s->freq.off[cod.dc]++; -} -struct sdefl_match { - int off; - int len; -}; -static void -sdefl_fnd(struct sdefl_match *m, const struct sdefl *s, - int chain_len, int max_match, const unsigned char *in, int p) { - int i = s->tbl[sdefl_hash32(&in[p])]; - int limit = ((p-SDEFL_WIN_SIZ) limit) { - if (in[i+m->len] == in[p+m->len] && - (sdefl_uload32(&in[i]) == sdefl_uload32(&in[p]))){ - int n = SDEFL_MIN_MATCH; - while (n < max_match && in[i+n] == in[p+n]) n++; - if (n > m->len) { - m->len = n, m->off = p - i; - if (n == max_match) break; - } - } - if (!(--chain_len)) break; - i = s->prv[i&SDEFL_WIN_MSK]; - } -} -static int -sdefl_compr(struct sdefl *s, unsigned char *out, const unsigned char *in, - int in_len, int lvl) { - unsigned char *q = out; - static const unsigned char pref[] = {8,10,14,24,30,48,65,96,130}; - int max_chain = (lvl < 8) ? (1 << (lvl + 1)): (1 << 13); - int n, i = 0, litlen = 0; - for (n = 0; n < SDEFL_HASH_SIZ; ++n) { - s->tbl[n] = SDEFL_NIL; - } - do {int blk_end = i + SDEFL_BLK_MAX < in_len ? i + SDEFL_BLK_MAX : in_len; - while (i < blk_end) { - struct sdefl_match m = {0}; - int max_match = ((in_len-i)>SDEFL_MAX_MATCH) ? SDEFL_MAX_MATCH:(in_len-i); - int nice_match = pref[lvl] < max_match ? pref[lvl] : max_match; - int run = 1, inc = 1, run_inc; - if (max_match > SDEFL_MIN_MATCH) { - sdefl_fnd(&m, s, max_chain, max_match, in, i); - } - if (lvl >= 5 && m.len >= SDEFL_MIN_MATCH && m.len < nice_match){ - struct sdefl_match m2 = {0}; - sdefl_fnd(&m2, s, max_chain, m.len+1, in, i+1); - m.len = (m2.len > m.len) ? 0 : m.len; - } - if (m.len >= SDEFL_MIN_MATCH) { - if (litlen) { - sdefl_seq(s, i - litlen, litlen); - litlen = 0; - } - sdefl_seq(s, -m.off, m.len); - sdefl_reg_match(s, m.off, m.len); - if (lvl < 2 && m.len >= nice_match) { - inc = m.len; - } else { - run = m.len; - } - } else { - s->freq.lit[in[i]]++; - litlen++; - } - run_inc = run * inc; - if (in_len - (i + run_inc) > SDEFL_MIN_MATCH) { - while (run-- > 0) { - unsigned h = sdefl_hash32(&in[i]); - s->prv[i&SDEFL_WIN_MSK] = s->tbl[h]; - s->tbl[h] = i, i += inc; - } - } else { - i += run_inc; - } - } - if (litlen) { - sdefl_seq(s, i - litlen, litlen); - litlen = 0; - } - sdefl_flush(&q, s, blk_end == in_len, in); - } while (i < in_len); - - if (s->bitcnt) - sdefl_put(&q, s, 0x00, 8 - s->bitcnt); - return (int)(q - out); -} -extern int -sdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) { - s->bits = s->bitcnt = 0; - return sdefl_compr(s, (unsigned char*)out, (const unsigned char*)in, n, lvl); -} -static unsigned -sdefl_adler32(unsigned adler32, const unsigned char *in, int in_len) { - #define SDEFL_ADLER_INIT (1) - const unsigned ADLER_MOD = 65521; - unsigned s1 = adler32 & 0xffff; - unsigned s2 = adler32 >> 16; - unsigned blk_len, i; - - blk_len = in_len % 5552; - while (in_len) { - for (i = 0; i + 7 < blk_len; i += 8) { - s1 += in[0]; s2 += s1; - s1 += in[1]; s2 += s1; - s1 += in[2]; s2 += s1; - s1 += in[3]; s2 += s1; - s1 += in[4]; s2 += s1; - s1 += in[5]; s2 += s1; - s1 += in[6]; s2 += s1; - s1 += in[7]; s2 += s1; - in += 8; - } - for (; i < blk_len; ++i) { - s1 += *in++, s2 += s1; - } - s1 %= ADLER_MOD; - s2 %= ADLER_MOD; - in_len -= blk_len; - blk_len = 5552; - } - return (unsigned)(s2 << 16) + (unsigned)s1; -} -extern int -zsdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) { - int p = 0; - unsigned a = 0; - unsigned char *q = (unsigned char*)out; - - s->bits = s->bitcnt = 0; - sdefl_put(&q, s, 0x78, 8); /* deflate, 32k window */ - sdefl_put(&q, s, 0x01, 8); /* fast compression */ - q += sdefl_compr(s, q, (const unsigned char*)in, n, lvl); - - /* append adler checksum */ - a = sdefl_adler32(SDEFL_ADLER_INIT, (const unsigned char*)in, n); - for (p = 0; p < 4; ++p) { - sdefl_put(&q, s, (a >> 24) & 0xFF, 8); - a <<= 8; - } - return (int)(q - (unsigned char*)out); -} -extern int -sdefl_bound(int len) { - int a = 128 + (len * 110) / 100; - int b = 128 + len + ((len / (31 * 1024)) + 1) * 5; - return (a > b) ? a : b; -} -#endif /* SDEFL_IMPLEMENTATION */ diff --git a/Dependencies/meshoptimizer/gltf/README.md b/Dependencies/meshoptimizer/gltf/README.md deleted file mode 100644 index f6de8c59..00000000 --- a/Dependencies/meshoptimizer/gltf/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# 📦 gltfpack - -gltfpack is a tool that can automatically optimize glTF files to reduce the download size and improve loading and rendering speed. - -## Installation - -You can download a pre-built binary for gltfpack on [Releases page](https://github.com/zeux/meshoptimizer/releases), or install [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended over npm since they can work with larger files, run faster, and support texture compression. - -## Usage - -To convert a glTF file using gltfpack, run the command-line binary like this on an input `.gltf`/`.glb`/`.obj` file (run it without arguments for a list of options): - -``` -gltfpack -i scene.gltf -o scene.glb -``` - -gltfpack substantially changes the glTF data by optimizing the meshes for vertex fetch and transform cache, quantizing the geometry to reduce the memory consumption and size, merging meshes to reduce the draw call count, quantizing and resampling animations to reduce animation size and simplify playback, and pruning the node tree by removing or collapsing redundant nodes. It will also simplify the meshes when requested to do so. - -By default gltfpack outputs regular `.glb`/`.gltf` files that have been optimized for GPU consumption using various cache optimizers and quantization. These files can be loaded by GLTF loaders that support `KHR_mesh_quantization` extension such as [three.js](https://threejs.org/) (r111+) and [Babylon.js](https://www.babylonjs.com/) (4.1+). - -When using `-c` option, gltfpack outputs compressed `.glb`/`.gltf` files that use meshoptimizer codecs to reduce the download size further. Loading these files requires extending GLTF loaders with support for [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md) extension; three.js supports it in r122+ (requires calling `GLTFLoader.setMeshoptDecoder`), Babylon.js supports it in 5.0+ without further setup. - -For better compression, you can use `-cc` option which applies additional compression; additionally make sure that your content delivery method is configured to use deflate (gzip) - meshoptimizer codecs are designed to produce output that can be compressed further with general purpose compressors. - -gltfpack can also compress textures using Basis Universal format stored in a KTX2 container (`-tc` flag, requires support for `KHR_texture_basisu`). Textures can also be embedded into `.bin`/`.glb` output using `-te` flag. - -## Decompression - -When using compressed files, [js/meshopt_decoder.js](https://github.com/zeux/meshoptimizer/blob/master/js/meshopt_decoder.js) or `js/meshopt_decoder.module.js` needs to be loaded to provide the WebAssembly decoder module like this: - -```js -import { MeshoptDecoder } from './meshopt_decoder.module.js'; - -... - -var loader = new GLTFLoader(); -loader.setMeshoptDecoder(MeshoptDecoder); -loader.load('pirate.glb', function (gltf) { scene.add(gltf.scene); }); -``` - -When using Three.js, this module can be imported from three.js repository from `examples/jsm/libs/meshopt_decoder.module.js`. - -Note that `meshopt_decoder` assumes that WebAssembly is supported. This is the case for all modern browsers; if support for legacy browsers such as Internet Explorer 11 is desired, it's recommended to use `-cf` flag when creating the glTF content. This will create and load fallback uncompressed buffers, but only on browsers that don't support WebAssembly. - -## Options - -By default gltfpack makes certain assumptions when optimizing the scenes, for example meshes that belong to nodes that aren't animated can be merged together, and has some defaults that represent a tradeoff between precision and size that are picked to fit most use cases. However, in some cases the resulting `.gltf` file needs to retain some way for the application to manipulate individual scene elements, and in other cases precision or size are more important to optimize for. gltfpack has a rich set of command line options to control various aspects of its behavior, with the full list available via `gltfpack -h`. - -The following settings are frequently used to reduce the resulting data size: - -* `-cc`: produce compressed gltf/glb files (requires `EXT_meshopt_compression`) -* `-tc`: convert all textures to KTX2 with BasisU supercompression (requires `KHR_texture_basisu` and may require `-tp` flag for compatibility with WebGL 1) -* `-mi`: use mesh instancing when serializing references to the same meshes (requires `EXT_mesh_gpu_instancing`) -* `-si R`: simplify meshes targeting triangle count ratio R (default: 1; R should be between 0 and 1) - -The following settings are frequently used to restrict some optimizations: - -* `-kn`: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally -* `-km`: keep named materials and disable named material merging -* `-ke`: keep extras data - -## License - -gltfpack is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). diff --git a/Dependencies/meshoptimizer/gltf/animation.cpp b/Dependencies/meshoptimizer/gltf/animation.cpp deleted file mode 100644 index a761f740..00000000 --- a/Dependencies/meshoptimizer/gltf/animation.cpp +++ /dev/null @@ -1,336 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include - -#include -#include -#include - -static float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type) -{ - switch (type) - { - case cgltf_animation_path_type_translation: - return std::max(std::max(fabsf(l.f[0] - r.f[0]), fabsf(l.f[1] - r.f[1])), fabsf(l.f[2] - r.f[2])); - - case cgltf_animation_path_type_rotation: - return acosf(std::min(1.f, fabsf(l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3]))); - - case cgltf_animation_path_type_scale: - return std::max(std::max(fabsf(l.f[0] / r.f[0] - 1), fabsf(l.f[1] / r.f[1] - 1)), fabsf(l.f[2] / r.f[2] - 1)); - - case cgltf_animation_path_type_weights: - return fabsf(l.f[0] - r.f[0]); - - default: - assert(!"Uknown animation path"); - return 0; - } -} - -static float getDeltaTolerance(cgltf_animation_path_type type) -{ - switch (type) - { - case cgltf_animation_path_type_translation: - return 0.0001f; // 0.1mm linear - - case cgltf_animation_path_type_rotation: - return 0.1f * (3.1415926f / 180.f); // 0.1 degrees - - case cgltf_animation_path_type_scale: - return 0.001f; // 0.1% ratio - - case cgltf_animation_path_type_weights: - return 0.001f; // 0.1% linear - - default: - assert(!"Uknown animation path"); - return 0; - } -} - -static Attr interpolateLinear(const Attr& l, const Attr& r, float t, cgltf_animation_path_type type) -{ - if (type == cgltf_animation_path_type_rotation) - { - // Approximating slerp, https://zeux.io/2015/07/23/approximating-slerp/ - // We also handle quaternion double-cover - float ca = l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3]; - - float d = fabsf(ca); - float A = 1.0904f + d * (-3.2452f + d * (3.55645f - d * 1.43519f)); - float B = 0.848013f + d * (-1.06021f + d * 0.215638f); - float k = A * (t - 0.5f) * (t - 0.5f) + B; - float ot = t + t * (t - 0.5f) * (t - 1) * k; - - float t0 = 1 - ot; - float t1 = ca > 0 ? ot : -ot; - - Attr lerp = {{ - l.f[0] * t0 + r.f[0] * t1, - l.f[1] * t0 + r.f[1] * t1, - l.f[2] * t0 + r.f[2] * t1, - l.f[3] * t0 + r.f[3] * t1, - }}; - - float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]); - - if (len > 0.f) - { - lerp.f[0] /= len; - lerp.f[1] /= len; - lerp.f[2] /= len; - lerp.f[3] /= len; - } - - return lerp; - } - else - { - Attr lerp = {{ - l.f[0] * (1 - t) + r.f[0] * t, - l.f[1] * (1 - t) + r.f[1] * t, - l.f[2] * (1 - t) + r.f[2] * t, - l.f[3] * (1 - t) + r.f[3] * t, - }}; - - return lerp; - } -} - -static Attr interpolateHermite(const Attr& v0, const Attr& t0, const Attr& v1, const Attr& t1, float t, float dt, cgltf_animation_path_type type) -{ - float s0 = 1 + t * t * (2 * t - 3); - float s1 = t + t * t * (t - 2); - float s2 = 1 - s0; - float s3 = t * t * (t - 1); - - float ts1 = dt * s1; - float ts3 = dt * s3; - - Attr lerp = {{ - s0 * v0.f[0] + ts1 * t0.f[0] + s2 * v1.f[0] + ts3 * t1.f[0], - s0 * v0.f[1] + ts1 * t0.f[1] + s2 * v1.f[1] + ts3 * t1.f[1], - s0 * v0.f[2] + ts1 * t0.f[2] + s2 * v1.f[2] + ts3 * t1.f[2], - s0 * v0.f[3] + ts1 * t0.f[3] + s2 * v1.f[3] + ts3 * t1.f[3], - }}; - - if (type == cgltf_animation_path_type_rotation) - { - float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]); - - if (len > 0.f) - { - lerp.f[0] /= len; - lerp.f[1] /= len; - lerp.f[2] /= len; - lerp.f[3] /= len; - } - } - - return lerp; -} - -static void resampleKeyframes(std::vector& data, const std::vector& input, const std::vector& output, cgltf_animation_path_type type, cgltf_interpolation_type interpolation, size_t components, int frames, float mint, int freq) -{ - size_t cursor = 0; - - for (int i = 0; i < frames; ++i) - { - float time = mint + float(i) / freq; - - while (cursor + 1 < input.size()) - { - float next_time = input[cursor + 1]; - - if (next_time > time) - break; - - cursor++; - } - - if (cursor + 1 < input.size()) - { - float cursor_time = input[cursor + 0]; - float next_time = input[cursor + 1]; - - float range = next_time - cursor_time; - float inv_range = (range == 0.f) ? 0.f : 1.f / (next_time - cursor_time); - float t = std::max(0.f, std::min(1.f, (time - cursor_time) * inv_range)); - - for (size_t j = 0; j < components; ++j) - { - switch (interpolation) - { - case cgltf_interpolation_type_linear: - { - const Attr& v0 = output[(cursor + 0) * components + j]; - const Attr& v1 = output[(cursor + 1) * components + j]; - data.push_back(interpolateLinear(v0, v1, t, type)); - } - break; - - case cgltf_interpolation_type_step: - { - const Attr& v = output[cursor * components + j]; - data.push_back(v); - } - break; - - case cgltf_interpolation_type_cubic_spline: - { - const Attr& v0 = output[(cursor * 3 + 1) * components + j]; - const Attr& b0 = output[(cursor * 3 + 2) * components + j]; - const Attr& a1 = output[(cursor * 3 + 3) * components + j]; - const Attr& v1 = output[(cursor * 3 + 4) * components + j]; - data.push_back(interpolateHermite(v0, b0, v1, a1, t, range, type)); - } - break; - - default: - assert(!"Unknown interpolation type"); - } - } - } - else - { - size_t offset = (interpolation == cgltf_interpolation_type_cubic_spline) ? cursor * 3 + 1 : cursor; - - for (size_t j = 0; j < components; ++j) - { - const Attr& v = output[offset * components + j]; - data.push_back(v); - } - } - } -} - -static float getMaxDelta(const std::vector& data, cgltf_animation_path_type type, int frames, const Attr* value, size_t components) -{ - assert(data.size() == frames * components); - - float result = 0; - - for (int i = 0; i < frames; ++i) - { - for (size_t j = 0; j < components; ++j) - { - float delta = getDelta(value[j], data[i * components + j], type); - - result = (result < delta) ? delta : result; - } - } - - return result; -} - -static void getBaseTransform(Attr* result, size_t components, cgltf_animation_path_type type, cgltf_node* node) -{ - switch (type) - { - case cgltf_animation_path_type_translation: - memcpy(result->f, node->translation, 3 * sizeof(float)); - break; - - case cgltf_animation_path_type_rotation: - memcpy(result->f, node->rotation, 4 * sizeof(float)); - break; - - case cgltf_animation_path_type_scale: - memcpy(result->f, node->scale, 3 * sizeof(float)); - break; - - case cgltf_animation_path_type_weights: - if (node->weights_count) - { - assert(node->weights_count == components); - memcpy(result->f, node->weights, components * sizeof(float)); - } - else if (node->mesh && node->mesh->weights_count) - { - assert(node->mesh->weights_count == components); - memcpy(result->f, node->mesh->weights, components * sizeof(float)); - } - break; - - default: - assert(!"Unknown animation path"); - } -} - -static float getWorldScale(cgltf_node* node) -{ - float transform[16]; - cgltf_node_transform_world(node, transform); - - // 3x3 determinant computes scale^3 - float a0 = transform[5] * transform[10] - transform[6] * transform[9]; - float a1 = transform[4] * transform[10] - transform[6] * transform[8]; - float a2 = transform[4] * transform[9] - transform[5] * transform[8]; - float det = transform[0] * a0 - transform[1] * a1 + transform[2] * a2; - - return powf(fabsf(det), 1.f / 3.f); -} - -void processAnimation(Animation& animation, const Settings& settings) -{ - float mint = FLT_MAX, maxt = 0; - - for (size_t i = 0; i < animation.tracks.size(); ++i) - { - const Track& track = animation.tracks[i]; - assert(!track.time.empty()); - - mint = std::min(mint, track.time.front()); - maxt = std::max(maxt, track.time.back()); - } - - mint = std::min(mint, maxt); - - // round the number of frames to nearest but favor the "up" direction - // this means that at 10 Hz resampling, we will try to preserve the last frame <10ms - // but if the last frame is <2ms we favor just removing this data - int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f); - - animation.start = mint; - animation.frames = frames; - - std::vector base; - - for (size_t i = 0; i < animation.tracks.size(); ++i) - { - Track& track = animation.tracks[i]; - - std::vector result; - resampleKeyframes(result, track.time, track.data, track.path, track.interpolation, track.components, frames, mint, settings.anim_freq); - - track.time.clear(); - track.data.swap(result); - - float tolerance = getDeltaTolerance(track.path); - - // translation tracks use world space tolerance; in the future, we should compute all errors as linear using hierarchy - if (track.node && track.path == cgltf_animation_path_type_translation) - { - float scale = getWorldScale(track.node); - tolerance /= scale == 0.f ? 1.f : scale; - } - - float deviation = getMaxDelta(track.data, track.path, frames, &track.data[0], track.components); - - if (deviation <= tolerance) - { - // track is constant (equal to first keyframe), we only need the first keyframe - track.constant = true; - track.data.resize(track.components); - - // track.dummy is true iff track redundantly sets up the value to be equal to default node transform - base.resize(track.components); - getBaseTransform(&base[0], track.components, track.path, track.node); - - track.dummy = getMaxDelta(track.data, track.path, 1, &base[0], track.components) <= tolerance; - } - } -} diff --git a/Dependencies/meshoptimizer/gltf/basisenc.cpp b/Dependencies/meshoptimizer/gltf/basisenc.cpp deleted file mode 100644 index dbaf8f24..00000000 --- a/Dependencies/meshoptimizer/gltf/basisenc.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#ifdef WITH_BASISU - -#define BASISU_NO_ITERATOR_DEBUG_LEVEL - -#ifdef __clang__ -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#endif - -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#pragma GCC diagnostic ignored "-Wunused-value" -#endif - -#include "encoder/basisu_comp.h" - -#include "gltfpack.h" - -struct BasisSettings -{ - int etc1s_l; - int etc1s_q; - int uastc_l; - float uastc_q; -}; - -static const BasisSettings kBasisSettings[10] = { - {1, 1, 0, 1.5f}, - {1, 6, 0, 1.f}, - {1, 20, 1, 1.0f}, - {1, 50, 1, 0.75f}, - {1, 90, 1, 0.5f}, - {1, 128, 1, 0.4f}, - {1, 160, 1, 0.34f}, - {1, 192, 1, 0.29f}, // default - {1, 224, 2, 0.26f}, - {1, 255, 2, 0.f}, -}; - -static std::unique_ptr gJobPool; - -void encodeInit(int jobs) -{ - using namespace basisu; - - basisu_encoder_init(); - - uint32_t num_threads = jobs == 0 ? std::thread::hardware_concurrency() : jobs; - - gJobPool.reset(new job_pool(num_threads)); -} - -static bool encodeInternal(const char* input, const char* output, bool yflip, bool normal_map, bool linear, bool uastc, int uastc_l, float uastc_q, int etc1s_l, int etc1s_q, int zstd_l, int width, int height) -{ - using namespace basisu; - - basis_compressor_params params; - - params.m_multithreading = gJobPool->get_total_threads() > 1; - params.m_pJob_pool = gJobPool.get(); - - if (uastc) - { - static const uint32_t s_level_flags[TOTAL_PACK_UASTC_LEVELS] = {cPackUASTCLevelFastest, cPackUASTCLevelFaster, cPackUASTCLevelDefault, cPackUASTCLevelSlower, cPackUASTCLevelVerySlow}; - - params.m_uastc = true; - - params.m_pack_uastc_flags &= ~cPackUASTCLevelMask; - params.m_pack_uastc_flags |= s_level_flags[uastc_l]; - - params.m_rdo_uastc = uastc_q > 0; - params.m_rdo_uastc_quality_scalar = uastc_q; - params.m_rdo_uastc_dict_size = 1024; - } - else - { - params.m_compression_level = etc1s_l; - params.m_quality_level = etc1s_q; - params.m_max_endpoint_clusters = 0; - params.m_max_selector_clusters = 0; - - params.m_no_selector_rdo = normal_map; - params.m_no_endpoint_rdo = normal_map; - } - - params.m_perceptual = !linear; - - params.m_mip_gen = true; - params.m_mip_srgb = !linear; - - params.m_resample_width = width; - params.m_resample_height = height; - - params.m_y_flip = yflip; - - params.m_create_ktx2_file = true; - params.m_ktx2_srgb_transfer_func = !linear; - - if (zstd_l) - { - params.m_ktx2_uastc_supercompression = basist::KTX2_SS_ZSTANDARD; - params.m_ktx2_zstd_supercompression_level = zstd_l; - } - - params.m_read_source_images = true; - params.m_write_output_basis_files = true; - - params.m_source_filenames.resize(1); - params.m_source_filenames[0] = input; - - params.m_out_filename = output; - - params.m_status_output = false; - - basis_compressor c; - - if (!c.init(params)) - return false; - - return c.process() == basis_compressor::cECSuccess; -} - -static bool encodeImage(const std::string& data, const char* mime_type, std::string& result, const ImageInfo& info, const Settings& settings) -{ - TempFile temp_input(mimeExtension(mime_type)); - TempFile temp_output(".ktx2"); - - if (!writeFile(temp_input.path.c_str(), data)) - return false; - - int quality = settings.texture_quality[info.kind]; - bool uastc = settings.texture_uastc[info.kind]; - - const BasisSettings& bs = kBasisSettings[quality - 1]; - - int width = 0, height = 0; - if (!getDimensions(data, mime_type, width, height)) - return false; - - adjustDimensions(width, height, settings); - - int zstd = uastc ? 9 : 0; - - bool ok = encodeInternal(temp_input.path.c_str(), temp_output.path.c_str(), settings.texture_flipy, info.normal_map, !info.srgb, uastc, bs.uastc_l, bs.uastc_q, bs.etc1s_l, bs.etc1s_q, zstd, width, height); - - return ok && readFile(temp_output.path.c_str(), result); -} - -void encodeImages(std::string* encoded, const cgltf_data* data, const std::vector& images, const char* input_path, const Settings& settings) -{ - assert(gJobPool); - - for (size_t i = 0; i < data->images_count; ++i) - { - const cgltf_image& image = data->images[i]; - ImageInfo info = images[i]; - - encoded[i].clear(); - - gJobPool->add_job([=]() { - std::string img_data; - std::string mime_type; - std::string result; - - if (readImage(image, input_path, img_data, mime_type) && encodeImage(img_data, mime_type.c_str(), result, info, settings)) - { - encoded[i].swap(result); - } - }, nullptr); // explicitly pass token to make sure we're using thread-safe job_pool implementation - } - - gJobPool->wait_for_all(); -} -#endif diff --git a/Dependencies/meshoptimizer/gltf/basislib.cpp b/Dependencies/meshoptimizer/gltf/basislib.cpp deleted file mode 100644 index 505729c2..00000000 --- a/Dependencies/meshoptimizer/gltf/basislib.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifdef WITH_BASISU - -#ifdef __clang__ -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#pragma GCC diagnostic ignored "-Wuninitialized-const-reference" -#endif - -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#pragma GCC diagnostic ignored "-Wdeprecated-copy" -#pragma GCC diagnostic ignored "-Wextra" -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#pragma GCC diagnostic ignored "-Wmisleading-indentation" -#pragma GCC diagnostic ignored "-Wparentheses" -#pragma GCC diagnostic ignored "-Wshadow" -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-value" -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#pragma GCC diagnostic ignored "-Wstrict-aliasing" // TODO: https://github.com/BinomialLLC/basis_universal/pull/275 -#pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif - -#ifdef _MSC_VER -#pragma warning(disable : 4702) // unreachable code -#pragma warning(disable : 4005) // macro redefinition -#endif - -#define BASISU_NO_ITERATOR_DEBUG_LEVEL - -#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_X64)) -#define BASISU_SUPPORT_SSE 1 -#endif - -#if defined(__SSE4_1__) -#define BASISU_SUPPORT_SSE 1 -#endif - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX -#endif - -#include "encoder/basisu_backend.cpp" -#include "encoder/basisu_basis_file.cpp" -#include "encoder/basisu_bc7enc.cpp" -#include "encoder/basisu_comp.cpp" -#include "encoder/basisu_enc.cpp" -#include "encoder/basisu_etc.cpp" -#include "encoder/basisu_frontend.cpp" -#include "encoder/basisu_gpu_texture.cpp" -#include "encoder/basisu_kernels_sse.cpp" -#include "encoder/basisu_opencl.cpp" -#include "encoder/basisu_pvrtc1_4.cpp" -#include "encoder/basisu_resample_filters.cpp" -#include "encoder/basisu_resampler.cpp" -#include "encoder/basisu_ssim.cpp" -#include "encoder/basisu_uastc_enc.cpp" -#include "encoder/jpgd.cpp" -#include "encoder/pvpngreader.cpp" -#include "transcoder/basisu_transcoder.cpp" - -#undef CLAMP -#include "zstd/zstd.c" - -#endif diff --git a/Dependencies/meshoptimizer/gltf/cli.js b/Dependencies/meshoptimizer/gltf/cli.js deleted file mode 100644 index a7bab554..00000000 --- a/Dependencies/meshoptimizer/gltf/cli.js +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node -// This file is part of gltfpack and is distributed under the terms of MIT License. -var gltfpack = require('./library.js'); - -var fs = require('fs'); - -var args = process.argv.slice(2); - -var interface = { - read: function (path) { - return fs.readFileSync(path); - }, - write: function (path, data) { - fs.writeFileSync(path, data); - }, -}; - -gltfpack.pack(args, interface) - .then(function (log) { - process.stdout.write(log); - process.exit(0); - }) - .catch(function (err) { - process.stderr.write(err.message); - process.exit(1); - }); diff --git a/Dependencies/meshoptimizer/gltf/fileio.cpp b/Dependencies/meshoptimizer/gltf/fileio.cpp deleted file mode 100644 index ec09552e..00000000 --- a/Dependencies/meshoptimizer/gltf/fileio.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include -#include -#include - -#ifdef _WIN32 -#include -#else -#include -#endif - -TempFile::TempFile(const char* suffix) - : fd(-1) -{ -#if defined(_WIN32) - const char* temp_dir = getenv("TEMP"); - path = temp_dir ? temp_dir : "."; - path += "\\gltfpack-XXXXXX"; - (void)_mktemp(&path[0]); - path += suffix; -#elif defined(__wasi__) - static int id = 0; - char ids[16]; - sprintf(ids, "%d", id++); - - path = "gltfpack-temp-"; - path += ids; - path += suffix; -#else - path = "/tmp/gltfpack-XXXXXX"; - path += suffix; - fd = mkstemps(&path[0], strlen(suffix)); -#endif -} - -TempFile::~TempFile() -{ - remove(path.c_str()); - -#ifndef _WIN32 - close(fd); -#endif -} - -std::string getFullPath(const char* path, const char* base_path) -{ - std::string result = base_path; - - std::string::size_type slash = result.find_last_of("/\\"); - result.erase(slash == std::string::npos ? 0 : slash + 1); - - result += path; - - return result; -} - -std::string getFileName(const char* path) -{ - std::string result = path; - - std::string::size_type slash = result.find_last_of("/\\"); - if (slash != std::string::npos) - result.erase(0, slash + 1); - - std::string::size_type dot = result.find_last_of('.'); - if (dot != std::string::npos) - result.erase(dot); - - return result; -} - -std::string getExtension(const char* path) -{ - std::string result = path; - - std::string::size_type slash = result.find_last_of("/\\"); - std::string::size_type dot = result.find_last_of('.'); - - if (slash != std::string::npos && dot != std::string::npos && dot < slash) - dot = std::string::npos; - - result.erase(0, dot); - - for (size_t i = 0; i < result.length(); ++i) - if (unsigned(result[i] - 'A') < 26) - result[i] = (result[i] - 'A') + 'a'; - - return result; -} - -bool readFile(const char* path, std::string& data) -{ - FILE* file = fopen(path, "rb"); - if (!file) - return false; - - fseek(file, 0, SEEK_END); - long length = ftell(file); - fseek(file, 0, SEEK_SET); - - if (length <= 0) - { - fclose(file); - return false; - } - - data.resize(length); - size_t result = fread(&data[0], 1, data.size(), file); - int rc = fclose(file); - - return rc == 0 && result == data.size(); -} - -bool writeFile(const char* path, const std::string& data) -{ - FILE* file = fopen(path, "wb"); - if (!file) - return false; - - size_t result = fwrite(&data[0], 1, data.size(), file); - int rc = fclose(file); - - return rc == 0 && result == data.size(); -} diff --git a/Dependencies/meshoptimizer/gltf/gltfpack.cpp b/Dependencies/meshoptimizer/gltf/gltfpack.cpp deleted file mode 100644 index c8a4c6a1..00000000 --- a/Dependencies/meshoptimizer/gltf/gltfpack.cpp +++ /dev/null @@ -1,1497 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include - -#include -#include -#include -#include -#include - -#ifdef __wasi__ -#include -#endif - -#include "../src/meshoptimizer.h" - -std::string getVersion() -{ - char result[32]; - sprintf(result, "%d.%d", MESHOPTIMIZER_VERSION / 1000, (MESHOPTIMIZER_VERSION % 1000) / 10); - return result; -} - -static void finalizeBufferViews(std::string& json, std::vector& views, std::string& bin, std::string* fallback, size_t& fallback_size) -{ - for (size_t i = 0; i < views.size(); ++i) - { - BufferView& view = views[i]; - - size_t bin_offset = bin.size(); - size_t fallback_offset = fallback_size; - - size_t count = view.data.size() / view.stride; - - if (view.compression == BufferView::Compression_None) - { - bin += view.data; - } - else - { - switch (view.compression) - { - case BufferView::Compression_Attribute: - compressVertexStream(bin, view.data, count, view.stride); - break; - case BufferView::Compression_Index: - compressIndexStream(bin, view.data, count, view.stride); - break; - case BufferView::Compression_IndexSequence: - compressIndexSequence(bin, view.data, count, view.stride); - break; - default: - assert(!"Unknown compression type"); - } - - if (fallback) - *fallback += view.data; - fallback_size += view.data.size(); - } - - size_t raw_offset = (view.compression != BufferView::Compression_None) ? fallback_offset : bin_offset; - - comma(json); - writeBufferView(json, view.kind, view.filter, count, view.stride, raw_offset, view.data.size(), view.compression, bin_offset, bin.size() - bin_offset); - - // record written bytes for statistics - view.bytes = bin.size() - bin_offset; - - // align each bufferView by 4 bytes - bin.resize((bin.size() + 3) & ~3); - if (fallback) - fallback->resize((fallback->size() + 3) & ~3); - fallback_size = (fallback_size + 3) & ~3; - } -} - -static void printMeshStats(const std::vector& meshes, const char* name) -{ - size_t mesh_triangles = 0; - size_t mesh_vertices = 0; - size_t total_triangles = 0; - size_t total_instances = 0; - size_t total_draws = 0; - - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - mesh_triangles += mesh.indices.size() / 3; - mesh_vertices += mesh.streams.empty() ? 0 : mesh.streams[0].data.size(); - - size_t instances = std::max(size_t(1), mesh.nodes.size() + mesh.instances.size()); - - total_triangles += mesh.indices.size() / 3 * instances; - total_instances += instances; - total_draws += std::max(size_t(1), mesh.nodes.size()); - } - - printf("%s: %d mesh primitives (%d triangles, %d vertices); %d draw calls (%d instances, %lld triangles)\n", name, - int(meshes.size()), int(mesh_triangles), int(mesh_vertices), - int(total_draws), int(total_instances), (long long)total_triangles); -} - -static void printSceneStats(const std::vector& views, const std::vector& meshes, size_t node_offset, size_t mesh_offset, size_t material_offset, size_t json_size, size_t bin_size) -{ - size_t bytes[BufferView::Kind_Count] = {}; - - for (size_t i = 0; i < views.size(); ++i) - { - const BufferView& view = views[i]; - bytes[view.kind] += view.bytes; - } - - printf("output: %d nodes, %d meshes (%d primitives), %d materials\n", int(node_offset), int(mesh_offset), int(meshes.size()), int(material_offset)); - printf("output: JSON %d bytes, buffers %d bytes\n", int(json_size), int(bin_size)); - printf("output: buffers: vertex %d bytes, index %d bytes, skin %d bytes, time %d bytes, keyframe %d bytes, instance %d bytes, image %d bytes\n", - int(bytes[BufferView::Kind_Vertex]), int(bytes[BufferView::Kind_Index]), int(bytes[BufferView::Kind_Skin]), - int(bytes[BufferView::Kind_Time]), int(bytes[BufferView::Kind_Keyframe]), int(bytes[BufferView::Kind_Instance]), - int(bytes[BufferView::Kind_Image])); -} - -static void printAttributeStats(const std::vector& views, BufferView::Kind kind, const char* name) -{ - for (size_t i = 0; i < views.size(); ++i) - { - const BufferView& view = views[i]; - - if (view.kind != kind) - continue; - - const char* variant = "unknown"; - - switch (kind) - { - case BufferView::Kind_Vertex: - variant = attributeType(cgltf_attribute_type(view.variant)); - break; - - case BufferView::Kind_Index: - variant = "index"; - break; - - case BufferView::Kind_Keyframe: - case BufferView::Kind_Instance: - variant = animationPath(cgltf_animation_path_type(view.variant)); - break; - - default:; - } - - size_t count = view.data.size() / view.stride; - - printf("stats: %s %s: compressed %d bytes (%.1f bits), raw %d bytes (%d bits)\n", - name, variant, - int(view.bytes), double(view.bytes) / double(count) * 8, - int(view.data.size()), int(view.stride * 8)); - } -} - -static void printImageStats(const std::vector& views, TextureKind kind, const char* name) -{ - size_t bytes = 0; - size_t count = 0; - - for (size_t i = 0; i < views.size(); ++i) - { - const BufferView& view = views[i]; - - if (view.kind != BufferView::Kind_Image) - continue; - - if (view.variant != -1 - kind) - continue; - - count += 1; - bytes += view.data.size(); - } - - if (count) - printf("stats: image %s: %d bytes in %d images\n", name, int(bytes), int(count)); -} - -static bool printReport(const char* path, cgltf_data* data, const std::vector& views, const std::vector& meshes, size_t node_count, size_t mesh_count, size_t material_count, size_t animation_count, size_t json_size, size_t bin_size) -{ - size_t bytes[BufferView::Kind_Count] = {}; - - for (size_t i = 0; i < views.size(); ++i) - { - const BufferView& view = views[i]; - bytes[view.kind] += view.bytes; - } - - size_t total_triangles = 0; - size_t total_instances = 0; - size_t total_draws = 0; - - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - size_t instances = std::max(size_t(1), mesh.nodes.size() + mesh.instances.size()); - - total_triangles += mesh.indices.size() / 3 * instances; - total_instances += instances; - total_draws += std::max(size_t(1), mesh.nodes.size()); - } - - FILE* out = fopen(path, "wb"); - if (!out) - return false; - - fprintf(out, "{\n"); - fprintf(out, "\t\"generator\": \"gltfpack %s\",\n", getVersion().c_str()); - fprintf(out, "\t\"scene\": {\n"); - fprintf(out, "\t\t\"nodeCount\": %d,\n", int(node_count)); - fprintf(out, "\t\t\"meshCount\": %d,\n", int(mesh_count)); - fprintf(out, "\t\t\"materialCount\": %d,\n", int(material_count)); - fprintf(out, "\t\t\"textureCount\": %d,\n", int(data->textures_count)); - fprintf(out, "\t\t\"animationCount\": %d\n", int(animation_count)); - fprintf(out, "\t},\n"); - fprintf(out, "\t\"render\": {\n"); - fprintf(out, "\t\t\"drawCount\": %d,\n", int(total_draws)); - fprintf(out, "\t\t\"instanceCount\": %d,\n", int(total_instances)); - fprintf(out, "\t\t\"triangleCount\": %lld\n", (long long)total_triangles); - fprintf(out, "\t},\n"); - fprintf(out, "\t\"data\": {\n"); - fprintf(out, "\t\t\"json\": %d,\n", int(json_size)); - fprintf(out, "\t\t\"binary\": %d,\n", int(bin_size)); - fprintf(out, "\t\t\"buffers\": {\n"); - fprintf(out, "\t\t\t\"vertex\": %d,\n", int(bytes[BufferView::Kind_Vertex])); - fprintf(out, "\t\t\t\"index\": %d,\n", int(bytes[BufferView::Kind_Index])); - fprintf(out, "\t\t\t\"animation\": %d,\n", int(bytes[BufferView::Kind_Time] + bytes[BufferView::Kind_Keyframe])); - fprintf(out, "\t\t\t\"transform\": %d,\n", int(bytes[BufferView::Kind_Skin] + bytes[BufferView::Kind_Instance])); - fprintf(out, "\t\t\t\"image\": %d\n", int(bytes[BufferView::Kind_Image])); - fprintf(out, "\t\t}\n"); - fprintf(out, "\t}\n"); - fprintf(out, "}\n"); - - int rc = fclose(out); - return rc == 0; -} - -static bool canTransformMesh(const Mesh& mesh) -{ - // volume thickness is specified in mesh coordinate space; to avoid modifying materials we prohibit transforming meshes with volume materials - if (mesh.material && mesh.material->has_volume && mesh.material->volume.thickness_factor > 0.f) - return false; - - return true; -} - -static void process(cgltf_data* data, const char* input_path, const char* output_path, const char* report_path, std::vector& meshes, std::vector& animations, const std::string& extras, const Settings& settings, std::string& json, std::string& bin, std::string& fallback, size_t& fallback_size) -{ - if (settings.verbose) - { - printf("input: %d nodes, %d meshes (%d primitives), %d materials, %d skins, %d animations\n", - int(data->nodes_count), int(data->meshes_count), int(meshes.size()), int(data->materials_count), int(data->skins_count), int(animations.size())); - printMeshStats(meshes, "input"); - } - - for (size_t i = 0; i < animations.size(); ++i) - { - processAnimation(animations[i], settings); - } - - std::vector nodes(data->nodes_count); - - markScenes(data, nodes); - markAnimated(data, nodes, animations); - - for (size_t i = 0; i < meshes.size(); ++i) - { - Mesh& mesh = meshes[i]; - assert(mesh.instances.empty()); - - // mesh is already world space, skip - if (mesh.nodes.empty()) - continue; - - // note: when -kn is specified, we keep mesh-node attachment so that named nodes can be transformed - if (settings.keep_nodes) - continue; - - // we keep skinned meshes or meshes with morph targets as is - // in theory we could transform both, but in practice transforming morph target meshes is more involved, - // and reparenting skinned meshes leads to incorrect bounding box generated in three.js - if (mesh.skin || mesh.targets) - continue; - - bool any_animated = false; - for (size_t j = 0; j < mesh.nodes.size(); ++j) - any_animated |= nodes[mesh.nodes[j] - data->nodes].animated; - - // animated meshes will be anchored to the same node that they used to be in to retain the animation - if (any_animated) - continue; - - int scene = nodes[mesh.nodes[0] - data->nodes].scene; - bool any_other_scene = false; - for (size_t j = 0; j < mesh.nodes.size(); ++j) - any_other_scene |= scene != nodes[mesh.nodes[j] - data->nodes].scene; - - // we only merge instances when all nodes have a single consistent scene - if (scene < 0 || any_other_scene) - continue; - - // we only merge multiple instances together if requested - // this often makes the scenes faster to render by reducing the draw call count, but can result in larger files - if (mesh.nodes.size() > 1 && !settings.mesh_merge && !settings.mesh_instancing) - continue; - - // prefer instancing if possible, use merging otherwise - if (mesh.nodes.size() > 1 && settings.mesh_instancing) - { - mesh.instances.resize(mesh.nodes.size()); - - for (size_t j = 0; j < mesh.nodes.size(); ++j) - cgltf_node_transform_world(mesh.nodes[j], mesh.instances[j].data); - - mesh.nodes.clear(); - mesh.scene = scene; - } - else if (canTransformMesh(mesh)) - { - mergeMeshInstances(mesh); - - assert(mesh.nodes.empty()); - mesh.scene = scene; - } - } - - // material information is required for mesh and image processing - std::vector materials(data->materials_count); - std::vector images(data->images_count); - - analyzeMaterials(data, materials, images); - - optimizeMaterials(data, input_path, images); - - // streams need to be filtered before mesh merging (or processing) to make sure we can merge meshes with redundant streams - for (size_t i = 0; i < meshes.size(); ++i) - { - Mesh& mesh = meshes[i]; - MaterialInfo mi = mesh.material ? materials[mesh.material - data->materials] : MaterialInfo(); - - // merge material requirements across all variants - for (size_t j = 0; j < mesh.variants.size(); ++j) - { - MaterialInfo vi = materials[mesh.variants[j].material - data->materials]; - - mi.needsTangents |= vi.needsTangents; - mi.textureSetMask |= vi.textureSetMask; - } - - filterStreams(mesh, mi); - } - - mergeMeshMaterials(data, meshes, settings); - mergeMeshes(meshes, settings); - filterEmptyMeshes(meshes); - - markNeededNodes(data, nodes, meshes, animations, settings); - markNeededMaterials(data, materials, meshes, settings); - -#ifndef NDEBUG - std::vector debug_meshes; - - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - if (settings.simplify_debug > 0) - { - Mesh kinds = {}; - Mesh loops = {}; - debugSimplify(mesh, kinds, loops, settings.simplify_debug); - debug_meshes.push_back(kinds); - debug_meshes.push_back(loops); - } - - if (settings.meshlet_debug > 0) - { - Mesh meshlets = {}; - Mesh bounds = {}; - debugMeshlets(mesh, meshlets, bounds, settings.meshlet_debug, /* scan= */ false); - debug_meshes.push_back(meshlets); - debug_meshes.push_back(bounds); - } - } -#endif - - for (size_t i = 0; i < meshes.size(); ++i) - { - processMesh(meshes[i], settings); - } - -#ifndef NDEBUG - meshes.insert(meshes.end(), debug_meshes.begin(), debug_meshes.end()); -#endif - - filterEmptyMeshes(meshes); // some meshes may become empty after processing - - QuantizationPosition qp = prepareQuantizationPosition(meshes, settings); - - std::vector qt_materials(materials.size()); - std::vector qt_meshes(meshes.size(), size_t(-1)); - prepareQuantizationTexture(data, qt_materials, qt_meshes, meshes, settings); - - QuantizationTexture qt_dummy = {}; - qt_dummy.bits = settings.tex_bits; - - std::string json_images; - std::string json_samplers; - std::string json_textures; - std::string json_materials; - std::string json_accessors; - std::string json_meshes; - std::string json_nodes; - std::string json_skins; - std::vector json_roots(data->scenes_count); - std::string json_animations; - std::string json_cameras; - std::string json_extensions; - - std::vector views; - - bool ext_pbr_specular_glossiness = false; - bool ext_clearcoat = false; - bool ext_transmission = false; - bool ext_ior = false; - bool ext_specular = false; - bool ext_sheen = false; - bool ext_volume = false; - bool ext_emissive_strength = false; - bool ext_iridescence = false; - bool ext_unlit = false; - bool ext_instancing = false; - bool ext_texture_transform = false; - - size_t accr_offset = 0; - size_t node_offset = 0; - size_t mesh_offset = 0; - size_t material_offset = 0; - - for (size_t i = 0; i < data->samplers_count; ++i) - { - const cgltf_sampler& sampler = data->samplers[i]; - - comma(json_samplers); - append(json_samplers, "{"); - writeSampler(json_samplers, sampler); - append(json_samplers, "}"); - } - - std::vector encoded_images; - -#ifdef WITH_BASISU - if (data->images_count && settings.texture_ktx2) - { - encoded_images.resize(data->images_count); - - encodeImages(encoded_images.data(), data, images, input_path, settings); - } -#endif - - for (size_t i = 0; i < data->images_count; ++i) - { - const cgltf_image& image = data->images[i]; - - comma(json_images); - append(json_images, "{"); - if (encoded_images.size()) - { - if (encoded_images[i].empty()) - fprintf(stderr, "Warning: unable to encode image %d (%s), skipping\n", int(i), image.uri ? image.uri : "?"); - else - writeEncodedImage(json_images, views, image, encoded_images[i], images[i], output_path, settings); - - encoded_images[i] = std::string(); // reclaim memory early - } - else - { - writeImage(json_images, views, image, images[i], i, input_path, settings); - } - append(json_images, "}"); - } - - for (size_t i = 0; i < data->textures_count; ++i) - { - const cgltf_texture& texture = data->textures[i]; - - comma(json_textures); - append(json_textures, "{"); - writeTexture(json_textures, texture, data, settings); - append(json_textures, "}"); - } - - for (size_t i = 0; i < data->materials_count; ++i) - { - MaterialInfo& mi = materials[i]; - - if (!mi.keep) - continue; - - const cgltf_material& material = data->materials[i]; - - comma(json_materials); - append(json_materials, "{"); - writeMaterial(json_materials, data, material, settings.quantize ? &qp : NULL, settings.quantize ? &qt_materials[i] : NULL); - if (settings.keep_extras) - writeExtras(json_materials, extras, material.extras); - append(json_materials, "}"); - - mi.remap = int(material_offset); - material_offset++; - - ext_pbr_specular_glossiness = ext_pbr_specular_glossiness || material.has_pbr_specular_glossiness; - ext_clearcoat = ext_clearcoat || material.has_clearcoat; - ext_transmission = ext_transmission || material.has_transmission; - ext_ior = ext_ior || material.has_ior; - ext_specular = ext_specular || material.has_specular; - ext_sheen = ext_sheen || material.has_sheen; - ext_volume = ext_volume || material.has_volume; - ext_emissive_strength = ext_emissive_strength || material.has_emissive_strength; - ext_iridescence = ext_iridescence || material.has_iridescence; - ext_unlit = ext_unlit || material.unlit; - ext_texture_transform = ext_texture_transform || mi.usesTextureTransform; - } - - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - comma(json_meshes); - append(json_meshes, "{\"primitives\":["); - - size_t pi = i; - for (; pi < meshes.size(); ++pi) - { - const Mesh& prim = meshes[pi]; - - if (prim.skin != mesh.skin || prim.targets != mesh.targets) - break; - - if (pi > i && (mesh.instances.size() || prim.instances.size())) - break; - - if (!compareMeshNodes(mesh, prim)) - break; - - if (!compareMeshTargets(mesh, prim)) - break; - - const QuantizationTexture& qt = qt_meshes[pi] == size_t(-1) ? qt_dummy : qt_materials[qt_meshes[pi]]; - - comma(json_meshes); - append(json_meshes, "{\"attributes\":{"); - writeMeshAttributes(json_meshes, views, json_accessors, accr_offset, prim, 0, qp, qt, settings); - append(json_meshes, "}"); - if (prim.type != cgltf_primitive_type_triangles) - { - append(json_meshes, ",\"mode\":"); - append(json_meshes, size_t(prim.type)); - } - if (mesh.targets) - { - append(json_meshes, ",\"targets\":["); - for (size_t j = 0; j < mesh.targets; ++j) - { - comma(json_meshes); - append(json_meshes, "{"); - writeMeshAttributes(json_meshes, views, json_accessors, accr_offset, prim, int(1 + j), qp, qt, settings); - append(json_meshes, "}"); - } - append(json_meshes, "]"); - } - - if (!prim.indices.empty()) - { - size_t index_accr = writeMeshIndices(views, json_accessors, accr_offset, prim, settings); - - append(json_meshes, ",\"indices\":"); - append(json_meshes, index_accr); - } - - if (prim.material) - { - MaterialInfo& mi = materials[prim.material - data->materials]; - - assert(mi.keep); - append(json_meshes, ",\"material\":"); - append(json_meshes, size_t(mi.remap)); - } - - if (prim.variants.size()) - { - append(json_meshes, ",\"extensions\":{\"KHR_materials_variants\":{\"mappings\":["); - - for (size_t j = 0; j < prim.variants.size(); ++j) - { - const cgltf_material_mapping& variant = prim.variants[j]; - MaterialInfo& mi = materials[variant.material - data->materials]; - - assert(mi.keep); - comma(json_meshes); - append(json_meshes, "{\"material\":"); - append(json_meshes, size_t(mi.remap)); - append(json_meshes, ",\"variants\":["); - append(json_meshes, size_t(variant.variant)); - append(json_meshes, "]}"); - } - - append(json_meshes, "]}}"); - } - - append(json_meshes, "}"); - } - - append(json_meshes, "]"); - - if (mesh.target_weights.size()) - { - append(json_meshes, ",\"weights\":["); - for (size_t j = 0; j < mesh.target_weights.size(); ++j) - { - comma(json_meshes); - append(json_meshes, mesh.target_weights[j]); - } - append(json_meshes, "]"); - } - - if (mesh.target_names.size()) - { - append(json_meshes, ",\"extras\":{\"targetNames\":["); - for (size_t j = 0; j < mesh.target_names.size(); ++j) - { - comma(json_meshes); - append(json_meshes, "\""); - append(json_meshes, mesh.target_names[j]); - append(json_meshes, "\""); - } - append(json_meshes, "]}"); - } - - append(json_meshes, "}"); - - assert(mesh.nodes.empty() || mesh.instances.empty()); - ext_instancing = ext_instancing || !mesh.instances.empty(); - - if (mesh.nodes.size()) - { - for (size_t j = 0; j < mesh.nodes.size(); ++j) - { - NodeInfo& ni = nodes[mesh.nodes[j] - data->nodes]; - - assert(ni.keep); - ni.meshes.push_back(node_offset); - - writeMeshNode(json_nodes, mesh_offset, mesh.nodes[j], mesh.skin, data, settings.quantize ? &qp : NULL); - - node_offset++; - } - } - else if (mesh.instances.size()) - { - assert(mesh.scene >= 0); - comma(json_roots[mesh.scene]); - append(json_roots[mesh.scene], node_offset); - - size_t instance_accr = writeInstances(views, json_accessors, accr_offset, mesh.instances, qp, settings); - - assert(!mesh.skin); - writeMeshNodeInstanced(json_nodes, mesh_offset, instance_accr); - - node_offset++; - } - else - { - assert(mesh.scene >= 0); - comma(json_roots[mesh.scene]); - append(json_roots[mesh.scene], node_offset); - - writeMeshNode(json_nodes, mesh_offset, NULL, mesh.skin, data, settings.quantize ? &qp : NULL); - - node_offset++; - } - - mesh_offset++; - - // skip all meshes that we've written in this iteration - assert(pi > i); - i = pi - 1; - } - - remapNodes(data, nodes, node_offset); - - for (size_t i = 0; i < data->nodes_count; ++i) - { - NodeInfo& ni = nodes[i]; - - if (!ni.keep) - continue; - - const cgltf_node& node = data->nodes[i]; - - comma(json_nodes); - append(json_nodes, "{"); - writeNode(json_nodes, node, nodes, data); - if (settings.keep_extras) - writeExtras(json_nodes, extras, node.extras); - append(json_nodes, "}"); - } - - for (size_t i = 0; i < data->scenes_count; ++i) - { - for (size_t j = 0; j < data->scenes[i].nodes_count; ++j) - { - NodeInfo& ni = nodes[data->scenes[i].nodes[j] - data->nodes]; - - if (ni.keep) - { - comma(json_roots[i]); - append(json_roots[i], size_t(ni.remap)); - } - } - } - - for (size_t i = 0; i < data->skins_count; ++i) - { - const cgltf_skin& skin = data->skins[i]; - - size_t matrix_accr = writeJointBindMatrices(views, json_accessors, accr_offset, skin, qp, settings); - - writeSkin(json_skins, skin, matrix_accr, nodes, data); - } - - for (size_t i = 0; i < animations.size(); ++i) - { - const Animation& animation = animations[i]; - - writeAnimation(json_animations, views, json_accessors, accr_offset, animation, i, data, nodes, settings); - } - - for (size_t i = 0; i < data->cameras_count; ++i) - { - const cgltf_camera& camera = data->cameras[i]; - - writeCamera(json_cameras, camera); - } - - if (data->lights_count > 0) - { - comma(json_extensions); - append(json_extensions, "\"KHR_lights_punctual\":{\"lights\":["); - - for (size_t i = 0; i < data->lights_count; ++i) - { - const cgltf_light& light = data->lights[i]; - - writeLight(json_extensions, light); - } - - append(json_extensions, "]}"); - } - - if (data->variants_count > 0) - { - comma(json_extensions); - append(json_extensions, "\"KHR_materials_variants\":{\"variants\":["); - - for (size_t i = 0; i < data->variants_count; ++i) - { - const cgltf_material_variant& variant = data->variants[i]; - - comma(json_extensions); - append(json_extensions, "{\"name\":\""); - append(json_extensions, variant.name); - append(json_extensions, "\"}"); - } - - append(json_extensions, "]}"); - } - - append(json, "\"asset\":{"); - append(json, "\"version\":\"2.0\",\"generator\":\"gltfpack "); - append(json, getVersion()); - append(json, "\""); - writeExtras(json, extras, data->asset.extras); - append(json, "}"); - - const ExtensionInfo extensions[] = { - {"KHR_mesh_quantization", settings.quantize, true}, - {"EXT_meshopt_compression", settings.compress, !settings.fallback}, - {"KHR_texture_transform", (settings.quantize && !json_textures.empty()) || ext_texture_transform, false}, - {"KHR_materials_pbrSpecularGlossiness", ext_pbr_specular_glossiness, false}, - {"KHR_materials_clearcoat", ext_clearcoat, false}, - {"KHR_materials_transmission", ext_transmission, false}, - {"KHR_materials_ior", ext_ior, false}, - {"KHR_materials_specular", ext_specular, false}, - {"KHR_materials_sheen", ext_sheen, false}, - {"KHR_materials_volume", ext_volume, false}, - {"KHR_materials_emissive_strength", ext_emissive_strength, false}, - {"KHR_materials_iridescence", ext_iridescence, false}, - {"KHR_materials_unlit", ext_unlit, false}, - {"KHR_materials_variants", data->variants_count > 0, false}, - {"KHR_lights_punctual", data->lights_count > 0, false}, - {"KHR_texture_basisu", !json_textures.empty() && settings.texture_ktx2, true}, - {"EXT_mesh_gpu_instancing", ext_instancing, true}, - }; - - writeExtensions(json, extensions, sizeof(extensions) / sizeof(extensions[0])); - - std::string json_views; - finalizeBufferViews(json_views, views, bin, settings.fallback ? &fallback : NULL, fallback_size); - - writeArray(json, "bufferViews", json_views); - writeArray(json, "accessors", json_accessors); - writeArray(json, "samplers", json_samplers); - writeArray(json, "images", json_images); - writeArray(json, "textures", json_textures); - writeArray(json, "materials", json_materials); - writeArray(json, "meshes", json_meshes); - writeArray(json, "skins", json_skins); - writeArray(json, "animations", json_animations); - writeArray(json, "nodes", json_nodes); - - if (!json_roots.empty()) - { - append(json, ",\"scenes\":["); - - for (size_t i = 0; i < data->scenes_count; ++i) - writeScene(json, data->scenes[i], json_roots[i]); - - append(json, "]"); - } - - writeArray(json, "cameras", json_cameras); - - if (data->scene) - { - append(json, ",\"scene\":"); - append(json, size_t(data->scene - data->scenes)); - } - - if (!json_extensions.empty()) - { - append(json, ",\"extensions\":{"); - append(json, json_extensions); - append(json, "}"); - } - - if (settings.verbose) - { - printMeshStats(meshes, "output"); - printSceneStats(views, meshes, node_offset, mesh_offset, material_offset, json.size(), bin.size()); - } - - if (settings.verbose > 1) - { - printAttributeStats(views, BufferView::Kind_Vertex, "vertex"); - printAttributeStats(views, BufferView::Kind_Index, "index"); - printAttributeStats(views, BufferView::Kind_Keyframe, "keyframe"); - printAttributeStats(views, BufferView::Kind_Instance, "instance"); - - printImageStats(views, TextureKind_Generic, "generic"); - printImageStats(views, TextureKind_Color, "color"); - printImageStats(views, TextureKind_Normal, "normal"); - printImageStats(views, TextureKind_Attrib, "attrib"); - } - - if (report_path) - { - if (!printReport(report_path, data, views, meshes, node_offset, mesh_offset, material_offset, animations.size(), json.size(), bin.size())) - { - fprintf(stderr, "Warning: cannot save report to %s\n", report_path); - } - } -} - -static void writeU32(FILE* out, uint32_t data) -{ - fwrite(&data, 4, 1, out); -} - -static const char* getBaseName(const char* path) -{ - const char* slash = strrchr(path, '/'); - const char* backslash = strrchr(path, '\\'); - - const char* rs = slash ? slash + 1 : path; - const char* bs = backslash ? backslash + 1 : path; - - return std::max(rs, bs); -} - -static std::string getBufferSpec(const char* bin_path, size_t bin_size, const char* fallback_path, size_t fallback_size, bool fallback_ref) -{ - std::string json; - append(json, "\"buffers\":["); - append(json, "{"); - if (bin_path) - { - append(json, "\"uri\":\""); - append(json, bin_path); - append(json, "\""); - } - comma(json); - append(json, "\"byteLength\":"); - append(json, bin_size); - append(json, "}"); - if (fallback_ref) - { - comma(json); - append(json, "{"); - if (fallback_path) - { - append(json, "\"uri\":\""); - append(json, fallback_path); - append(json, "\""); - } - comma(json); - append(json, "\"byteLength\":"); - append(json, fallback_size); - append(json, ",\"extensions\":{"); - append(json, "\"EXT_meshopt_compression\":{"); - append(json, "\"fallback\":true"); - append(json, "}}"); - append(json, "}"); - } - append(json, "]"); - - return json; -} - -int gltfpack(const char* input, const char* output, const char* report, Settings settings) -{ - cgltf_data* data = 0; - std::vector meshes; - std::vector animations; - std::string extras; - - std::string iext = getExtension(input); - std::string oext = output ? getExtension(output) : ""; - - if (iext == ".gltf" || iext == ".glb") - { - const char* error = 0; - data = parseGltf(input, meshes, animations, extras, &error); - - if (error) - { - fprintf(stderr, "Error loading %s: %s\n", input, error); - return 2; - } - } - else if (iext == ".obj") - { - const char* error = 0; - data = parseObj(input, meshes, &error); - - if (!data) - { - fprintf(stderr, "Error loading %s: %s\n", input, error); - return 2; - } - } - else - { - fprintf(stderr, "Error loading %s: unknown extension (expected .gltf or .glb or .obj)\n", input); - return 2; - } - -#ifndef WITH_BASISU - if (data->images_count && settings.texture_ktx2) - { - fprintf(stderr, "Error: gltfpack was built without BasisU support, texture compression is not available\n"); -#ifdef __wasi__ - fprintf(stderr, "Note: node.js builds do not support BasisU due to lack of platform features; download a native build from https://github.com/zeux/meshoptimizer/releases\n"); -#endif - return 3; - } -#endif - - if (oext == ".glb") - { - settings.texture_embed = true; - } - - std::string json, bin, fallback; - size_t fallback_size = 0; - process(data, input, output, report, meshes, animations, extras, settings, json, bin, fallback, fallback_size); - - cgltf_free(data); - - if (!output) - { - return 0; - } - - if (oext == ".gltf") - { - std::string binpath = output; - binpath.replace(binpath.size() - 5, 5, ".bin"); - - std::string fbpath = output; - fbpath.replace(fbpath.size() - 5, 5, ".fallback.bin"); - - FILE* outjson = fopen(output, "wb"); - FILE* outbin = fopen(binpath.c_str(), "wb"); - FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL; - if (!outjson || !outbin || (!outfb && settings.fallback)) - { - fprintf(stderr, "Error saving %s\n", output); - return 4; - } - - std::string bufferspec = getBufferSpec(getBaseName(binpath.c_str()), bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback_size, settings.compress); - - fprintf(outjson, "{"); - fwrite(bufferspec.c_str(), bufferspec.size(), 1, outjson); - fprintf(outjson, ","); - fwrite(json.c_str(), json.size(), 1, outjson); - fprintf(outjson, "}"); - - fwrite(bin.c_str(), bin.size(), 1, outbin); - - if (settings.fallback) - fwrite(fallback.c_str(), fallback.size(), 1, outfb); - - int rc = 0; - rc |= fclose(outjson); - rc |= fclose(outbin); - if (outfb) - rc |= fclose(outfb); - - if (rc) - { - fprintf(stderr, "Error saving %s\n", output); - return 4; - } - } - else if (oext == ".glb") - { - std::string fbpath = output; - fbpath.replace(fbpath.size() - 4, 4, ".fallback.bin"); - - FILE* out = fopen(output, "wb"); - FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL; - if (!out || (!outfb && settings.fallback)) - { - fprintf(stderr, "Error saving %s\n", output); - return 4; - } - - std::string bufferspec = getBufferSpec(NULL, bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback_size, settings.compress); - - json.insert(0, "{" + bufferspec + ","); - json.push_back('}'); - - while (json.size() % 4) - json.push_back(' '); - - while (bin.size() % 4) - bin.push_back('\0'); - - writeU32(out, 0x46546C67); - writeU32(out, 2); - writeU32(out, uint32_t(12 + 8 + json.size() + 8 + bin.size())); - - writeU32(out, uint32_t(json.size())); - writeU32(out, 0x4E4F534A); - fwrite(json.c_str(), json.size(), 1, out); - - writeU32(out, uint32_t(bin.size())); - writeU32(out, 0x004E4942); - fwrite(bin.c_str(), bin.size(), 1, out); - - if (settings.fallback) - fwrite(fallback.c_str(), fallback.size(), 1, outfb); - - int rc = 0; - rc |= fclose(out); - if (outfb) - rc |= fclose(outfb); - - if (rc) - { - fprintf(stderr, "Error saving %s\n", output); - return 4; - } - } - else - { - fprintf(stderr, "Error saving %s: unknown extension (expected .gltf or .glb)\n", output); - return 4; - } - - return 0; -} - -Settings defaults() -{ - Settings settings = {}; - settings.quantize = true; - settings.pos_bits = 14; - settings.tex_bits = 12; - settings.nrm_bits = 8; - settings.col_bits = 8; - settings.trn_bits = 16; - settings.rot_bits = 12; - settings.scl_bits = 16; - settings.anim_freq = 30; - settings.simplify_threshold = 1.f; - settings.texture_scale = 1.f; - for (int kind = 0; kind < TextureKind__Count; ++kind) - settings.texture_quality[kind] = 8; - - return settings; -} - -template -T clamp(T v, T min, T max) -{ - return v < min ? min : v > max ? max : v; -} - -unsigned int textureMask(const char* arg) -{ - unsigned int result = 0; - - while (arg) - { - const char* comma = strchr(arg, ','); - size_t seg = comma ? comma - arg - 1 : strlen(arg); - - if (strncmp(arg, "color", seg) == 0) - result |= 1 << TextureKind_Color; - else if (strncmp(arg, "normal", seg) == 0) - result |= 1 << TextureKind_Normal; - else if (strncmp(arg, "attrib", seg) == 0) - result |= 1 << TextureKind_Attrib; - else - fprintf(stderr, "Warning: unrecognized texture class %.*s\n", int(seg), arg); - - arg = comma ? comma + 1 : NULL; - } - - return result; -} - -int main(int argc, char** argv) -{ -#ifndef __wasi__ - setlocale(LC_ALL, "C"); // disable locale specific convention for number parsing/printing -#endif - - meshopt_encodeIndexVersion(1); - - Settings settings = defaults(); - - const char* input = 0; - const char* output = 0; - const char* report = 0; - bool help = false; - bool test = false; - - std::vector testinputs; - - for (int i = 1; i < argc; ++i) - { - const char* arg = argv[i]; - - if (strcmp(arg, "-vp") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.pos_bits = clamp(atoi(argv[++i]), 1, 16); - } - else if (strcmp(arg, "-vt") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.tex_bits = clamp(atoi(argv[++i]), 1, 16); - } - else if (strcmp(arg, "-vn") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.nrm_bits = clamp(atoi(argv[++i]), 1, 16); - } - else if (strcmp(arg, "-vc") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.col_bits = clamp(atoi(argv[++i]), 1, 16); - } - else if (strcmp(arg, "-at") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.trn_bits = clamp(atoi(argv[++i]), 1, 24); - } - else if (strcmp(arg, "-ar") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.rot_bits = clamp(atoi(argv[++i]), 4, 16); - } - else if (strcmp(arg, "-as") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.scl_bits = clamp(atoi(argv[++i]), 1, 24); - } - else if (strcmp(arg, "-af") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.anim_freq = clamp(atoi(argv[++i]), 1, 100); - } - else if (strcmp(arg, "-ac") == 0) - { - settings.anim_const = true; - } - else if (strcmp(arg, "-kn") == 0) - { - settings.keep_nodes = true; - } - else if (strcmp(arg, "-km") == 0) - { - settings.keep_materials = true; - } - else if (strcmp(arg, "-ke") == 0) - { - settings.keep_extras = true; - } - else if (strcmp(arg, "-mm") == 0) - { - settings.mesh_merge = true; - } - else if (strcmp(arg, "-mi") == 0) - { - settings.mesh_instancing = true; - } - else if (strcmp(arg, "-si") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.simplify_threshold = clamp(float(atof(argv[++i])), 0.f, 1.f); - } - else if (strcmp(arg, "-sa") == 0) - { - settings.simplify_aggressive = true; - } -#ifndef NDEBUG - else if (strcmp(arg, "-sd") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.simplify_debug = clamp(float(atof(argv[++i])), 0.f, 1.f); - } - else if (strcmp(arg, "-md") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.meshlet_debug = clamp(atoi(argv[++i]), 3, 255); - } -#endif - else if (strcmp(arg, "-tu") == 0) - { - settings.texture_ktx2 = true; - - unsigned int mask = ~0u; - if (i + 1 < argc && isalpha(argv[i + 1][0])) - mask = textureMask(argv[++i]); - - for (int kind = 0; kind < TextureKind__Count; ++kind) - if (mask & (1 << kind)) - settings.texture_uastc[kind] = true; - } - else if (strcmp(arg, "-tc") == 0) - { - settings.texture_ktx2 = true; - } - else if (strcmp(arg, "-tq") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - int quality = clamp(atoi(argv[++i]), 1, 10); - for (int kind = 0; kind < TextureKind__Count; ++kind) - settings.texture_quality[kind] = quality; - } - else if (strcmp(arg, "-tq") == 0 && i + 2 < argc && isalpha(argv[i + 1][0]) && isdigit(argv[i + 2][0])) - { - unsigned int mask = textureMask(argv[++i]); - int quality = clamp(atoi(argv[++i]), 1, 10); - - for (int kind = 0; kind < TextureKind__Count; ++kind) - if (mask & (1 << kind)) - settings.texture_quality[kind] = quality; - } - else if (strcmp(arg, "-ts") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.texture_scale = clamp(float(atof(argv[++i])), 0.f, 1.f); - } - else if (strcmp(arg, "-tl") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.texture_limit = atoi(argv[++i]); - } - else if (strcmp(arg, "-tp") == 0) - { - settings.texture_pow2 = true; - } - else if (strcmp(arg, "-tfy") == 0) - { - settings.texture_flipy = true; - } - else if (strcmp(arg, "-te") == 0) - { - fprintf(stderr, "Warning: -te is deprecated and will be removed in the future; gltfpack now automatically embeds textures into GLB files\n"); - } - else if (strcmp(arg, "-tj") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) - { - settings.texture_jobs = clamp(atoi(argv[++i]), 0, 128); - } - else if (strcmp(arg, "-noq") == 0) - { - settings.quantize = false; - } - else if (strcmp(arg, "-i") == 0 && i + 1 < argc && !input) - { - input = argv[++i]; - } - else if (strcmp(arg, "-o") == 0 && i + 1 < argc && !output) - { - output = argv[++i]; - } - else if (strcmp(arg, "-r") == 0 && i + 1 < argc && !report) - { - report = argv[++i]; - } - else if (strcmp(arg, "-c") == 0) - { - settings.compress = true; - } - else if (strcmp(arg, "-cc") == 0) - { - settings.compress = true; - settings.compressmore = true; - } - else if (strcmp(arg, "-cf") == 0) - { - settings.compress = true; - settings.fallback = true; - } - else if (strcmp(arg, "-v") == 0) - { - settings.verbose = 1; - } - else if (strcmp(arg, "-vv") == 0) - { - settings.verbose = 2; - } - else if (strcmp(arg, "-h") == 0) - { - help = true; - } - else if (strcmp(arg, "-test") == 0) - { - test = true; - } - else if (arg[0] == '-') - { - fprintf(stderr, "Unrecognized option %s\n", arg); - return 1; - } - else if (test) - { - testinputs.push_back(arg); - } - else - { - fprintf(stderr, "Expected option, got %s instead\n", arg); - return 1; - } - } - - // shortcut for gltfpack -v - if (settings.verbose && argc == 2) - { - printf("gltfpack %s\n", getVersion().c_str()); - return 0; - } - -#ifdef WITH_BASISU - if (settings.texture_ktx2) - encodeInit(settings.texture_jobs); -#endif - - if (test) - { - for (size_t i = 0; i < testinputs.size(); ++i) - { - const char* path = testinputs[i]; - - printf("%s\n", path); - gltfpack(path, NULL, NULL, settings); - } - - return 0; - } - - if (!input || !output || help) - { - fprintf(stderr, "gltfpack %s\n", getVersion().c_str()); - fprintf(stderr, "Usage: gltfpack [options] -i input -o output\n"); - - if (help) - { - fprintf(stderr, "\nBasics:\n"); - fprintf(stderr, "\t-i file: input file to process, .obj/.gltf/.glb\n"); - fprintf(stderr, "\t-o file: output file path, .gltf/.glb\n"); - fprintf(stderr, "\t-c: produce compressed gltf/glb files (-cc for higher compression ratio)\n"); - fprintf(stderr, "\nTextures:\n"); - fprintf(stderr, "\t-tc: convert all textures to KTX2 with BasisU supercompression\n"); - fprintf(stderr, "\t-tu: use UASTC when encoding textures (much higher quality and much larger size)\n"); - fprintf(stderr, "\t-tq N: set texture encoding quality (default: 8; N should be between 1 and 10\n"); - fprintf(stderr, "\t-ts R: scale texture dimensions by the ratio R (default: 1; R should be between 0 and 1)\n"); - fprintf(stderr, "\t-tl N: limit texture dimensions to N pixels (default: 0 = no limit)\n"); - fprintf(stderr, "\t-tp: resize textures to nearest power of 2 to conform to WebGL1 restrictions\n"); - fprintf(stderr, "\t-tfy: flip textures along Y axis during BasisU supercompression\n"); - fprintf(stderr, "\t-tj N: use N threads when compressing textures\n"); - fprintf(stderr, "\tTexture classes:\n"); - fprintf(stderr, "\t-tu C: use UASTC when encoding textures of class C\n"); - fprintf(stderr, "\t-tq C N: set texture encoding quality for class C\n"); - fprintf(stderr, "\t... where C is a comma-separated list (no spaces) with valid values color,normal,attrib\n"); - fprintf(stderr, "\nSimplification:\n"); - fprintf(stderr, "\t-si R: simplify meshes targeting triangle count ratio R (default: 1; R should be between 0 and 1)\n"); - fprintf(stderr, "\t-sa: aggressively simplify to the target ratio disregarding quality\n"); - fprintf(stderr, "\nVertices:\n"); - fprintf(stderr, "\t-vp N: use N-bit quantization for positions (default: 14; N should be between 1 and 16)\n"); - fprintf(stderr, "\t-vt N: use N-bit quantization for texture coordinates (default: 12; N should be between 1 and 16)\n"); - fprintf(stderr, "\t-vn N: use N-bit quantization for normals and tangents (default: 8; N should be between 1 and 16)\n"); - fprintf(stderr, "\t-vc N: use N-bit quantization for colors (default: 8; N should be between 1 and 16)\n"); - fprintf(stderr, "\nAnimations:\n"); - fprintf(stderr, "\t-at N: use N-bit quantization for translations (default: 16; N should be between 1 and 24)\n"); - fprintf(stderr, "\t-ar N: use N-bit quantization for rotations (default: 12; N should be between 4 and 16)\n"); - fprintf(stderr, "\t-as N: use N-bit quantization for scale (default: 16; N should be between 1 and 24)\n"); - fprintf(stderr, "\t-af N: resample animations at N Hz (default: 30)\n"); - fprintf(stderr, "\t-ac: keep constant animation tracks even if they don't modify the node transform\n"); - fprintf(stderr, "\nScene:\n"); - fprintf(stderr, "\t-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n"); - fprintf(stderr, "\t-km: keep named materials and disable named material merging\n"); - fprintf(stderr, "\t-ke: keep extras data\n"); - fprintf(stderr, "\t-mm: merge instances of the same mesh together when possible\n"); - fprintf(stderr, "\t-mi: use EXT_mesh_gpu_instancing when serializing multiple mesh instances\n"); - fprintf(stderr, "\nMiscellaneous:\n"); - fprintf(stderr, "\t-cf: produce compressed gltf/glb files with fallback for loaders that don't support compression\n"); - fprintf(stderr, "\t-noq: disable quantization; produces much larger glTF files with no extensions\n"); - fprintf(stderr, "\t-v: verbose output (print version when used without other options)\n"); - fprintf(stderr, "\t-r file: output a JSON report to file\n"); - fprintf(stderr, "\t-h: display this help and exit\n"); - } - else - { - fprintf(stderr, "\nBasics:\n"); - fprintf(stderr, "\t-i file: input file to process, .obj/.gltf/.glb\n"); - fprintf(stderr, "\t-o file: output file path, .gltf/.glb\n"); - fprintf(stderr, "\t-c: produce compressed gltf/glb files (-cc for higher compression ratio)\n"); - fprintf(stderr, "\t-tc: convert all textures to KTX2 with BasisU supercompression\n"); - fprintf(stderr, "\t-si R: simplify meshes targeting triangle count ratio R (default: 1; R should be between 0 and 1)\n"); - fprintf(stderr, "\nRun gltfpack -h to display a full list of options\n"); - } - - return 1; - } - - if (settings.texture_limit && !settings.texture_ktx2) - { - fprintf(stderr, "Option -tl is only supported when -tc is set as well\n"); - return 1; - } - - if (settings.texture_pow2 && (settings.texture_limit & (settings.texture_limit - 1)) != 0) - { - fprintf(stderr, "Option -tp requires the limit specified via -tl to be a power of 2\n"); - return 1; - } - - if (settings.texture_scale < 1 && !settings.texture_ktx2) - { - fprintf(stderr, "Option -ts is only supported when -tc is set as well\n"); - return 1; - } - - if (settings.texture_pow2 && !settings.texture_ktx2) - { - fprintf(stderr, "Option -tp is only supported when -tc is set as well\n"); - return 1; - } - - if (settings.texture_flipy && !settings.texture_ktx2) - { - fprintf(stderr, "Option -tfy is only supported when -tc is set as well\n"); - return 1; - } - - return gltfpack(input, output, report, settings); -} - -#ifdef __wasi__ -extern "C" int pack(int argc, char** argv) -{ - chdir("/gltfpack-$pwd"); - - int result = main(argc, argv); - fflush(NULL); - return result; -} -#endif diff --git a/Dependencies/meshoptimizer/gltf/gltfpack.h b/Dependencies/meshoptimizer/gltf/gltfpack.h deleted file mode 100644 index 69aba1a7..00000000 --- a/Dependencies/meshoptimizer/gltf/gltfpack.h +++ /dev/null @@ -1,381 +0,0 @@ -/** - * gltfpack - version 0.17 - * - * Copyright (C) 2016-2021, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) - * Report bugs and download new versions at https://github.com/zeux/meshoptimizer - * - * This application is distributed under the MIT License. See notice at the end of this file. - */ - -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "../extern/cgltf.h" - -#include - -#include -#include - -struct Attr -{ - float f[4]; -}; - -struct Stream -{ - cgltf_attribute_type type; - int index; - int target; // 0 = base mesh, 1+ = morph target - - std::vector data; -}; - -struct Transform -{ - float data[16]; -}; - -struct Mesh -{ - int scene; - std::vector nodes; - std::vector instances; - - cgltf_material* material; - cgltf_skin* skin; - - cgltf_primitive_type type; - - std::vector streams; - std::vector indices; - - size_t targets; - std::vector target_weights; - std::vector target_names; - - std::vector variants; -}; - -struct Track -{ - cgltf_node* node; - cgltf_animation_path_type path; - - bool constant; - bool dummy; - - size_t components; // 1 unless path is cgltf_animation_path_type_weights - - cgltf_interpolation_type interpolation; - - std::vector time; // empty for resampled or constant animations - std::vector data; -}; - -struct Animation -{ - const char* name; - - float start; - int frames; - - std::vector tracks; -}; - -enum TextureKind -{ - TextureKind_Generic, - TextureKind_Color, - TextureKind_Normal, - TextureKind_Attrib, - - TextureKind__Count -}; - -struct Settings -{ - int pos_bits; - int tex_bits; - int nrm_bits; - int col_bits; - - int trn_bits; - int rot_bits; - int scl_bits; - - int anim_freq; - bool anim_const; - - bool keep_nodes; - bool keep_materials; - bool keep_extras; - - bool mesh_merge; - bool mesh_instancing; - - float simplify_threshold; - bool simplify_aggressive; - float simplify_debug; - - int meshlet_debug; - - bool texture_ktx2; - bool texture_embed; - - bool texture_pow2; - bool texture_flipy; - float texture_scale; - int texture_limit; - - bool texture_uastc[TextureKind__Count]; - int texture_quality[TextureKind__Count]; - - int texture_jobs; - - bool quantize; - - bool compress; - bool compressmore; - bool fallback; - - int verbose; -}; - -struct QuantizationPosition -{ - float offset[3]; - float scale; - int bits; -}; - -struct QuantizationTexture -{ - float offset[2]; - float scale[2]; - int bits; -}; - -struct StreamFormat -{ - enum Filter - { - Filter_None = 0, - Filter_Oct = 1, - Filter_Quat = 2, - Filter_Exp = 3, - }; - - cgltf_type type; - cgltf_component_type component_type; - bool normalized; - size_t stride; - Filter filter; -}; - -struct NodeInfo -{ - int scene; - - bool keep; - bool animated; - - unsigned int animated_paths; - - int remap; - std::vector meshes; -}; - -struct MaterialInfo -{ - bool keep; - - bool usesTextureTransform; - bool needsTangents; - unsigned int textureSetMask; - - int remap; -}; - -struct ImageInfo -{ - TextureKind kind; - bool normal_map; - bool srgb; - - int channels; -}; - -struct ExtensionInfo -{ - const char* name; - - bool used; - bool required; -}; - -struct BufferView -{ - enum Kind - { - Kind_Vertex, - Kind_Index, - Kind_Skin, - Kind_Time, - Kind_Keyframe, - Kind_Instance, - Kind_Image, - Kind_Count - }; - - enum Compression - { - Compression_None = -1, - Compression_Attribute, - Compression_Index, - Compression_IndexSequence, - }; - - Kind kind; - StreamFormat::Filter filter; - Compression compression; - size_t stride; - int variant; - - std::string data; - - size_t bytes; -}; - -struct TempFile -{ - std::string path; - int fd; - - TempFile(const char* suffix); - ~TempFile(); -}; - -std::string getFullPath(const char* path, const char* base_path); -std::string getFileName(const char* path); -std::string getExtension(const char* path); - -bool readFile(const char* path, std::string& data); -bool writeFile(const char* path, const std::string& data); - -cgltf_data* parseObj(const char* path, std::vector& meshes, const char** error); -cgltf_data* parseGltf(const char* path, std::vector& meshes, std::vector& animations, std::string& extras, const char** error); - -void processAnimation(Animation& animation, const Settings& settings); -void processMesh(Mesh& mesh, const Settings& settings); - -void debugSimplify(const Mesh& mesh, Mesh& kinds, Mesh& loops, float ratio); -void debugMeshlets(const Mesh& mesh, Mesh& meshlets, Mesh& bounds, int max_vertices, bool scan); - -bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs); -bool compareMeshVariants(const Mesh& lhs, const Mesh& rhs); -bool compareMeshNodes(const Mesh& lhs, const Mesh& rhs); - -void mergeMeshInstances(Mesh& mesh); -void mergeMeshes(std::vector& meshes, const Settings& settings); -void filterEmptyMeshes(std::vector& meshes); -void filterStreams(Mesh& mesh, const MaterialInfo& mi); - -void mergeMeshMaterials(cgltf_data* data, std::vector& meshes, const Settings& settings); -void markNeededMaterials(cgltf_data* data, std::vector& materials, const std::vector& meshes, const Settings& settings); - -bool hasValidTransform(const cgltf_texture_view& view); - -void analyzeMaterials(cgltf_data* data, std::vector& materials, std::vector& images); -void optimizeMaterials(cgltf_data* data, const char* input_path, std::vector& images); - -bool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type); -bool hasAlpha(const std::string& data, const char* mime_type); -bool getDimensions(const std::string& data, const char* mime_type, int& width, int& height); -void adjustDimensions(int& width, int& height, const Settings& settings); -const char* mimeExtension(const char* mime_type); - -#ifdef WITH_BASISU -void encodeInit(int jobs); -bool encodeBasis(const std::string& data, const char* mime_type, std::string& result, const ImageInfo& info, const Settings& settings); -void encodeImages(std::string* encoded, const cgltf_data* data, const std::vector& images, const char* input_path, const Settings& settings); -#endif - -void markScenes(cgltf_data* data, std::vector& nodes); -void markAnimated(cgltf_data* data, std::vector& nodes, const std::vector& animations); -void markNeededNodes(cgltf_data* data, std::vector& nodes, const std::vector& meshes, const std::vector& animations, const Settings& settings); -void remapNodes(cgltf_data* data, std::vector& nodes, size_t& node_offset); -void decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform); - -QuantizationPosition prepareQuantizationPosition(const std::vector& meshes, const Settings& settings); -void prepareQuantizationTexture(cgltf_data* data, std::vector& result, std::vector& indices, const std::vector& meshes, const Settings& settings); -void getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition* qp); - -StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings); -StreamFormat writeIndexStream(std::string& bin, const std::vector& stream); -StreamFormat writeTimeStream(std::string& bin, const std::vector& data); -StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector& data, const Settings& settings); - -void compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride); -void compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride); -void compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride); - -size_t getBufferView(std::vector& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant = 0); - -void comma(std::string& s); -void append(std::string& s, size_t v); -void append(std::string& s, float v); -void append(std::string& s, const char* v); -void append(std::string& s, const std::string& v); -void appendJson(std::string& s, const char* begin, const char* end); - -const char* attributeType(cgltf_attribute_type type); -const char* animationPath(cgltf_animation_path_type type); - -void writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt); -void writeBufferView(std::string& json, BufferView::Kind kind, StreamFormat::Filter filter, size_t count, size_t stride, size_t bin_offset, size_t bin_size, BufferView::Compression compression, size_t compressed_offset, size_t compressed_size); -void writeSampler(std::string& json, const cgltf_sampler& sampler); -void writeImage(std::string& json, std::vector& views, const cgltf_image& image, const ImageInfo& info, size_t index, const char* input_path, const Settings& settings); -void writeEncodedImage(std::string& json, std::vector& views, const cgltf_image& image, const std::string& encoded, const ImageInfo& info, const char* output_path, const Settings& settings); -void writeTexture(std::string& json, const cgltf_texture& texture, cgltf_data* data, const Settings& settings); -void writeMeshAttributes(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings); -size_t writeMeshIndices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const Settings& settings); -size_t writeJointBindMatrices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings); -size_t writeInstances(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& transforms, const QuantizationPosition& qp, const Settings& settings); -void writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp); -void writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset); -void writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector& nodes, cgltf_data* data); -void writeNode(std::string& json, const cgltf_node& node, const std::vector& nodes, cgltf_data* data); -void writeAnimation(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector& nodes, const Settings& settings); -void writeCamera(std::string& json, const cgltf_camera& camera); -void writeLight(std::string& json, const cgltf_light& light); -void writeArray(std::string& json, const char* name, const std::string& contents); -void writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count); -void writeExtras(std::string& json, const std::string& data, const cgltf_extras& extras); -void writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots); - -/** - * Copyright (c) 2016-2021 Arseny Kapoulkine - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ diff --git a/Dependencies/meshoptimizer/gltf/image.cpp b/Dependencies/meshoptimizer/gltf/image.cpp deleted file mode 100644 index e0d05d64..00000000 --- a/Dependencies/meshoptimizer/gltf/image.cpp +++ /dev/null @@ -1,268 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include - -static const char* kMimeTypes[][2] = { - {"image/jpeg", ".jpg"}, - {"image/jpeg", ".jpeg"}, - {"image/png", ".png"}, -}; - -static const char* inferMimeType(const char* path) -{ - std::string ext = getExtension(path); - - for (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i) - if (ext == kMimeTypes[i][1]) - return kMimeTypes[i][0]; - - return ""; -} - -static bool parseDataUri(const char* uri, std::string& mime_type, std::string& result) -{ - if (strncmp(uri, "data:", 5) == 0) - { - const char* comma = strchr(uri, ','); - - if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) - { - const char* base64 = comma + 1; - size_t base64_size = strlen(base64); - size_t size = base64_size - base64_size / 4; - - if (base64_size >= 2) - { - size -= base64[base64_size - 2] == '='; - size -= base64[base64_size - 1] == '='; - } - - void* data = 0; - - cgltf_options options = {}; - cgltf_result res = cgltf_load_buffer_base64(&options, size, base64, &data); - - if (res != cgltf_result_success) - return false; - - mime_type = std::string(uri + 5, comma - 7); - result = std::string(static_cast(data), size); - - free(data); - - return true; - } - } - - return false; -} - -bool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type) -{ - if (image.uri && parseDataUri(image.uri, mime_type, data)) - { - return true; - } - else if (image.buffer_view && image.buffer_view->buffer->data && image.mime_type) - { - const cgltf_buffer_view* view = image.buffer_view; - - data.assign(static_cast(view->buffer->data) + view->offset, view->size); - mime_type = image.mime_type; - return true; - } - else if (image.uri && *image.uri) - { - std::string path = image.uri; - - cgltf_decode_uri(&path[0]); - path.resize(strlen(&path[0])); - - mime_type = image.mime_type ? image.mime_type : inferMimeType(path.c_str()); - - return readFile(getFullPath(path.c_str(), input_path).c_str(), data); - } - else - { - return false; - } -} - -static int readInt16(const std::string& data, size_t offset) -{ - return (unsigned char)data[offset] * 256 + (unsigned char)data[offset + 1]; -} - -static int readInt32(const std::string& data, size_t offset) -{ - return (unsigned((unsigned char)data[offset]) << 24) | - (unsigned((unsigned char)data[offset + 1]) << 16) | - (unsigned((unsigned char)data[offset + 2]) << 8) | - unsigned((unsigned char)data[offset + 3]); -} - -static bool getDimensionsPng(const std::string& data, int& width, int& height) -{ - if (data.size() < 8 + 8 + 13 + 4) - return false; - - const char* signature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; - if (data.compare(0, 8, signature) != 0) - return false; - - if (data.compare(12, 4, "IHDR") != 0) - return false; - - width = readInt32(data, 16); - height = readInt32(data, 20); - - return true; -} - -static bool getDimensionsJpeg(const std::string& data, int& width, int& height) -{ - size_t offset = 0; - - // note, this can stop parsing before reaching the end but we stop at SOF anyway - while (offset + 4 <= data.size()) - { - if (data[offset] != '\xff') - return false; - - char marker = data[offset + 1]; - - if (marker == '\xff') - { - offset++; - continue; // padding - } - - // d0..d9 correspond to SOI, RSTn, EOI - if (marker == 0 || unsigned(marker - '\xd0') <= 9) - { - offset += 2; - continue; // no payload - } - - // c0..c1 correspond to SOF0, SOF1 - if (marker == '\xc0' || marker == '\xc2') - { - if (offset + 10 > data.size()) - return false; - - width = readInt16(data, offset + 7); - height = readInt16(data, offset + 5); - - return true; - } - - offset += 2 + readInt16(data, offset + 2); - } - - return false; -} - -static bool hasTransparencyPng(const std::string& data) -{ - if (data.size() < 8 + 8 + 13 + 4) - return false; - - const char* signature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; - if (data.compare(0, 8, signature) != 0) - return false; - - if (data.compare(12, 4, "IHDR") != 0) - return false; - - int ctype = data[25]; - - if (ctype != 3) - return ctype == 4 || ctype == 6; - - size_t offset = 8; // reparse IHDR chunk for simplicity - - while (offset + 12 <= data.size()) - { - int length = readInt32(data, offset); - - if (length < 0) - return false; - - if (data.compare(offset + 4, 4, "tRNS") == 0) - return true; - - offset += 12 + length; - } - - return false; -} - -bool hasAlpha(const std::string& data, const char* mime_type) -{ - if (strcmp(mime_type, "image/png") == 0) - return hasTransparencyPng(data); - else - return false; -} - -bool getDimensions(const std::string& data, const char* mime_type, int& width, int& height) -{ - if (strcmp(mime_type, "image/png") == 0) - return getDimensionsPng(data, width, height); - if (strcmp(mime_type, "image/jpeg") == 0) - return getDimensionsJpeg(data, width, height); - - return false; -} - -static int roundPow2(int value) -{ - int result = 1; - - while (result < value) - result <<= 1; - - // to prevent odd texture sizes from increasing the size too much, we round to nearest power of 2 above a certain size - if (value > 128 && result * 3 / 4 > value) - result >>= 1; - - return result; -} - -static int roundBlock(int value, bool pow2) -{ - if (value == 0) - return 4; - - if (pow2 && value > 4) - return roundPow2(value); - - return (value + 3) & ~3; -} - -void adjustDimensions(int& width, int& height, const Settings& settings) -{ - width = int(width * settings.texture_scale); - height = int(height * settings.texture_scale); - - if (settings.texture_limit && (width > settings.texture_limit || height > settings.texture_limit)) - { - float limit_scale = float(settings.texture_limit) / float(width > height ? width : height); - - width = int(width * limit_scale); - height = int(height * limit_scale); - } - - width = roundBlock(width, settings.texture_pow2); - height = roundBlock(height, settings.texture_pow2); -} - -const char* mimeExtension(const char* mime_type) -{ - for (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i) - if (strcmp(kMimeTypes[i][0], mime_type) == 0) - return kMimeTypes[i][1]; - - return ".raw"; -} diff --git a/Dependencies/meshoptimizer/gltf/json.cpp b/Dependencies/meshoptimizer/gltf/json.cpp deleted file mode 100644 index fa06adf0..00000000 --- a/Dependencies/meshoptimizer/gltf/json.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include - -void comma(std::string& s) -{ - char ch = s.empty() ? 0 : s[s.size() - 1]; - - if (ch != 0 && ch != '[' && ch != '{') - s += ","; -} - -void append(std::string& s, size_t v) -{ - char buf[32]; - sprintf(buf, "%zu", v); - s += buf; -} - -void append(std::string& s, float v) -{ - char buf[512]; - sprintf(buf, "%.9g", v); - s += buf; -} - -void append(std::string& s, const char* v) -{ - s += v; -} - -void append(std::string& s, const std::string& v) -{ - s += v; -} - -void appendJson(std::string& s, const char* begin, const char* end) -{ - enum State - { - None, - Escape, - Quoted - } state = None; - - for (const char* it = begin; it != end; ++it) - { - char ch = *it; - - // whitespace outside of quoted strings can be ignored - if (state != None || !isspace(ch)) - s += ch; - - // the finite automata tracks whether we're inside a quoted string - switch (state) - { - case None: - state = (ch == '"') ? Quoted : None; - break; - - case Quoted: - state = (ch == '"') ? None : (ch == '\\') ? Escape : Quoted; - break; - - case Escape: - state = Quoted; - break; - - default: - assert(!"Unexpected parsing state"); - } - } -} diff --git a/Dependencies/meshoptimizer/gltf/library.js b/Dependencies/meshoptimizer/gltf/library.js deleted file mode 100644 index ec5b641b..00000000 --- a/Dependencies/meshoptimizer/gltf/library.js +++ /dev/null @@ -1,374 +0,0 @@ -// This file is part of gltfpack and is distributed under the terms of MIT License. - -/** - * Initialize the library with the Wasm module (library.wasm) - * - * @param wasm Promise with contents of library.wasm - * - * Note: this is called automatically in node.js - */ -function init(wasm) { - if (ready) { - throw new Error("init must be called once"); - } - - ready = Promise.resolve(wasm) - .then(function (buffer) { - return WebAssembly.instantiate(buffer, { wasi_snapshot_preview1: wasi }); - }) - .then(function (result) { - instance = result.instance; - instance.exports.__wasm_call_ctors(); - }); -} - -/** - * Pack the requested glTF data using the requested command line and access interface. - * - * @param args An array of strings with the input arguments; the paths for input and output files are interpreted by the interface - * @param iface An interface to the system that will be used to service file requests and other system calls - * @return Promise that indicates completion of the operation - * - * iface should contain the following methods: - * read(path): Given a path, return a Uint8Array with the contents of that path - * write(path, data): Write the specified Uint8Array to the provided path - */ -function pack(args, iface) { - if (!ready) { - throw new Error("init must be called before pack"); - } - - var argv = args.slice(); - argv.unshift("gltfpack"); - - return ready.then(function () { - var buf = uploadArgv(argv); - - output.position = 0; - output.size = 0; - - interface = iface; - var result = instance.exports.pack(argv.length, buf); - interface = undefined; - - instance.exports.free(buf); - - var log = getString(output.data.buffer, 0, output.size); - - if (result != 0) { - throw new Error(log); - } else { - return log; - } - }); -} - -// Library implementation (here be dragons) -var WASI_EBADF = 8; -var WASI_EINVAL = 28; -var WASI_EIO = 29; -var WASI_ENOSYS = 52; - -var ready; -var instance; -var interface; - -var output = { data: new Uint8Array(), position: 0, size: 0 }; -var fds = { 1: output, 2: output, 3: { mount: "/", path: "/" }, 4: { mount: "/gltfpack-$pwd", path: "" } }; - -var wasi = { - proc_exit: function(rval) { - }, - - fd_close: function(fd) { - if (!fds[fd]) { - return WASI_EBADF; - } - - try { - if (fds[fd].close) { - fds[fd].close(); - } - fds[fd] = undefined; - return 0; - } catch (err) { - fds[fd] = undefined; - return WASI_EIO; - } - }, - - fd_fdstat_get: function(fd, stat) { - if (!fds[fd]) { - return WASI_EBADF; - } - - var heap = getHeap(); - heap.setUint8(stat + 0, fds[fd].path !== undefined ? 3 : 4); - heap.setUint16(stat + 2, 0, true); - heap.setUint32(stat + 8, 0, true); - heap.setUint32(stat + 12, 0, true); - heap.setUint32(stat + 16, 0, true); - heap.setUint32(stat + 20, 0, true); - return 0; - }, - - path_open32: function(parent_fd, dirflags, path, path_len, oflags, fs_rights_base, fs_rights_inheriting, fdflags, opened_fd) { - if (!fds[parent_fd] || fds[parent_fd].path === undefined) { - return WASI_EBADF; - } - - var heap = getHeap(); - - var file = {}; - file.name = fds[parent_fd].path + getString(heap.buffer, path, path_len); - file.position = 0; - - if (oflags & 1) { - file.data = new Uint8Array(4096); - file.size = 0; - file.close = function () { - interface.write(file.name, new Uint8Array(file.data.buffer, 0, file.size)); - }; - } else { - try { - file.data = interface.read(file.name); - - if (!file.data) { - return WASI_EIO; - } - - file.size = file.data.length; - } catch (err) { - return WASI_EIO; - } - } - - var fd = nextFd(); - fds[fd] = file; - - heap.setUint32(opened_fd, fd, true); - return 0; - }, - - path_filestat_get: function(parent_fd, flags, path, path_len, buf) { - if (!fds[parent_fd] || fds[parent_fd].path === undefined) { - return WASI_EBADF; - } - - var heap = getHeap(); - var name = getString(heap.buffer, path, path_len); - - var heap = getHeap(); - for (var i = 0; i < 64; ++i) - heap.setUint8(buf + i, 0); - - heap.setUint8(buf + 16, name == "." ? 3 : 4); - return 0; - }, - - fd_prestat_get: function(fd, buf) { - if (!fds[fd] || fds[fd].path === undefined) { - return WASI_EBADF; - } - - var path_buf = stringBuffer(fds[fd].mount); - - var heap = getHeap(); - heap.setUint8(buf, 0); - heap.setUint32(buf + 4, path_buf.length, true); - return 0; - }, - - fd_prestat_dir_name: function(fd, path, path_len) { - if (!fds[fd] || fds[fd].path === undefined) { - return WASI_EBADF; - } - - var path_buf = stringBuffer(fds[fd].mount); - - if (path_len != path_buf.length) { - return WASI_EINVAL; - } - - var heap = getHeap(); - new Uint8Array(heap.buffer).set(path_buf, path); - return 0; - }, - - path_remove_directory: function(parent_fd, path, path_len) { - return WASI_EINVAL; - }, - - fd_fdstat_set_flags: function(fd, flags) { - return WASI_ENOSYS; - }, - - fd_seek32: function(fd, offset, whence, newoffset) { - if (!fds[fd]) { - return WASI_EBADF; - } - - var newposition; - - switch (whence) { - case 0: - newposition = offset; - break; - - case 1: - newposition = fds[fd].position + offset; - break; - - case 2: - newposition = fds[fd].size; - break; - - default: - return WASI_EINVAL; - } - - if (newposition > fds[fd].size) { - return WASI_EINVAL; - } - - fds[fd].position = newposition; - - var heap = getHeap(); - heap.setUint32(newoffset, newposition, true); - return 0; - }, - - fd_read: function(fd, iovs, iovs_len, nread) { - if (!fds[fd]) { - return WASI_EBADF; - } - - var heap = getHeap(); - var read = 0; - - for (var i = 0; i < iovs_len; ++i) { - var buf = heap.getUint32(iovs + 8 * i + 0, true); - var buf_len = heap.getUint32(iovs + 8 * i + 4, true); - - var readi = Math.min(fds[fd].size - fds[fd].position, buf_len); - - new Uint8Array(heap.buffer).set(fds[fd].data.subarray(fds[fd].position, fds[fd].position + readi), buf); - - fds[fd].position += readi; - read += readi; - } - - heap.setUint32(nread, read, true); - return 0; - }, - - fd_write: function(fd, iovs, iovs_len, nwritten) { - if (!fds[fd]) { - return WASI_EBADF; - } - - var heap = getHeap(); - var written = 0; - - for (var i = 0; i < iovs_len; ++i) { - var buf = heap.getUint32(iovs + 8 * i + 0, true); - var buf_len = heap.getUint32(iovs + 8 * i + 4, true); - - if (fds[fd].position + buf_len > fds[fd].data.length) { - fds[fd].data = growArray(fds[fd].data, fds[fd].position + buf_len); - } - - fds[fd].data.set(new Uint8Array(heap.buffer, buf, buf_len), fds[fd].position); - fds[fd].position += buf_len; - fds[fd].size = Math.max(fds[fd].position, fds[fd].size); - - written += buf_len; - } - - heap.setUint32(nwritten, written, true); - return 0; - }, -}; - -function nextFd() { - for (var i = 1; ; ++i) { - if (fds[i] === undefined) { - return i; - } - } -} - -function getHeap() { - return new DataView(instance.exports.memory.buffer); -} - -function getString(buffer, offset, length) { - return new TextDecoder().decode(new Uint8Array(buffer, offset, length)); -} - -function stringBuffer(string) { - return new TextEncoder().encode(string); -} - -function growArray(data, len) { - var new_length = Math.max(1, data.length); - while (new_length < len) { - new_length *= 2; - } - - var new_data = new Uint8Array(new_length); - new_data.set(data); - - return new_data; -} - -function uploadArgv(argv) { - var buf_size = argv.length * 4; - for (var i = 0; i < argv.length; ++i) { - buf_size += stringBuffer(argv[i]).length + 1; - } - - var buf = instance.exports.malloc(buf_size); - var argp = buf + argv.length * 4; - - var heap = getHeap(); - - for (var i = 0; i < argv.length; ++i) { - var item = stringBuffer(argv[i]); - - heap.setUint32(buf + i * 4, argp, true); - new Uint8Array(heap.buffer).set(item, argp); - heap.setUint8(argp + item.length, 0); - - argp += item.length + 1; - } - - return buf; -} - -// Automatic initialization for node.js -if (typeof window === 'undefined' && typeof process !== 'undefined' && process.release.name === 'node') { - var fs = require('fs'); - var util = require('util'); - - // Node versions before v12 don't support TextEncoder/TextDecoder natively, but util. provides compatible replacements - if (typeof TextEncoder === 'undefined' && typeof TextDecoder === 'undefined') { - TextEncoder = util.TextEncoder; - TextDecoder = util.TextDecoder; - } - - init(fs.readFileSync(__dirname + '/library.wasm')); -} - -// UMD -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof module === 'object' && module.exports) { - module.exports = factory(); - } else { - root.gltfpack = factory(); - } -}(typeof self !== 'undefined' ? self : this, function () { - return { init, pack }; -})); diff --git a/Dependencies/meshoptimizer/gltf/material.cpp b/Dependencies/meshoptimizer/gltf/material.cpp deleted file mode 100644 index 68b952c1..00000000 --- a/Dependencies/meshoptimizer/gltf/material.cpp +++ /dev/null @@ -1,530 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include - -static bool areTextureViewsEqual(const cgltf_texture_view& lhs, const cgltf_texture_view& rhs) -{ - if (lhs.has_transform != rhs.has_transform) - return false; - - if (lhs.has_transform) - { - const cgltf_texture_transform& lt = lhs.transform; - const cgltf_texture_transform& rt = rhs.transform; - - if (memcmp(lt.offset, rt.offset, sizeof(cgltf_float) * 2) != 0) - return false; - - if (lt.rotation != rt.rotation) - return false; - - if (memcmp(lt.scale, rt.scale, sizeof(cgltf_float) * 2) != 0) - return false; - - if (lt.texcoord != rt.texcoord) - return false; - } - - if (lhs.texture != rhs.texture) - return false; - - if (lhs.texcoord != rhs.texcoord) - return false; - - if (lhs.scale != rhs.scale) - return false; - - return true; -} - -static bool areExtrasEqual(cgltf_data* data, const cgltf_extras& lhs, const cgltf_extras& rhs) -{ - if (lhs.end_offset - lhs.start_offset != rhs.end_offset - rhs.start_offset) - return false; - - if (memcmp(data->json + lhs.start_offset, data->json + rhs.start_offset, lhs.end_offset - lhs.start_offset) != 0) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_pbr_metallic_roughness& lhs, const cgltf_pbr_metallic_roughness& rhs) -{ - if (!areTextureViewsEqual(lhs.base_color_texture, rhs.base_color_texture)) - return false; - - if (!areTextureViewsEqual(lhs.metallic_roughness_texture, rhs.metallic_roughness_texture)) - return false; - - if (memcmp(lhs.base_color_factor, rhs.base_color_factor, sizeof(cgltf_float) * 4) != 0) - return false; - - if (lhs.metallic_factor != rhs.metallic_factor) - return false; - - if (lhs.roughness_factor != rhs.roughness_factor) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_pbr_specular_glossiness& lhs, const cgltf_pbr_specular_glossiness& rhs) -{ - if (!areTextureViewsEqual(lhs.diffuse_texture, rhs.diffuse_texture)) - return false; - - if (!areTextureViewsEqual(lhs.specular_glossiness_texture, rhs.specular_glossiness_texture)) - return false; - - if (memcmp(lhs.diffuse_factor, rhs.diffuse_factor, sizeof(cgltf_float) * 4) != 0) - return false; - - if (memcmp(lhs.specular_factor, rhs.specular_factor, sizeof(cgltf_float) * 3) != 0) - return false; - - if (lhs.glossiness_factor != rhs.glossiness_factor) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_clearcoat& lhs, const cgltf_clearcoat& rhs) -{ - if (!areTextureViewsEqual(lhs.clearcoat_texture, rhs.clearcoat_texture)) - return false; - - if (!areTextureViewsEqual(lhs.clearcoat_roughness_texture, rhs.clearcoat_roughness_texture)) - return false; - - if (!areTextureViewsEqual(lhs.clearcoat_normal_texture, rhs.clearcoat_normal_texture)) - return false; - - if (lhs.clearcoat_factor != rhs.clearcoat_factor) - return false; - - if (lhs.clearcoat_roughness_factor != rhs.clearcoat_roughness_factor) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_transmission& lhs, const cgltf_transmission& rhs) -{ - if (!areTextureViewsEqual(lhs.transmission_texture, rhs.transmission_texture)) - return false; - - if (lhs.transmission_factor != rhs.transmission_factor) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_ior& lhs, const cgltf_ior& rhs) -{ - if (lhs.ior != rhs.ior) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_specular& lhs, const cgltf_specular& rhs) -{ - if (!areTextureViewsEqual(lhs.specular_texture, rhs.specular_texture)) - return false; - - if (!areTextureViewsEqual(lhs.specular_color_texture, rhs.specular_color_texture)) - return false; - - if (lhs.specular_factor != rhs.specular_factor) - return false; - - if (memcmp(lhs.specular_color_factor, rhs.specular_color_factor, sizeof(cgltf_float) * 3) != 0) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_sheen& lhs, const cgltf_sheen& rhs) -{ - if (!areTextureViewsEqual(lhs.sheen_color_texture, rhs.sheen_color_texture)) - return false; - - if (memcmp(lhs.sheen_color_factor, rhs.sheen_color_factor, sizeof(cgltf_float) * 3) != 0) - return false; - - if (!areTextureViewsEqual(lhs.sheen_roughness_texture, rhs.sheen_roughness_texture)) - return false; - - if (lhs.sheen_roughness_factor != rhs.sheen_roughness_factor) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_volume& lhs, const cgltf_volume& rhs) -{ - if (!areTextureViewsEqual(lhs.thickness_texture, rhs.thickness_texture)) - return false; - - if (lhs.thickness_factor != rhs.thickness_factor) - return false; - - if (memcmp(lhs.attenuation_color, rhs.attenuation_color, sizeof(cgltf_float) * 3) != 0) - return false; - - if (lhs.attenuation_distance != rhs.attenuation_distance) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_emissive_strength& lhs, const cgltf_emissive_strength& rhs) -{ - if (lhs.emissive_strength != rhs.emissive_strength) - return false; - - return true; -} - -static bool areMaterialComponentsEqual(const cgltf_iridescence& lhs, const cgltf_iridescence& rhs) -{ - if (lhs.iridescence_factor != rhs.iridescence_factor) - return false; - - if (!areTextureViewsEqual(lhs.iridescence_texture, rhs.iridescence_texture)) - return false; - - if (lhs.iridescence_ior != rhs.iridescence_ior) - return false; - - if (lhs.iridescence_thickness_min != rhs.iridescence_thickness_min) - return false; - - if (lhs.iridescence_thickness_max != rhs.iridescence_thickness_max) - return false; - - if (!areTextureViewsEqual(lhs.iridescence_thickness_texture, rhs.iridescence_thickness_texture)) - return false; - - return true; -} - -static bool areMaterialsEqual(cgltf_data* data, const cgltf_material& lhs, const cgltf_material& rhs, const Settings& settings) -{ - if (lhs.has_pbr_metallic_roughness != rhs.has_pbr_metallic_roughness) - return false; - - if (lhs.has_pbr_metallic_roughness && !areMaterialComponentsEqual(lhs.pbr_metallic_roughness, rhs.pbr_metallic_roughness)) - return false; - - if (lhs.has_pbr_specular_glossiness != rhs.has_pbr_specular_glossiness) - return false; - - if (lhs.has_pbr_specular_glossiness && !areMaterialComponentsEqual(lhs.pbr_specular_glossiness, rhs.pbr_specular_glossiness)) - return false; - - if (lhs.has_clearcoat != rhs.has_clearcoat) - return false; - - if (lhs.has_clearcoat && !areMaterialComponentsEqual(lhs.clearcoat, rhs.clearcoat)) - return false; - - if (lhs.has_transmission != rhs.has_transmission) - return false; - - if (lhs.has_transmission && !areMaterialComponentsEqual(lhs.transmission, rhs.transmission)) - return false; - - if (lhs.has_ior != rhs.has_ior) - return false; - - if (lhs.has_ior && !areMaterialComponentsEqual(lhs.ior, rhs.ior)) - return false; - - if (lhs.has_specular != rhs.has_specular) - return false; - - if (lhs.has_specular && !areMaterialComponentsEqual(lhs.specular, rhs.specular)) - return false; - - if (lhs.has_sheen != rhs.has_sheen) - return false; - - if (lhs.has_sheen && !areMaterialComponentsEqual(lhs.sheen, rhs.sheen)) - return false; - - if (lhs.has_volume != rhs.has_volume) - return false; - - if (lhs.has_volume && !areMaterialComponentsEqual(lhs.volume, rhs.volume)) - return false; - - if (lhs.has_emissive_strength != rhs.has_emissive_strength) - return false; - - if (lhs.has_emissive_strength && !areMaterialComponentsEqual(lhs.emissive_strength, rhs.emissive_strength)) - return false; - - if (lhs.has_iridescence != rhs.has_iridescence) - return false; - - if (lhs.has_iridescence && !areMaterialComponentsEqual(lhs.iridescence, rhs.iridescence)) - return false; - - if (!areTextureViewsEqual(lhs.normal_texture, rhs.normal_texture)) - return false; - - if (!areTextureViewsEqual(lhs.occlusion_texture, rhs.occlusion_texture)) - return false; - - if (!areTextureViewsEqual(lhs.emissive_texture, rhs.emissive_texture)) - return false; - - if (memcmp(lhs.emissive_factor, rhs.emissive_factor, sizeof(cgltf_float) * 3) != 0) - return false; - - if (lhs.alpha_mode != rhs.alpha_mode) - return false; - - if (lhs.alpha_cutoff != rhs.alpha_cutoff) - return false; - - if (lhs.double_sided != rhs.double_sided) - return false; - - if (lhs.unlit != rhs.unlit) - return false; - - if (settings.keep_extras && !areExtrasEqual(data, lhs.extras, rhs.extras)) - return false; - - return true; -} - -void mergeMeshMaterials(cgltf_data* data, std::vector& meshes, const Settings& settings) -{ - std::vector material_remap(data->materials_count); - - for (size_t i = 0; i < data->materials_count; ++i) - { - material_remap[i] = &data->materials[i]; - - if (settings.keep_materials && data->materials[i].name && *data->materials[i].name) - continue; - - for (size_t j = 0; j < i; ++j) - { - if (settings.keep_materials && data->materials[j].name && *data->materials[j].name) - continue; - - if (areMaterialsEqual(data, data->materials[i], data->materials[j], settings)) - { - material_remap[i] = &data->materials[j]; - break; - } - } - } - - for (size_t i = 0; i < meshes.size(); ++i) - { - Mesh& mesh = meshes[i]; - - if (mesh.material) - mesh.material = material_remap[mesh.material - data->materials]; - - for (size_t j = 0; j < mesh.variants.size(); ++j) - mesh.variants[j].material = material_remap[mesh.variants[j].material - data->materials]; - } -} - -void markNeededMaterials(cgltf_data* data, std::vector& materials, const std::vector& meshes, const Settings& settings) -{ - // mark all used materials as kept - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - if (mesh.material) - { - MaterialInfo& mi = materials[mesh.material - data->materials]; - - mi.keep = true; - } - - for (size_t j = 0; j < mesh.variants.size(); ++j) - { - MaterialInfo& mi = materials[mesh.variants[j].material - data->materials]; - - mi.keep = true; - } - } - - // mark all named materials as kept if requested - if (settings.keep_materials) - { - for (size_t i = 0; i < data->materials_count; ++i) - { - cgltf_material& material = data->materials[i]; - - if (material.name && *material.name) - { - materials[i].keep = true; - } - } - } -} - -bool hasValidTransform(const cgltf_texture_view& view) -{ - if (view.has_transform) - { - if (view.transform.offset[0] != 0.0f || view.transform.offset[1] != 0.0f || - view.transform.scale[0] != 1.0f || view.transform.scale[1] != 1.0f || - view.transform.rotation != 0.0f) - return true; - - if (view.transform.has_texcoord && view.transform.texcoord != view.texcoord) - return true; - } - - return false; -} - -static void analyzeMaterialTexture(const cgltf_texture_view& view, TextureKind kind, MaterialInfo& mi, cgltf_data* data, std::vector& images) -{ - mi.usesTextureTransform |= hasValidTransform(view); - - if (view.texture && view.texture->image) - { - ImageInfo& info = images[view.texture->image - data->images]; - - mi.textureSetMask |= 1u << view.texcoord; - mi.needsTangents |= (kind == TextureKind_Normal); - - if (info.kind == TextureKind_Generic) - info.kind = kind; - else if (info.kind > kind) // this is useful to keep color textures that have attrib data in alpha tagged as color - info.kind = kind; - - info.normal_map |= (kind == TextureKind_Normal); - info.srgb |= (kind == TextureKind_Color); - } -} - -static void analyzeMaterial(const cgltf_material& material, MaterialInfo& mi, cgltf_data* data, std::vector& images) -{ - if (material.has_pbr_metallic_roughness) - { - analyzeMaterialTexture(material.pbr_metallic_roughness.base_color_texture, TextureKind_Color, mi, data, images); - analyzeMaterialTexture(material.pbr_metallic_roughness.metallic_roughness_texture, TextureKind_Attrib, mi, data, images); - } - - if (material.has_pbr_specular_glossiness) - { - analyzeMaterialTexture(material.pbr_specular_glossiness.diffuse_texture, TextureKind_Color, mi, data, images); - analyzeMaterialTexture(material.pbr_specular_glossiness.specular_glossiness_texture, TextureKind_Attrib, mi, data, images); - } - - if (material.has_clearcoat) - { - analyzeMaterialTexture(material.clearcoat.clearcoat_texture, TextureKind_Attrib, mi, data, images); - analyzeMaterialTexture(material.clearcoat.clearcoat_roughness_texture, TextureKind_Attrib, mi, data, images); - analyzeMaterialTexture(material.clearcoat.clearcoat_normal_texture, TextureKind_Normal, mi, data, images); - } - - if (material.has_transmission) - { - analyzeMaterialTexture(material.transmission.transmission_texture, TextureKind_Attrib, mi, data, images); - } - - if (material.has_specular) - { - analyzeMaterialTexture(material.specular.specular_texture, TextureKind_Attrib, mi, data, images); - analyzeMaterialTexture(material.specular.specular_color_texture, TextureKind_Color, mi, data, images); - } - - if (material.has_sheen) - { - analyzeMaterialTexture(material.sheen.sheen_color_texture, TextureKind_Color, mi, data, images); - analyzeMaterialTexture(material.sheen.sheen_roughness_texture, TextureKind_Attrib, mi, data, images); - } - - if (material.has_volume) - { - analyzeMaterialTexture(material.volume.thickness_texture, TextureKind_Attrib, mi, data, images); - } - - if (material.has_iridescence) - { - analyzeMaterialTexture(material.iridescence.iridescence_texture, TextureKind_Attrib, mi, data, images); - analyzeMaterialTexture(material.iridescence.iridescence_thickness_texture, TextureKind_Attrib, mi, data, images); - } - - analyzeMaterialTexture(material.normal_texture, TextureKind_Normal, mi, data, images); - analyzeMaterialTexture(material.occlusion_texture, TextureKind_Attrib, mi, data, images); - analyzeMaterialTexture(material.emissive_texture, TextureKind_Color, mi, data, images); -} - -void analyzeMaterials(cgltf_data* data, std::vector& materials, std::vector& images) -{ - for (size_t i = 0; i < data->materials_count; ++i) - { - analyzeMaterial(data->materials[i], materials[i], data, images); - } -} - -static const cgltf_texture_view* getColorTexture(const cgltf_material& material) -{ - if (material.has_pbr_metallic_roughness) - return &material.pbr_metallic_roughness.base_color_texture; - - if (material.has_pbr_specular_glossiness) - return &material.pbr_specular_glossiness.diffuse_texture; - - return NULL; -} - -static float getAlphaFactor(const cgltf_material& material) -{ - if (material.has_pbr_metallic_roughness) - return material.pbr_metallic_roughness.base_color_factor[3]; - - if (material.has_pbr_specular_glossiness) - return material.pbr_specular_glossiness.diffuse_factor[3]; - - return 1.f; -} - -static int getChannels(const cgltf_image& image, ImageInfo& info, const char* input_path) -{ - if (info.channels) - return info.channels; - - std::string img_data; - std::string mime_type; - if (readImage(image, input_path, img_data, mime_type)) - info.channels = hasAlpha(img_data, mime_type.c_str()) ? 4 : 3; - else - info.channels = -1; - - return info.channels; -} - -void optimizeMaterials(cgltf_data* data, const char* input_path, std::vector& images) -{ - for (size_t i = 0; i < data->materials_count; ++i) - { - // remove BLEND/MASK from materials that don't have alpha information - if (data->materials[i].alpha_mode != cgltf_alpha_mode_opaque) - { - const cgltf_texture_view* color = getColorTexture(data->materials[i]); - float alpha = getAlphaFactor(data->materials[i]); - - if (alpha == 1.f && !(color && color->texture && color->texture->image && getChannels(*color->texture->image, images[color->texture->image - data->images], input_path) == 4)) - { - data->materials[i].alpha_mode = cgltf_alpha_mode_opaque; - } - } - } -} diff --git a/Dependencies/meshoptimizer/gltf/mesh.cpp b/Dependencies/meshoptimizer/gltf/mesh.cpp deleted file mode 100644 index 9278f2ba..00000000 --- a/Dependencies/meshoptimizer/gltf/mesh.cpp +++ /dev/null @@ -1,996 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include - -#include -#include - -#include "../src/meshoptimizer.h" - -static float inverseTranspose(float* result, const float* transform) -{ - float m[4][4] = {}; - memcpy(m, transform, 16 * sizeof(float)); - - float det = - m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) - - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + - m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); - - float invdet = (det == 0.f) ? 0.f : 1.f / det; - - float r[4][4] = {}; - - r[0][0] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * invdet; - r[1][0] = (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * invdet; - r[2][0] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * invdet; - r[0][1] = (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * invdet; - r[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * invdet; - r[2][1] = (m[1][0] * m[0][2] - m[0][0] * m[1][2]) * invdet; - r[0][2] = (m[1][0] * m[2][1] - m[2][0] * m[1][1]) * invdet; - r[1][2] = (m[2][0] * m[0][1] - m[0][0] * m[2][1]) * invdet; - r[2][2] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * invdet; - - r[3][3] = 1.f; - - memcpy(result, r, 16 * sizeof(float)); - - return det; -} - -static void transformPosition(float* res, const float* ptr, const float* transform) -{ - float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12]; - float y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13]; - float z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14]; - - res[0] = x; - res[1] = y; - res[2] = z; -} - -static void transformNormal(float* res, const float* ptr, const float* transform) -{ - float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8]; - float y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9]; - float z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10]; - - float l = sqrtf(x * x + y * y + z * z); - float s = (l == 0.f) ? 0.f : 1 / l; - - res[0] = x * s; - res[1] = y * s; - res[2] = z * s; -} - -// assumes mesh & target are structurally identical -static void transformMesh(Mesh& target, const Mesh& mesh, const cgltf_node* node) -{ - assert(target.streams.size() == mesh.streams.size()); - assert(target.indices.size() == mesh.indices.size()); - - float transform[16]; - cgltf_node_transform_world(node, transform); - - float transforminvt[16]; - float det = inverseTranspose(transforminvt, transform); - - for (size_t si = 0; si < mesh.streams.size(); ++si) - { - const Stream& source = mesh.streams[si]; - Stream& stream = target.streams[si]; - - assert(source.type == stream.type); - assert(source.data.size() == stream.data.size()); - - if (stream.type == cgltf_attribute_type_position) - { - for (size_t i = 0; i < stream.data.size(); ++i) - transformPosition(stream.data[i].f, source.data[i].f, transform); - } - else if (stream.type == cgltf_attribute_type_normal) - { - for (size_t i = 0; i < stream.data.size(); ++i) - transformNormal(stream.data[i].f, source.data[i].f, transforminvt); - } - else if (stream.type == cgltf_attribute_type_tangent) - { - for (size_t i = 0; i < stream.data.size(); ++i) - transformNormal(stream.data[i].f, source.data[i].f, transform); - } - } - - // copy indices so that we can modify them below - target.indices = mesh.indices; - - if (det < 0 && mesh.type == cgltf_primitive_type_triangles) - { - // negative scale means we need to flip face winding - for (size_t i = 0; i < target.indices.size(); i += 3) - std::swap(target.indices[i + 0], target.indices[i + 1]); - } -} - -bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs) -{ - if (lhs.targets != rhs.targets) - return false; - - if (lhs.target_weights.size() != rhs.target_weights.size()) - return false; - - for (size_t i = 0; i < lhs.target_weights.size(); ++i) - if (lhs.target_weights[i] != rhs.target_weights[i]) - return false; - - if (lhs.target_names.size() != rhs.target_names.size()) - return false; - - for (size_t i = 0; i < lhs.target_names.size(); ++i) - if (strcmp(lhs.target_names[i], rhs.target_names[i]) != 0) - return false; - - return true; -} - -bool compareMeshVariants(const Mesh& lhs, const Mesh& rhs) -{ - if (lhs.variants.size() != rhs.variants.size()) - return false; - - for (size_t i = 0; i < lhs.variants.size(); ++i) - { - if (lhs.variants[i].variant != rhs.variants[i].variant) - return false; - - if (lhs.variants[i].material != rhs.variants[i].material) - return false; - } - - return true; -} - -bool compareMeshNodes(const Mesh& lhs, const Mesh& rhs) -{ - if (lhs.nodes.size() != rhs.nodes.size()) - return false; - - for (size_t i = 0; i < lhs.nodes.size(); ++i) - if (lhs.nodes[i] != rhs.nodes[i]) - return false; - - return true; -} - -static bool canMergeMeshNodes(cgltf_node* lhs, cgltf_node* rhs, const Settings& settings) -{ - if (lhs == rhs) - return true; - - if (lhs->parent != rhs->parent) - return false; - - bool lhs_transform = lhs->has_translation | lhs->has_rotation | lhs->has_scale | lhs->has_matrix | (!!lhs->weights); - bool rhs_transform = rhs->has_translation | rhs->has_rotation | rhs->has_scale | rhs->has_matrix | (!!rhs->weights); - - if (lhs_transform || rhs_transform) - return false; - - if (settings.keep_nodes) - { - if (lhs->name && *lhs->name) - return false; - - if (rhs->name && *rhs->name) - return false; - } - - // we can merge nodes that don't have transforms of their own and have the same parent - // this is helpful when instead of splitting mesh into primitives, DCCs split mesh into mesh nodes - return true; -} - -static bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings) -{ - if (lhs.scene != rhs.scene) - return false; - - if (lhs.nodes.size() != rhs.nodes.size()) - return false; - - for (size_t i = 0; i < lhs.nodes.size(); ++i) - if (!canMergeMeshNodes(lhs.nodes[i], rhs.nodes[i], settings)) - return false; - - if (lhs.instances.size() || rhs.instances.size()) - return false; - - if (lhs.material != rhs.material) - return false; - - if (lhs.skin != rhs.skin) - return false; - - if (lhs.type != rhs.type) - return false; - - if (!compareMeshTargets(lhs, rhs)) - return false; - - if (!compareMeshVariants(lhs, rhs)) - return false; - - if (lhs.indices.empty() != rhs.indices.empty()) - return false; - - if (lhs.streams.size() != rhs.streams.size()) - return false; - - for (size_t i = 0; i < lhs.streams.size(); ++i) - if (lhs.streams[i].type != rhs.streams[i].type || lhs.streams[i].index != rhs.streams[i].index || lhs.streams[i].target != rhs.streams[i].target) - return false; - - return true; -} - -static void mergeMeshes(Mesh& target, const Mesh& mesh) -{ - assert(target.streams.size() == mesh.streams.size()); - - size_t vertex_offset = target.streams[0].data.size(); - size_t index_offset = target.indices.size(); - - for (size_t i = 0; i < target.streams.size(); ++i) - target.streams[i].data.insert(target.streams[i].data.end(), mesh.streams[i].data.begin(), mesh.streams[i].data.end()); - - target.indices.resize(target.indices.size() + mesh.indices.size()); - - size_t index_count = mesh.indices.size(); - - for (size_t i = 0; i < index_count; ++i) - target.indices[index_offset + i] = unsigned(vertex_offset + mesh.indices[i]); -} - -void mergeMeshInstances(Mesh& mesh) -{ - if (mesh.nodes.empty()) - return; - - // fast-path: for single instance meshes we transform in-place - if (mesh.nodes.size() == 1) - { - transformMesh(mesh, mesh, mesh.nodes[0]); - mesh.nodes.clear(); - return; - } - - Mesh base = mesh; - Mesh transformed = base; - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - mesh.streams[i].data.clear(); - mesh.streams[i].data.reserve(base.streams[i].data.size() * mesh.nodes.size()); - } - - mesh.indices.clear(); - mesh.indices.reserve(base.indices.size() * mesh.nodes.size()); - - for (size_t i = 0; i < mesh.nodes.size(); ++i) - { - transformMesh(transformed, base, mesh.nodes[i]); - mergeMeshes(mesh, transformed); - } - - mesh.nodes.clear(); -} - -void mergeMeshes(std::vector& meshes, const Settings& settings) -{ - for (size_t i = 0; i < meshes.size(); ++i) - { - Mesh& target = meshes[i]; - - if (target.streams.empty()) - continue; - - size_t target_vertices = target.streams[0].data.size(); - size_t target_indices = target.indices.size(); - - size_t last_merged = i; - - for (size_t j = i + 1; j < meshes.size(); ++j) - { - Mesh& mesh = meshes[j]; - - if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) - { - target_vertices += mesh.streams[0].data.size(); - target_indices += mesh.indices.size(); - last_merged = j; - } - } - - for (size_t j = 0; j < target.streams.size(); ++j) - target.streams[j].data.reserve(target_vertices); - - target.indices.reserve(target_indices); - - for (size_t j = i + 1; j <= last_merged; ++j) - { - Mesh& mesh = meshes[j]; - - if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) - { - mergeMeshes(target, mesh); - - mesh.streams.clear(); - mesh.indices.clear(); - mesh.nodes.clear(); - mesh.instances.clear(); - } - } - - assert(target.streams[0].data.size() == target_vertices); - assert(target.indices.size() == target_indices); - } -} - -void filterEmptyMeshes(std::vector& meshes) -{ - size_t write = 0; - - for (size_t i = 0; i < meshes.size(); ++i) - { - Mesh& mesh = meshes[i]; - - if (mesh.streams.empty()) - continue; - - if (mesh.streams[0].data.empty()) - continue; - - if (mesh.type != cgltf_primitive_type_points && mesh.indices.empty()) - continue; - - // the following code is roughly equivalent to meshes[write] = std::move(mesh) - std::vector streams; - streams.swap(mesh.streams); - - std::vector indices; - indices.swap(mesh.indices); - - meshes[write] = mesh; - meshes[write].streams.swap(streams); - meshes[write].indices.swap(indices); - - write++; - } - - meshes.resize(write); -} - -static bool hasColors(const std::vector& data) -{ - const float threshold = 0.99f; - - for (size_t i = 0; i < data.size(); ++i) - { - const Attr& a = data[i]; - - if (a.f[0] < threshold || a.f[1] < threshold || a.f[2] < threshold || a.f[3] < threshold) - return true; - } - - return false; -} - -static bool hasDeltas(const std::vector& data) -{ - const float threshold = 0.01f; - - for (size_t i = 0; i < data.size(); ++i) - { - const Attr& a = data[i]; - - if (fabsf(a.f[0]) > threshold || fabsf(a.f[1]) > threshold || fabsf(a.f[2]) > threshold) - return true; - } - - return false; -} - -void filterStreams(Mesh& mesh, const MaterialInfo& mi) -{ - bool morph_normal = false; - bool morph_tangent = false; - int keep_texture_set = -1; - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - Stream& stream = mesh.streams[i]; - - if (stream.target) - { - morph_normal = morph_normal || (stream.type == cgltf_attribute_type_normal && hasDeltas(stream.data)); - morph_tangent = morph_tangent || (stream.type == cgltf_attribute_type_tangent && hasDeltas(stream.data)); - } - - if (stream.type == cgltf_attribute_type_texcoord && (mi.textureSetMask & (1u << stream.index)) != 0) - { - keep_texture_set = std::max(keep_texture_set, stream.index); - } - } - - size_t write = 0; - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - Stream& stream = mesh.streams[i]; - - if (stream.type == cgltf_attribute_type_texcoord && stream.index > keep_texture_set) - continue; - - if (stream.type == cgltf_attribute_type_tangent && !mi.needsTangents) - continue; - - if ((stream.type == cgltf_attribute_type_joints || stream.type == cgltf_attribute_type_weights) && !mesh.skin) - continue; - - if (stream.type == cgltf_attribute_type_color && !hasColors(stream.data)) - continue; - - if (stream.target && stream.type == cgltf_attribute_type_normal && !morph_normal) - continue; - - if (stream.target && stream.type == cgltf_attribute_type_tangent && !morph_tangent) - continue; - - // the following code is roughly equivalent to streams[write] = std::move(stream) - std::vector data; - data.swap(stream.data); - - mesh.streams[write] = stream; - mesh.streams[write].data.swap(data); - - write++; - } - - mesh.streams.resize(write); -} - -static void reindexMesh(Mesh& mesh) -{ - size_t total_vertices = mesh.streams[0].data.size(); - size_t total_indices = mesh.indices.size(); - - std::vector streams; - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - if (mesh.streams[i].target) - continue; - - assert(mesh.streams[i].data.size() == total_vertices); - - meshopt_Stream stream = {&mesh.streams[i].data[0], sizeof(Attr), sizeof(Attr)}; - streams.push_back(stream); - } - - if (streams.empty()) - return; - - std::vector remap(total_vertices); - size_t unique_vertices = meshopt_generateVertexRemapMulti(&remap[0], &mesh.indices[0], total_indices, total_vertices, &streams[0], streams.size()); - assert(unique_vertices <= total_vertices); - - meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], total_indices, &remap[0]); - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - assert(mesh.streams[i].data.size() == total_vertices); - - meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], total_vertices, sizeof(Attr), &remap[0]); - mesh.streams[i].data.resize(unique_vertices); - } -} - -static void filterTriangles(Mesh& mesh) -{ - assert(mesh.type == cgltf_primitive_type_triangles); - - unsigned int* indices = &mesh.indices[0]; - size_t total_indices = mesh.indices.size(); - - size_t write = 0; - - for (size_t i = 0; i < total_indices; i += 3) - { - unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2]; - - if (a != b && a != c && b != c) - { - indices[write + 0] = a; - indices[write + 1] = b; - indices[write + 2] = c; - write += 3; - } - } - - mesh.indices.resize(write); -} - -static Stream* getStream(Mesh& mesh, cgltf_attribute_type type, int index = 0) -{ - for (size_t i = 0; i < mesh.streams.size(); ++i) - if (mesh.streams[i].type == type && mesh.streams[i].index == index) - return &mesh.streams[i]; - - return 0; -} - -static void simplifyMesh(Mesh& mesh, float threshold, bool aggressive) -{ - assert(mesh.type == cgltf_primitive_type_triangles); - - const Stream* positions = getStream(mesh, cgltf_attribute_type_position); - if (!positions) - return; - - size_t vertex_count = mesh.streams[0].data.size(); - - size_t target_index_count = size_t(double(mesh.indices.size() / 3) * threshold) * 3; - float target_error = 1e-2f; - float target_error_aggressive = 1e-1f; - - if (target_index_count < 1) - return; - - std::vector indices(mesh.indices.size()); - indices.resize(meshopt_simplify(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count, target_error)); - mesh.indices.swap(indices); - - // Note: if the simplifier got stuck, we can try to reindex without normals/tangents and retry - // For now we simply fall back to aggressive simplifier instead - - // if the precise simplifier got "stuck", we'll try to simplify using the sloppy simplifier; this is only used when aggressive simplification is enabled as it breaks attribute discontinuities - if (aggressive && mesh.indices.size() > target_index_count) - { - indices.resize(meshopt_simplifySloppy(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count, target_error_aggressive)); - mesh.indices.swap(indices); - } -} - -static void optimizeMesh(Mesh& mesh, bool compressmore) -{ - assert(mesh.type == cgltf_primitive_type_triangles); - - size_t vertex_count = mesh.streams[0].data.size(); - - if (compressmore) - meshopt_optimizeVertexCacheStrip(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), vertex_count); - else - meshopt_optimizeVertexCache(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), vertex_count); - - std::vector remap(vertex_count); - size_t unique_vertices = meshopt_optimizeVertexFetchRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), vertex_count); - assert(unique_vertices <= vertex_count); - - meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &remap[0]); - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - assert(mesh.streams[i].data.size() == vertex_count); - - meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]); - mesh.streams[i].data.resize(unique_vertices); - } -} - -struct BoneInfluence -{ - float i; - float w; -}; - -struct BoneInfluenceWeightPredicate -{ - bool operator()(const BoneInfluence& lhs, const BoneInfluence& rhs) const - { - return lhs.w > rhs.w; - } -}; - -static void filterBones(Mesh& mesh) -{ - const int kMaxGroups = 8; - - std::pair groups[kMaxGroups]; - int group_count = 0; - - // gather all joint/weight groups; each group contains 4 bone influences - for (int i = 0; i < kMaxGroups; ++i) - { - Stream* jg = getStream(mesh, cgltf_attribute_type_joints, int(i)); - Stream* wg = getStream(mesh, cgltf_attribute_type_weights, int(i)); - - if (!jg || !wg) - break; - - groups[group_count++] = std::make_pair(jg, wg); - } - - if (group_count == 0) - return; - - // weights below cutoff can't be represented in quantized 8-bit storage - const float weight_cutoff = 0.5f / 255.f; - - size_t vertex_count = mesh.streams[0].data.size(); - - BoneInfluence inf[kMaxGroups * 4] = {}; - - for (size_t i = 0; i < vertex_count; ++i) - { - int count = 0; - - // gather all bone influences for this vertex - for (int j = 0; j < group_count; ++j) - { - const Attr& ja = groups[j].first->data[i]; - const Attr& wa = groups[j].second->data[i]; - - for (int k = 0; k < 4; ++k) - if (wa.f[k] > weight_cutoff) - { - inf[count].i = ja.f[k]; - inf[count].w = wa.f[k]; - count++; - } - } - - // pick top 4 influences; this also sorts resulting influences by weight which helps renderers that use influence subset in shader LODs - std::sort(inf, inf + count, BoneInfluenceWeightPredicate()); - - // copy the top 4 influences back into stream 0 - we will remove other streams at the end - Attr& ja = groups[0].first->data[i]; - Attr& wa = groups[0].second->data[i]; - - for (int k = 0; k < 4; ++k) - { - if (k < count) - { - ja.f[k] = inf[k].i; - wa.f[k] = inf[k].w; - } - else - { - ja.f[k] = 0.f; - wa.f[k] = 0.f; - } - } - } - - // remove redundant weight/joint streams - for (size_t i = 0; i < mesh.streams.size();) - { - Stream& s = mesh.streams[i]; - - if ((s.type == cgltf_attribute_type_joints || s.type == cgltf_attribute_type_weights) && s.index > 0) - mesh.streams.erase(mesh.streams.begin() + i); - else - ++i; - } -} - -static void simplifyPointMesh(Mesh& mesh, float threshold) -{ - assert(mesh.type == cgltf_primitive_type_points); - - if (threshold >= 1) - return; - - const Stream* positions = getStream(mesh, cgltf_attribute_type_position); - if (!positions) - return; - - size_t vertex_count = mesh.streams[0].data.size(); - - size_t target_vertex_count = size_t(double(vertex_count) * threshold); - - if (target_vertex_count < 1) - return; - - std::vector indices(target_vertex_count); - indices.resize(meshopt_simplifyPoints(&indices[0], positions->data[0].f, vertex_count, sizeof(Attr), target_vertex_count)); - - std::vector scratch(indices.size()); - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - std::vector& data = mesh.streams[i].data; - - assert(data.size() == vertex_count); - - for (size_t j = 0; j < indices.size(); ++j) - scratch[j] = data[indices[j]]; - - data = scratch; - } -} - -static void sortPointMesh(Mesh& mesh) -{ - assert(mesh.type == cgltf_primitive_type_points); - - const Stream* positions = getStream(mesh, cgltf_attribute_type_position); - if (!positions) - return; - - size_t vertex_count = mesh.streams[0].data.size(); - - std::vector remap(vertex_count); - meshopt_spatialSortRemap(&remap[0], positions->data[0].f, vertex_count, sizeof(Attr)); - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - assert(mesh.streams[i].data.size() == vertex_count); - - meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]); - } -} - -void processMesh(Mesh& mesh, const Settings& settings) -{ - switch (mesh.type) - { - case cgltf_primitive_type_points: - assert(mesh.indices.empty()); - simplifyPointMesh(mesh, settings.simplify_threshold); - sortPointMesh(mesh); - break; - - case cgltf_primitive_type_lines: - break; - - case cgltf_primitive_type_triangles: - filterBones(mesh); - reindexMesh(mesh); - filterTriangles(mesh); - if (settings.simplify_threshold < 1) - simplifyMesh(mesh, settings.simplify_threshold, settings.simplify_aggressive); - optimizeMesh(mesh, settings.compressmore); - break; - - default: - assert(!"Unknown primitive type"); - } -} - -#ifndef NDEBUG -extern MESHOPTIMIZER_API unsigned char* meshopt_simplifyDebugKind; -extern MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoop; -extern MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoopBack; - -void debugSimplify(const Mesh& source, Mesh& kinds, Mesh& loops, float ratio) -{ - Mesh mesh = source; - assert(mesh.type == cgltf_primitive_type_triangles); - - // note: it's important to follow the same pipeline as processMesh - // otherwise the result won't match - filterBones(mesh); - reindexMesh(mesh); - filterTriangles(mesh); - - // before simplification we need to setup target kind/loop arrays - size_t vertex_count = mesh.streams[0].data.size(); - - std::vector kind(vertex_count); - std::vector loop(vertex_count); - std::vector loopback(vertex_count); - std::vector live(vertex_count); - - meshopt_simplifyDebugKind = &kind[0]; - meshopt_simplifyDebugLoop = &loop[0]; - meshopt_simplifyDebugLoopBack = &loopback[0]; - - simplifyMesh(mesh, ratio, /* aggressive= */ false); - - meshopt_simplifyDebugKind = 0; - meshopt_simplifyDebugLoop = 0; - meshopt_simplifyDebugLoopBack = 0; - - // fill out live info - for (size_t i = 0; i < mesh.indices.size(); ++i) - live[mesh.indices[i]] = true; - - // color palette for display - static const Attr kPalette[] = { - {0.5f, 0.5f, 0.5f, 1.f}, // manifold - {0.f, 0.f, 1.f, 1.f}, // border - {0.f, 1.f, 0.f, 1.f}, // seam - {0.f, 1.f, 1.f, 1.f}, // complex - {1.f, 0.f, 0.f, 1.f}, // locked - }; - - // prepare meshes - kinds.nodes = mesh.nodes; - kinds.skin = mesh.skin; - - loops.nodes = mesh.nodes; - loops.skin = mesh.skin; - - for (size_t i = 0; i < mesh.streams.size(); ++i) - { - const Stream& stream = mesh.streams[i]; - - if (stream.target == 0 && (stream.type == cgltf_attribute_type_position || stream.type == cgltf_attribute_type_joints || stream.type == cgltf_attribute_type_weights)) - { - kinds.streams.push_back(stream); - loops.streams.push_back(stream); - } - } - - // transform kind/loop data into lines & points - Stream colors = {cgltf_attribute_type_color}; - colors.data.resize(vertex_count); - - for (size_t i = 0; i < vertex_count; ++i) - colors.data[i] = kPalette[kind[i]]; - - kinds.type = cgltf_primitive_type_points; - - kinds.streams.push_back(colors); - - for (size_t i = 0; i < vertex_count; ++i) - if (live[i] && kind[i] != 0) - kinds.indices.push_back(unsigned(i)); - - loops.type = cgltf_primitive_type_lines; - - loops.streams.push_back(colors); - - for (size_t i = 0; i < vertex_count; ++i) - if (live[i] && (kind[i] == 1 || kind[i] == 2)) - { - if (loop[i] != ~0u && live[loop[i]]) - { - loops.indices.push_back(unsigned(i)); - loops.indices.push_back(loop[i]); - } - - if (loopback[i] != ~0u && live[loopback[i]]) - { - loops.indices.push_back(loopback[i]); - loops.indices.push_back(unsigned(i)); - } - } -} - -void debugMeshlets(const Mesh& source, Mesh& meshlets, Mesh& bounds, int max_vertices, bool scan) -{ - Mesh mesh = source; - assert(mesh.type == cgltf_primitive_type_triangles); - - reindexMesh(mesh); - - if (scan) - optimizeMesh(mesh, false); - - const Stream* positions = getStream(mesh, cgltf_attribute_type_position); - assert(positions); - - const float cone_weight = 0.f; - - size_t max_triangles = (max_vertices * 2 + 3) & ~3; - size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles); - - std::vector ml(max_meshlets); - std::vector mlv(max_meshlets * max_vertices); - std::vector mlt(max_meshlets * max_triangles * 3); - - if (scan) - ml.resize(meshopt_buildMeshletsScan(&ml[0], &mlv[0], &mlt[0], &mesh.indices[0], mesh.indices.size(), positions->data.size(), max_vertices, max_triangles)); - else - ml.resize(meshopt_buildMeshlets(&ml[0], &mlv[0], &mlt[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, positions->data.size(), sizeof(Attr), max_vertices, max_triangles, cone_weight)); - - // generate meshlet meshes, using unique colors - meshlets.nodes = mesh.nodes; - - Stream mv = {cgltf_attribute_type_position}; - Stream mc = {cgltf_attribute_type_color}; - - for (size_t i = 0; i < ml.size(); ++i) - { - const meshopt_Meshlet& m = ml[i]; - - unsigned int h = unsigned(i); - h ^= h >> 13; - h *= 0x5bd1e995; - h ^= h >> 15; - - Attr c = {{float(h & 0xff) / 255.f, float((h >> 8) & 0xff) / 255.f, float((h >> 16) & 0xff) / 255.f, 1.f}}; - - unsigned int offset = unsigned(mv.data.size()); - - for (size_t j = 0; j < m.vertex_count; ++j) - { - mv.data.push_back(positions->data[mlv[m.vertex_offset + j]]); - mc.data.push_back(c); - } - - for (size_t j = 0; j < m.triangle_count; ++j) - { - meshlets.indices.push_back(offset + mlt[m.triangle_offset + j * 3 + 0]); - meshlets.indices.push_back(offset + mlt[m.triangle_offset + j * 3 + 1]); - meshlets.indices.push_back(offset + mlt[m.triangle_offset + j * 3 + 2]); - } - } - - meshlets.type = cgltf_primitive_type_triangles; - meshlets.streams.push_back(mv); - meshlets.streams.push_back(mc); - - // generate bounds meshes, using a sphere per meshlet - bounds.nodes = mesh.nodes; - - Stream bv = {cgltf_attribute_type_position}; - Stream bc = {cgltf_attribute_type_color}; - - for (size_t i = 0; i < ml.size(); ++i) - { - const meshopt_Meshlet& m = ml[i]; - - meshopt_Bounds mb = meshopt_computeMeshletBounds(&mlv[m.vertex_offset], &mlt[m.triangle_offset], m.triangle_count, positions->data[0].f, positions->data.size(), sizeof(Attr)); - - unsigned int h = unsigned(i); - h ^= h >> 13; - h *= 0x5bd1e995; - h ^= h >> 15; - - Attr c = {{float(h & 0xff) / 255.f, float((h >> 8) & 0xff) / 255.f, float((h >> 16) & 0xff) / 255.f, 0.1f}}; - - unsigned int offset = unsigned(bv.data.size()); - - const int N = 10; - - for (int y = 0; y <= N; ++y) - { - float u = (y == N) ? 0 : float(y) / N * 2 * 3.1415926f; - float sinu = sinf(u), cosu = cosf(u); - - for (int x = 0; x <= N; ++x) - { - float v = float(x) / N * 3.1415926f; - float sinv = sinf(v), cosv = cosf(v); - - float fx = sinv * cosu; - float fy = sinv * sinu; - float fz = cosv; - - Attr p = {{mb.center[0] + mb.radius * fx, mb.center[1] + mb.radius * fy, mb.center[2] + mb.radius * fz, 1.f}}; - - bv.data.push_back(p); - bc.data.push_back(c); - } - } - - for (int y = 0; y < N; ++y) - for (int x = 0; x < N; ++x) - { - bounds.indices.push_back(offset + (N + 1) * (y + 0) + (x + 0)); - bounds.indices.push_back(offset + (N + 1) * (y + 0) + (x + 1)); - bounds.indices.push_back(offset + (N + 1) * (y + 1) + (x + 0)); - - bounds.indices.push_back(offset + (N + 1) * (y + 1) + (x + 0)); - bounds.indices.push_back(offset + (N + 1) * (y + 0) + (x + 1)); - bounds.indices.push_back(offset + (N + 1) * (y + 1) + (x + 1)); - } - } - - bounds.type = cgltf_primitive_type_triangles; - bounds.streams.push_back(bv); - bounds.streams.push_back(bc); -} -#endif diff --git a/Dependencies/meshoptimizer/gltf/node.cpp b/Dependencies/meshoptimizer/gltf/node.cpp deleted file mode 100644 index 5186e533..00000000 --- a/Dependencies/meshoptimizer/gltf/node.cpp +++ /dev/null @@ -1,206 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include -#include - -void markScenes(cgltf_data* data, std::vector& nodes) -{ - for (size_t i = 0; i < nodes.size(); ++i) - nodes[i].scene = -1; - - for (size_t i = 0; i < data->scenes_count; ++i) - for (size_t j = 0; j < data->scenes[i].nodes_count; ++j) - { - NodeInfo& ni = nodes[data->scenes[i].nodes[j] - data->nodes]; - - if (ni.scene >= 0) - ni.scene = -2; // multiple scenes - else - ni.scene = int(i); - } - - for (size_t i = 0; i < data->nodes_count; ++i) - { - cgltf_node* root = &data->nodes[i]; - while (root->parent) - root = root->parent; - - nodes[i].scene = nodes[root - data->nodes].scene; - } -} - -void markAnimated(cgltf_data* data, std::vector& nodes, const std::vector& animations) -{ - for (size_t i = 0; i < animations.size(); ++i) - { - const Animation& animation = animations[i]; - - for (size_t j = 0; j < animation.tracks.size(); ++j) - { - const Track& track = animation.tracks[j]; - - // mark nodes that have animation tracks that change their base transform as animated - if (!track.dummy) - { - NodeInfo& ni = nodes[track.node - data->nodes]; - - ni.animated_paths |= (1 << track.path); - } - } - } - - for (size_t i = 0; i < data->nodes_count; ++i) - { - NodeInfo& ni = nodes[i]; - - for (cgltf_node* node = &data->nodes[i]; node; node = node->parent) - ni.animated |= nodes[node - data->nodes].animated_paths != 0; - } -} - -void markNeededNodes(cgltf_data* data, std::vector& nodes, const std::vector& meshes, const std::vector& animations, const Settings& settings) -{ - // mark all joints as kept - for (size_t i = 0; i < data->skins_count; ++i) - { - const cgltf_skin& skin = data->skins[i]; - - // for now we keep all joints directly referenced by the skin and the entire ancestry tree; we keep names for joints as well - for (size_t j = 0; j < skin.joints_count; ++j) - { - NodeInfo& ni = nodes[skin.joints[j] - data->nodes]; - - ni.keep = true; - } - } - - // mark all animated nodes as kept - for (size_t i = 0; i < animations.size(); ++i) - { - const Animation& animation = animations[i]; - - for (size_t j = 0; j < animation.tracks.size(); ++j) - { - const Track& track = animation.tracks[j]; - - if (settings.anim_const || !track.dummy) - { - NodeInfo& ni = nodes[track.node - data->nodes]; - - ni.keep = true; - } - } - } - - // mark all mesh nodes as kept - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - for (size_t j = 0; j < mesh.nodes.size(); ++j) - { - NodeInfo& ni = nodes[mesh.nodes[j] - data->nodes]; - - ni.keep = true; - } - } - - // mark all light/camera nodes as kept - for (size_t i = 0; i < data->nodes_count; ++i) - { - const cgltf_node& node = data->nodes[i]; - - if (node.light || node.camera) - { - nodes[i].keep = true; - } - } - - // mark all named nodes as needed (if -kn is specified) - if (settings.keep_nodes) - { - for (size_t i = 0; i < data->nodes_count; ++i) - { - const cgltf_node& node = data->nodes[i]; - - if (node.name && *node.name) - { - nodes[i].keep = true; - } - } - } -} - -void remapNodes(cgltf_data* data, std::vector& nodes, size_t& node_offset) -{ - // to keep a node, we currently need to keep the entire ancestry chain - for (size_t i = 0; i < data->nodes_count; ++i) - { - if (!nodes[i].keep) - continue; - - for (cgltf_node* node = &data->nodes[i]; node; node = node->parent) - nodes[node - data->nodes].keep = true; - } - - // generate sequential indices for all nodes; they aren't sorted topologically - for (size_t i = 0; i < data->nodes_count; ++i) - { - NodeInfo& ni = nodes[i]; - - if (ni.keep) - { - ni.remap = int(node_offset); - - node_offset++; - } - } -} - -void decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform) -{ - float m[4][4] = {}; - memcpy(m, transform, 16 * sizeof(float)); - - // extract translation from last row - translation[0] = m[3][0]; - translation[1] = m[3][1]; - translation[2] = m[3][2]; - - // compute determinant to determine handedness - float det = - m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) - - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + - m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); - - float sign = (det < 0.f) ? -1.f : 1.f; - - // recover scale from axis lengths - scale[0] = sqrtf(m[0][0] * m[0][0] + m[1][0] * m[1][0] + m[2][0] * m[2][0]) * sign; - scale[1] = sqrtf(m[0][1] * m[0][1] + m[1][1] * m[1][1] + m[2][1] * m[2][1]) * sign; - scale[2] = sqrtf(m[0][2] * m[0][2] + m[1][2] * m[1][2] + m[2][2] * m[2][2]) * sign; - - // normalize axes to get a pure rotation matrix - float rsx = (scale[0] == 0.f) ? 0.f : 1.f / scale[0]; - float rsy = (scale[1] == 0.f) ? 0.f : 1.f / scale[1]; - float rsz = (scale[2] == 0.f) ? 0.f : 1.f / scale[2]; - - float r00 = m[0][0] * rsx, r10 = m[1][0] * rsx, r20 = m[2][0] * rsx; - float r01 = m[0][1] * rsy, r11 = m[1][1] * rsy, r21 = m[2][1] * rsy; - float r02 = m[0][2] * rsz, r12 = m[1][2] * rsz, r22 = m[2][2] * rsz; - - // "branchless" version of Mike Day's matrix to quaternion conversion - int qc = r22 < 0 ? (r00 > r11 ? 0 : 1) : (r00 < -r11 ? 2 : 3); - float qs1 = qc & 2 ? -1.f : 1.f; - float qs2 = qc & 1 ? -1.f : 1.f; - float qs3 = (qc - 1) & 2 ? -1.f : 1.f; - - float qt = 1.f - qs3 * r00 - qs2 * r11 - qs1 * r22; - float qs = 0.5f / sqrtf(qt); - - rotation[qc ^ 0] = qs * qt; - rotation[qc ^ 1] = qs * (r01 + qs1 * r10); - rotation[qc ^ 2] = qs * (r20 + qs2 * r02); - rotation[qc ^ 3] = qs * (r12 + qs3 * r21); -} diff --git a/Dependencies/meshoptimizer/gltf/package.json b/Dependencies/meshoptimizer/gltf/package.json deleted file mode 100644 index 62845c0a..00000000 --- a/Dependencies/meshoptimizer/gltf/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "gltfpack", - "version": "0.17.0", - "description": "A command-line tool that can optimize glTF files for size and speed", - "author": "Arseny Kapoulkine", - "license": "MIT", - "bugs": "https://github.com/zeux/meshoptimizer/issues", - "homepage": "https://github.com/zeux/meshoptimizer", - "keywords": [ - "gltf" - ], - "repository": { - "type": "git", - "url": "https://github.com/zeux/meshoptimizer" - }, - "bin": "./cli.js", - "main": "./library.js", - "files": [ - "*.js", "*.wasm" - ], - "scripts": { - "prepublishOnly": "node cli.js -v" - } -} diff --git a/Dependencies/meshoptimizer/gltf/parsegltf.cpp b/Dependencies/meshoptimizer/gltf/parsegltf.cpp deleted file mode 100644 index ec7b633f..00000000 --- a/Dependencies/meshoptimizer/gltf/parsegltf.cpp +++ /dev/null @@ -1,525 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include -#include -#include - -static const char* getError(cgltf_result result, cgltf_data* data) -{ - switch (result) - { - case cgltf_result_file_not_found: - return data ? "resource not found" : "file not found"; - - case cgltf_result_io_error: - return "I/O error"; - - case cgltf_result_invalid_json: - return "invalid JSON"; - - case cgltf_result_invalid_gltf: - return "invalid GLTF"; - - case cgltf_result_out_of_memory: - return "out of memory"; - - case cgltf_result_legacy_gltf: - return "legacy GLTF"; - - case cgltf_result_data_too_short: - return data ? "buffer too short" : "not a GLTF file"; - - case cgltf_result_unknown_format: - return data ? "unknown resource format" : "not a GLTF file"; - - default: - return "unknown error"; - } -} - -static void readAccessor(std::vector& data, const cgltf_accessor* accessor) -{ - assert(accessor->type == cgltf_type_scalar); - - data.resize(accessor->count); - cgltf_accessor_unpack_floats(accessor, &data[0], data.size()); -} - -static void readAccessor(std::vector& data, const cgltf_accessor* accessor) -{ - size_t components = cgltf_num_components(accessor->type); - - std::vector temp(accessor->count * components); - cgltf_accessor_unpack_floats(accessor, &temp[0], temp.size()); - - data.resize(accessor->count); - - for (size_t i = 0; i < accessor->count; ++i) - { - for (size_t k = 0; k < components && k < 4; ++k) - data[i].f[k] = temp[i * components + k]; - } -} - -static void fixupIndices(std::vector& indices, cgltf_primitive_type& type) -{ - if (type == cgltf_primitive_type_line_loop) - { - std::vector result; - result.reserve(indices.size() * 2 + 2); - - for (size_t i = 1; i <= indices.size(); ++i) - { - result.push_back(indices[i - 1]); - result.push_back(indices[i % indices.size()]); - } - - indices.swap(result); - type = cgltf_primitive_type_lines; - } - else if (type == cgltf_primitive_type_line_strip) - { - std::vector result; - result.reserve(indices.size() * 2); - - for (size_t i = 1; i < indices.size(); ++i) - { - result.push_back(indices[i - 1]); - result.push_back(indices[i]); - } - - indices.swap(result); - type = cgltf_primitive_type_lines; - } - else if (type == cgltf_primitive_type_triangle_strip) - { - std::vector result; - result.reserve(indices.size() * 3); - - for (size_t i = 2; i < indices.size(); ++i) - { - int flip = i & 1; - - result.push_back(indices[i - 2 + flip]); - result.push_back(indices[i - 1 - flip]); - result.push_back(indices[i]); - } - - indices.swap(result); - type = cgltf_primitive_type_triangles; - } - else if (type == cgltf_primitive_type_triangle_fan) - { - std::vector result; - result.reserve(indices.size() * 3); - - for (size_t i = 2; i < indices.size(); ++i) - { - result.push_back(indices[0]); - result.push_back(indices[i - 1]); - result.push_back(indices[i]); - } - - indices.swap(result); - type = cgltf_primitive_type_triangles; - } - else if (type == cgltf_primitive_type_lines) - { - // glTF files don't require that line index count is divisible by 2, but it is obviously critical for scenes to render - indices.resize(indices.size() / 2 * 2); - } - else if (type == cgltf_primitive_type_triangles) - { - // glTF files don't require that triangle index count is divisible by 3, but it is obviously critical for scenes to render - indices.resize(indices.size() / 3 * 3); - } -} - -static void parseMeshesGltf(cgltf_data* data, std::vector& meshes, std::vector >& mesh_remap) -{ - size_t total_primitives = 0; - - for (size_t mi = 0; mi < data->meshes_count; ++mi) - total_primitives += data->meshes[mi].primitives_count; - - meshes.reserve(total_primitives); - mesh_remap.resize(data->meshes_count); - - for (size_t mi = 0; mi < data->meshes_count; ++mi) - { - const cgltf_mesh& mesh = data->meshes[mi]; - - size_t remap_offset = meshes.size(); - - for (size_t pi = 0; pi < mesh.primitives_count; ++pi) - { - const cgltf_primitive& primitive = mesh.primitives[pi]; - - if (primitive.type == cgltf_primitive_type_points && primitive.indices) - { - fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because indexed points are not supported\n", int(pi), int(mi)); - continue; - } - - meshes.push_back(Mesh()); - Mesh& result = meshes.back(); - - result.scene = -1; - - result.material = primitive.material; - - result.type = primitive.type; - - result.streams.reserve(primitive.attributes_count); - - if (primitive.indices) - { - result.indices.resize(primitive.indices->count); - for (size_t i = 0; i < primitive.indices->count; ++i) - result.indices[i] = unsigned(cgltf_accessor_read_index(primitive.indices, i)); - } - else if (primitive.type != cgltf_primitive_type_points) - { - size_t count = primitive.attributes ? primitive.attributes[0].data->count : 0; - - // note, while we could generate a good index buffer, reindexMesh will take care of this - result.indices.resize(count); - for (size_t i = 0; i < count; ++i) - result.indices[i] = unsigned(i); - } - - fixupIndices(result.indices, result.type); - - for (size_t ai = 0; ai < primitive.attributes_count; ++ai) - { - const cgltf_attribute& attr = primitive.attributes[ai]; - - if (attr.type == cgltf_attribute_type_invalid) - { - fprintf(stderr, "Warning: ignoring unknown attribute %s in primitive %d of mesh %d\n", attr.name, int(pi), int(mi)); - continue; - } - - result.streams.push_back(Stream()); - Stream& s = result.streams.back(); - - s.type = attr.type; - s.index = attr.index; - - readAccessor(s.data, attr.data); - - if (attr.type == cgltf_attribute_type_color && attr.data->type == cgltf_type_vec3) - { - for (size_t i = 0; i < s.data.size(); ++i) - s.data[i].f[3] = 1.0f; - } - } - - for (size_t ti = 0; ti < primitive.targets_count; ++ti) - { - const cgltf_morph_target& target = primitive.targets[ti]; - - for (size_t ai = 0; ai < target.attributes_count; ++ai) - { - const cgltf_attribute& attr = target.attributes[ai]; - - if (attr.type == cgltf_attribute_type_invalid) - { - fprintf(stderr, "Warning: ignoring unknown attribute %s in morph target %d of primitive %d of mesh %d\n", attr.name, int(ti), int(pi), int(mi)); - continue; - } - - result.streams.push_back(Stream()); - Stream& s = result.streams.back(); - - s.type = attr.type; - s.index = attr.index; - s.target = int(ti + 1); - - readAccessor(s.data, attr.data); - } - } - - result.targets = primitive.targets_count; - result.target_weights.assign(mesh.weights, mesh.weights + mesh.weights_count); - result.target_names.assign(mesh.target_names, mesh.target_names + mesh.target_names_count); - - result.variants.assign(primitive.mappings, primitive.mappings + primitive.mappings_count); - } - - mesh_remap[mi] = std::make_pair(remap_offset, meshes.size()); - } -} - -static void parseMeshNodesGltf(cgltf_data* data, std::vector& meshes, const std::vector >& mesh_remap) -{ - for (size_t i = 0; i < data->nodes_count; ++i) - { - cgltf_node& node = data->nodes[i]; - if (!node.mesh) - continue; - - std::pair range = mesh_remap[node.mesh - data->meshes]; - - for (size_t mi = range.first; mi < range.second; ++mi) - { - Mesh* mesh = &meshes[mi]; - - if (!mesh->nodes.empty() && mesh->skin != node.skin) - { - // this should be extremely rare - if the same mesh is used with different skins, we need to duplicate it - // in this case we don't spend any effort on keeping the number of duplicates to the minimum, because this - // should really never happen. - meshes.push_back(*mesh); - mesh = &meshes.back(); - } - - mesh->nodes.push_back(&node); - mesh->skin = node.skin; - } - } - - for (size_t i = 0; i < meshes.size(); ++i) - { - Mesh& mesh = meshes[i]; - - // because the rest of gltfpack assumes that empty nodes array = world-space mesh, we need to filter unused meshes - if (mesh.nodes.empty()) - { - mesh.streams.clear(); - mesh.indices.clear(); - } - } -} - -static void parseAnimationsGltf(cgltf_data* data, std::vector& animations) -{ - animations.reserve(data->animations_count); - - for (size_t i = 0; i < data->animations_count; ++i) - { - const cgltf_animation& animation = data->animations[i]; - - animations.push_back(Animation()); - Animation& result = animations.back(); - - result.name = animation.name; - - result.tracks.reserve(animation.channels_count); - - for (size_t j = 0; j < animation.channels_count; ++j) - { - const cgltf_animation_channel& channel = animation.channels[j]; - - if (!channel.target_node) - { - fprintf(stderr, "Warning: ignoring channel %d of animation %d because it has no target node\n", int(j), int(i)); - continue; - } - - result.tracks.push_back(Track()); - Track& track = result.tracks.back(); - - track.node = channel.target_node; - track.path = channel.target_path; - - track.components = (channel.target_path == cgltf_animation_path_type_weights) ? track.node->mesh->primitives[0].targets_count : 1; - - track.interpolation = channel.sampler->interpolation; - - readAccessor(track.time, channel.sampler->input); - readAccessor(track.data, channel.sampler->output); - } - - if (result.tracks.empty()) - { - fprintf(stderr, "Warning: ignoring animation %d because it has no valid tracks\n", int(i)); - animations.pop_back(); - } - } -} - -static bool requiresExtension(cgltf_data* data, const char* name) -{ - for (size_t i = 0; i < data->extensions_required_count; ++i) - if (strcmp(data->extensions_required[i], name) == 0) - return true; - - return false; -} - -static bool needsDummyBuffers(cgltf_data* data) -{ - for (size_t i = 0; i < data->accessors_count; ++i) - { - cgltf_accessor* accessor = &data->accessors[i]; - - if (accessor->buffer_view && accessor->buffer_view->buffer->data == NULL) - return true; - - if (accessor->is_sparse) - { - cgltf_accessor_sparse* sparse = &accessor->sparse; - - if (sparse->indices_buffer_view->buffer->data == NULL) - return true; - if (sparse->values_buffer_view->buffer->data == NULL) - return true; - } - } - - for (size_t i = 0; i < data->images_count; ++i) - { - cgltf_image* image = &data->images[i]; - - if (image->buffer_view && image->buffer_view->buffer->data == NULL) - return true; - } - - return false; -} - -static void evacuateExtras(cgltf_data* data, std::string& extras, cgltf_extras& item) -{ - size_t offset = extras.size(); - - extras.append(data->json + item.start_offset, item.end_offset - item.start_offset); - - item.start_offset = offset; - item.end_offset = extras.size(); -} - -static void evacuateExtras(cgltf_data* data, std::string& extras) -{ - size_t size = 0; - - size += data->asset.extras.end_offset - data->asset.extras.start_offset; - - for (size_t i = 0; i < data->materials_count; ++i) - size += data->materials[i].extras.end_offset - data->materials[i].extras.start_offset; - - for (size_t i = 0; i < data->nodes_count; ++i) - size += data->nodes[i].extras.end_offset - data->nodes[i].extras.start_offset; - - extras.reserve(size); - - evacuateExtras(data, extras, data->asset.extras); - - for (size_t i = 0; i < data->materials_count; ++i) - evacuateExtras(data, extras, data->materials[i].extras); - - for (size_t i = 0; i < data->nodes_count; ++i) - evacuateExtras(data, extras, data->nodes[i].extras); -} - -static void freeFile(cgltf_data* data) -{ - data->json = NULL; - data->bin = NULL; - - free(data->file_data); - data->file_data = NULL; -} - -static bool freeUnusedBuffers(cgltf_data* data) -{ - std::vector used(data->buffers_count); - - for (size_t i = 0; i < data->skins_count; ++i) - { - const cgltf_skin& skin = data->skins[i]; - - if (skin.inverse_bind_matrices && skin.inverse_bind_matrices->buffer_view) - { - assert(skin.inverse_bind_matrices->buffer_view->buffer); - used[skin.inverse_bind_matrices->buffer_view->buffer - data->buffers] = 1; - } - } - - for (size_t i = 0; i < data->images_count; ++i) - { - const cgltf_image& image = data->images[i]; - - if (image.buffer_view) - { - assert(image.buffer_view->buffer); - used[image.buffer_view->buffer - data->buffers] = 1; - } - } - - bool free_bin = false; - - for (size_t i = 0; i < data->buffers_count; ++i) - { - cgltf_buffer& buffer = data->buffers[i]; - - if (!used[i] && buffer.data) - { - if (buffer.data != data->bin) - free(buffer.data); - else - free_bin = true; - - buffer.data = NULL; - } - } - - return free_bin; -} - -cgltf_data* parseGltf(const char* path, std::vector& meshes, std::vector& animations, std::string& extras, const char** error) -{ - cgltf_data* data = 0; - - cgltf_options options = {}; - cgltf_result result = cgltf_parse_file(&options, path, &data); - - if (data) - { - evacuateExtras(data, extras); - - if (!data->bin) - freeFile(data); - } - - result = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, path) : result; - result = (result == cgltf_result_success) ? cgltf_validate(data) : result; - - *error = NULL; - - if (result != cgltf_result_success) - *error = getError(result, data); - else if (requiresExtension(data, "KHR_draco_mesh_compression")) - *error = "file requires Draco mesh compression support"; - else if (requiresExtension(data, "EXT_meshopt_compression")) - *error = "file has already been compressed using gltfpack"; - else if (requiresExtension(data, "KHR_texture_basisu")) - *error = "file requires BasisU texture support"; - else if (requiresExtension(data, "EXT_mesh_gpu_instancing")) - *error = "file requires mesh instancing support"; - else if (needsDummyBuffers(data)) - *error = "buffer has no data"; - - if (*error) - { - cgltf_free(data); - return 0; - } - - if (requiresExtension(data, "KHR_mesh_quantization")) - fprintf(stderr, "Warning: file uses quantized geometry; repacking may result in increased quantization error\n"); - - std::vector > mesh_remap; - - parseMeshesGltf(data, meshes, mesh_remap); - parseMeshNodesGltf(data, meshes, mesh_remap); - parseAnimationsGltf(data, animations); - - bool free_bin = freeUnusedBuffers(data); - - if (data->bin && free_bin) - freeFile(data); - - return data; -} diff --git a/Dependencies/meshoptimizer/gltf/parseobj.cpp b/Dependencies/meshoptimizer/gltf/parseobj.cpp deleted file mode 100644 index 0637e949..00000000 --- a/Dependencies/meshoptimizer/gltf/parseobj.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include "../extern/fast_obj.h" - -#include - -#include -#include - -static void defaultFree(void*, void* p) -{ - free(p); -} - -static int textureIndex(const std::vector& textures, const char* name) -{ - for (size_t i = 0; i < textures.size(); ++i) - if (textures[i] == name) - return int(i); - - return -1; -} - -static cgltf_data* parseSceneObj(fastObjMesh* obj) -{ - cgltf_data* data = (cgltf_data*)calloc(1, sizeof(cgltf_data)); - data->memory.free = defaultFree; - - std::vector textures; - - for (unsigned int mi = 0; mi < obj->material_count; ++mi) - { - fastObjMaterial& om = obj->materials[mi]; - - if (om.map_Kd.name && textureIndex(textures, om.map_Kd.name) < 0) - textures.push_back(om.map_Kd.name); - } - - data->images = (cgltf_image*)calloc(textures.size(), sizeof(cgltf_image)); - data->images_count = textures.size(); - - for (size_t i = 0; i < textures.size(); ++i) - { - data->images[i].uri = (char*)malloc(textures[i].size() + 1); - strcpy(data->images[i].uri, textures[i].c_str()); - } - - data->textures = (cgltf_texture*)calloc(textures.size(), sizeof(cgltf_texture)); - data->textures_count = textures.size(); - - for (size_t i = 0; i < textures.size(); ++i) - { - data->textures[i].image = &data->images[i]; - } - - data->materials = (cgltf_material*)calloc(obj->material_count, sizeof(cgltf_material)); - data->materials_count = obj->material_count; - - for (unsigned int mi = 0; mi < obj->material_count; ++mi) - { - cgltf_material& gm = data->materials[mi]; - fastObjMaterial& om = obj->materials[mi]; - - gm.has_pbr_metallic_roughness = true; - gm.pbr_metallic_roughness.base_color_factor[0] = 1.0f; - gm.pbr_metallic_roughness.base_color_factor[1] = 1.0f; - gm.pbr_metallic_roughness.base_color_factor[2] = 1.0f; - gm.pbr_metallic_roughness.base_color_factor[3] = 1.0f; - gm.pbr_metallic_roughness.metallic_factor = 0.0f; - gm.pbr_metallic_roughness.roughness_factor = 1.0f; - - gm.alpha_cutoff = 0.5f; - - if (om.map_Kd.name) - { - gm.pbr_metallic_roughness.base_color_texture.texture = &data->textures[textureIndex(textures, om.map_Kd.name)]; - gm.pbr_metallic_roughness.base_color_texture.scale = 1.0f; - - gm.alpha_mode = (om.illum == 4 || om.illum == 6 || om.illum == 7 || om.illum == 9) ? cgltf_alpha_mode_mask : cgltf_alpha_mode_opaque; - } - - if (om.map_d.name) - { - gm.alpha_mode = cgltf_alpha_mode_blend; - } - } - - data->scenes = (cgltf_scene*)calloc(1, sizeof(cgltf_scene)); - data->scenes_count = 1; - - return data; -} - -static void parseMeshesObj(fastObjMesh* obj, cgltf_data* data, std::vector& meshes) -{ - unsigned int material_count = std::max(obj->material_count, 1u); - - std::vector vertex_count(material_count); - std::vector index_count(material_count); - - for (unsigned int fi = 0; fi < obj->face_count; ++fi) - { - unsigned int mi = obj->face_materials[fi]; - - vertex_count[mi] += obj->face_vertices[fi]; - index_count[mi] += (obj->face_vertices[fi] - 2) * 3; - } - - std::vector mesh_index(material_count); - - for (unsigned int mi = 0; mi < material_count; ++mi) - { - if (index_count[mi] == 0) - continue; - - mesh_index[mi] = meshes.size(); - - meshes.push_back(Mesh()); - Mesh& mesh = meshes.back(); - - if (data->materials_count) - { - assert(mi < data->materials_count); - mesh.material = &data->materials[mi]; - } - - mesh.type = cgltf_primitive_type_triangles; - - mesh.streams.resize(3); - mesh.streams[0].type = cgltf_attribute_type_position; - mesh.streams[0].data.resize(vertex_count[mi]); - mesh.streams[1].type = cgltf_attribute_type_normal; - mesh.streams[1].data.resize(vertex_count[mi]); - mesh.streams[2].type = cgltf_attribute_type_texcoord; - mesh.streams[2].data.resize(vertex_count[mi]); - mesh.indices.resize(index_count[mi]); - mesh.targets = 0; - } - - std::vector mesh_normals(meshes.size()); - std::vector mesh_texcoords(meshes.size()); - - std::vector vertex_offset(material_count); - std::vector index_offset(material_count); - - size_t group_offset = 0; - - for (unsigned int fi = 0; fi < obj->face_count; ++fi) - { - unsigned int mi = obj->face_materials[fi]; - Mesh& mesh = meshes[mesh_index[mi]]; - - size_t vo = vertex_offset[mi]; - size_t io = index_offset[mi]; - - for (unsigned int vi = 0; vi < obj->face_vertices[fi]; ++vi) - { - fastObjIndex ii = obj->indices[group_offset + vi]; - - Attr p = {{obj->positions[ii.p * 3 + 0], obj->positions[ii.p * 3 + 1], obj->positions[ii.p * 3 + 2]}}; - Attr n = {{obj->normals[ii.n * 3 + 0], obj->normals[ii.n * 3 + 1], obj->normals[ii.n * 3 + 2]}}; - Attr t = {{obj->texcoords[ii.t * 2 + 0], 1.f - obj->texcoords[ii.t * 2 + 1]}}; - - mesh.streams[0].data[vo + vi] = p; - mesh.streams[1].data[vo + vi] = n; - mesh.streams[2].data[vo + vi] = t; - - mesh_normals[mesh_index[mi]] |= ii.n > 0; - mesh_texcoords[mesh_index[mi]] |= ii.t > 0; - } - - for (unsigned int vi = 2; vi < obj->face_vertices[fi]; ++vi) - { - size_t to = io + (vi - 2) * 3; - - mesh.indices[to + 0] = unsigned(vo); - mesh.indices[to + 1] = unsigned(vo + vi - 1); - mesh.indices[to + 2] = unsigned(vo + vi); - } - - vertex_offset[mi] += obj->face_vertices[fi]; - index_offset[mi] += (obj->face_vertices[fi] - 2) * 3; - group_offset += obj->face_vertices[fi]; - } - - for (size_t i = 0; i < meshes.size(); ++i) - { - Mesh& mesh = meshes[i]; - - assert(mesh.streams.size() == 3); - assert(mesh.streams[1].type == cgltf_attribute_type_normal); - assert(mesh.streams[2].type == cgltf_attribute_type_texcoord); - - if (!mesh_texcoords[i]) - mesh.streams.erase(mesh.streams.begin() + 2); - - if (!mesh_normals[i]) - mesh.streams.erase(mesh.streams.begin() + 1); - } -} - -cgltf_data* parseObj(const char* path, std::vector& meshes, const char** error) -{ - fastObjMesh* obj = fast_obj_read(path); - - if (!obj) - { - *error = "file not found"; - return 0; - } - - cgltf_data* data = parseSceneObj(obj); - parseMeshesObj(obj, data, meshes); - - fast_obj_destroy(obj); - - return data; -} diff --git a/Dependencies/meshoptimizer/gltf/stream.cpp b/Dependencies/meshoptimizer/gltf/stream.cpp deleted file mode 100644 index ab757ac6..00000000 --- a/Dependencies/meshoptimizer/gltf/stream.cpp +++ /dev/null @@ -1,820 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include - -#include -#include -#include -#include - -#include "../src/meshoptimizer.h" - -struct Bounds -{ - Attr min, max; - - Bounds() - { - min.f[0] = min.f[1] = min.f[2] = min.f[3] = +FLT_MAX; - max.f[0] = max.f[1] = max.f[2] = max.f[3] = -FLT_MAX; - } - - bool isValid() const - { - return min.f[0] <= max.f[0] && min.f[1] <= max.f[1] && min.f[2] <= max.f[2] && min.f[3] <= max.f[3]; - } -}; - -static void updateAttributeBounds(const Mesh& mesh, cgltf_attribute_type type, Bounds& b) -{ - Attr pad = {}; - - for (size_t j = 0; j < mesh.streams.size(); ++j) - { - const Stream& s = mesh.streams[j]; - - if (s.type == type) - { - if (s.target == 0) - { - for (size_t k = 0; k < s.data.size(); ++k) - { - const Attr& a = s.data[k]; - - b.min.f[0] = std::min(b.min.f[0], a.f[0]); - b.min.f[1] = std::min(b.min.f[1], a.f[1]); - b.min.f[2] = std::min(b.min.f[2], a.f[2]); - b.min.f[3] = std::min(b.min.f[3], a.f[3]); - - b.max.f[0] = std::max(b.max.f[0], a.f[0]); - b.max.f[1] = std::max(b.max.f[1], a.f[1]); - b.max.f[2] = std::max(b.max.f[2], a.f[2]); - b.max.f[3] = std::max(b.max.f[3], a.f[3]); - } - } - else - { - for (size_t k = 0; k < s.data.size(); ++k) - { - const Attr& a = s.data[k]; - - pad.f[0] = std::max(pad.f[0], fabsf(a.f[0])); - pad.f[1] = std::max(pad.f[1], fabsf(a.f[1])); - pad.f[2] = std::max(pad.f[2], fabsf(a.f[2])); - pad.f[3] = std::max(pad.f[3], fabsf(a.f[3])); - } - } - } - } - - for (int k = 0; k < 4; ++k) - { - b.min.f[k] -= pad.f[k]; - b.max.f[k] += pad.f[k]; - } -} - -QuantizationPosition prepareQuantizationPosition(const std::vector& meshes, const Settings& settings) -{ - QuantizationPosition result = {}; - - result.bits = settings.pos_bits; - - Bounds b; - - for (size_t i = 0; i < meshes.size(); ++i) - { - updateAttributeBounds(meshes[i], cgltf_attribute_type_position, b); - } - - if (b.isValid()) - { - result.offset[0] = b.min.f[0]; - result.offset[1] = b.min.f[1]; - result.offset[2] = b.min.f[2]; - result.scale = std::max(b.max.f[0] - b.min.f[0], std::max(b.max.f[1] - b.min.f[1], b.max.f[2] - b.min.f[2])); - } - - return result; -} - -static size_t follow(std::vector& parents, size_t index) -{ - while (index != parents[index]) - { - size_t parent = parents[index]; - - parents[index] = parents[parent]; - index = parent; - } - - return index; -} - -void prepareQuantizationTexture(cgltf_data* data, std::vector& result, std::vector& indices, const std::vector& meshes, const Settings& settings) -{ - // use union-find to associate each material with a canonical material - // this is necessary because any set of materials that are used on the same mesh must use the same quantization - std::vector parents(result.size()); - - for (size_t i = 0; i < parents.size(); ++i) - parents[i] = i; - - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - if (!mesh.material && mesh.variants.empty()) - continue; - - size_t root = follow(parents, (mesh.material ? mesh.material : mesh.variants[0].material) - data->materials); - - for (size_t j = 0; j < mesh.variants.size(); ++j) - { - size_t var = follow(parents, mesh.variants[j].material - data->materials); - - parents[var] = root; - } - - indices[i] = root; - } - - // compute canonical material bounds based on meshes that use them - std::vector bounds(result.size()); - - for (size_t i = 0; i < meshes.size(); ++i) - { - const Mesh& mesh = meshes[i]; - - if (!mesh.material && mesh.variants.empty()) - continue; - - indices[i] = follow(parents, indices[i]); - updateAttributeBounds(mesh, cgltf_attribute_type_texcoord, bounds[indices[i]]); - } - - // update all material data using canonical bounds - for (size_t i = 0; i < result.size(); ++i) - { - QuantizationTexture& qt = result[i]; - - qt.bits = settings.tex_bits; - - const Bounds& b = bounds[follow(parents, i)]; - - if (b.isValid()) - { - qt.offset[0] = b.min.f[0]; - qt.offset[1] = b.min.f[1]; - qt.scale[0] = b.max.f[0] - b.min.f[0]; - qt.scale[1] = b.max.f[1] - b.min.f[1]; - } - } -} - -void getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition* qp) -{ - assert(stream.type == cgltf_attribute_type_position); - assert(stream.data.size() > 0); - - min[0] = min[1] = min[2] = FLT_MAX; - max[0] = max[1] = max[2] = -FLT_MAX; - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - for (int k = 0; k < 3; ++k) - { - min[k] = std::min(min[k], a.f[k]); - max[k] = std::max(max[k], a.f[k]); - } - } - - if (qp) - { - float pos_rscale = qp->scale == 0.f ? 0.f : 1.f / qp->scale; - - for (int k = 0; k < 3; ++k) - { - if (stream.target == 0) - { - min[k] = float(meshopt_quantizeUnorm((min[k] - qp->offset[k]) * pos_rscale, qp->bits)); - max[k] = float(meshopt_quantizeUnorm((max[k] - qp->offset[k]) * pos_rscale, qp->bits)); - } - else - { - min[k] = (min[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(min[k]) * pos_rscale, qp->bits)); - max[k] = (max[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(max[k]) * pos_rscale, qp->bits)); - } - } - } -} - -static void renormalizeWeights(uint8_t (&w)[4]) -{ - int sum = w[0] + w[1] + w[2] + w[3]; - - if (sum == 255) - return; - - // we assume that the total error is limited to 0.5/component = 2 - // this means that it's acceptable to adjust the max. component to compensate for the error - int max = 0; - - for (int k = 1; k < 4; ++k) - if (w[k] > w[max]) - max = k; - - w[max] += uint8_t(255 - sum); -} - -static void encodeOct(int& fu, int& fv, float nx, float ny, float nz, int bits) -{ - float nl = fabsf(nx) + fabsf(ny) + fabsf(nz); - float ns = nl == 0.f ? 0.f : 1.f / nl; - - nx *= ns; - ny *= ns; - - float u = (nz >= 0.f) ? nx : (1 - fabsf(ny)) * (nx >= 0.f ? 1.f : -1.f); - float v = (nz >= 0.f) ? ny : (1 - fabsf(nx)) * (ny >= 0.f ? 1.f : -1.f); - - fu = meshopt_quantizeSnorm(u, bits); - fv = meshopt_quantizeSnorm(v, bits); -} - -static StreamFormat writeVertexStreamRaw(std::string& bin, const Stream& stream, cgltf_type type, size_t components) -{ - assert(components >= 1 && components <= 4); - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - bin.append(reinterpret_cast(a.f), sizeof(float) * components); - } - - StreamFormat format = {type, cgltf_component_type_r_32f, false, sizeof(float) * components}; - return format; -} - -static int quantizeColor(float v, int bytebits, int bits) -{ - int result = meshopt_quantizeUnorm(v, bytebits); - - // replicate the top bit into the low significant bits - const int mask = (1 << (bytebits - bits)) - 1; - - return (result & ~mask) | (mask & -(result >> (bytebits - 1))); -} - -StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings) -{ - if (stream.type == cgltf_attribute_type_position) - { - if (!settings.quantize) - return writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3); - - if (stream.target == 0) - { - float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale; - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - uint16_t v[4] = { - uint16_t(meshopt_quantizeUnorm((a.f[0] - qp.offset[0]) * pos_rscale, qp.bits)), - uint16_t(meshopt_quantizeUnorm((a.f[1] - qp.offset[1]) * pos_rscale, qp.bits)), - uint16_t(meshopt_quantizeUnorm((a.f[2] - qp.offset[2]) * pos_rscale, qp.bits)), - 0}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16u, false, 8}; - return format; - } - else - { - float pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale; - - int maxv = 0; - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)); - maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)); - maxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)); - } - - if (maxv <= 127) - { - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - int8_t v[4] = { - int8_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)), - int8_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)), - int8_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)), - 0}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_8, false, 4}; - return format; - } - else - { - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - int16_t v[4] = { - int16_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)), - int16_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)), - int16_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)), - 0}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16, false, 8}; - return format; - } - } - } - else if (stream.type == cgltf_attribute_type_texcoord) - { - if (!settings.quantize) - return writeVertexStreamRaw(bin, stream, cgltf_type_vec2, 2); - - float uv_rscale[2] = { - qt.scale[0] == 0.f ? 0.f : 1.f / qt.scale[0], - qt.scale[1] == 0.f ? 0.f : 1.f / qt.scale[1], - }; - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - uint16_t v[2] = { - uint16_t(meshopt_quantizeUnorm((a.f[0] - qt.offset[0]) * uv_rscale[0], qt.bits)), - uint16_t(meshopt_quantizeUnorm((a.f[1] - qt.offset[1]) * uv_rscale[1], qt.bits)), - }; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_16u, false, 4}; - return format; - } - else if (stream.type == cgltf_attribute_type_normal) - { - if (!settings.quantize) - return writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3); - - bool oct = settings.compressmore && stream.target == 0; - int bits = settings.nrm_bits; - - StreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None; - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - float nx = a.f[0], ny = a.f[1], nz = a.f[2]; - - if (bits > 8) - { - int16_t v[4]; - - if (oct) - { - int fu, fv; - encodeOct(fu, fv, nx, ny, nz, bits); - - v[0] = int16_t(fu); - v[1] = int16_t(fv); - v[2] = int16_t(meshopt_quantizeSnorm(1.f, bits)); - v[3] = 0; - } - else - { - v[0] = int16_t(meshopt_quantizeSnorm(nx, bits)); - v[1] = int16_t(meshopt_quantizeSnorm(ny, bits)); - v[2] = int16_t(meshopt_quantizeSnorm(nz, bits)); - v[3] = 0; - } - - bin.append(reinterpret_cast(v), sizeof(v)); - } - else - { - int8_t v[4]; - - if (oct) - { - int fu, fv; - encodeOct(fu, fv, nx, ny, nz, bits); - - v[0] = int8_t(fu); - v[1] = int8_t(fv); - v[2] = int8_t(meshopt_quantizeSnorm(1.f, bits)); - v[3] = 0; - } - else - { - v[0] = int8_t(meshopt_quantizeSnorm(nx, bits)); - v[1] = int8_t(meshopt_quantizeSnorm(ny, bits)); - v[2] = int8_t(meshopt_quantizeSnorm(nz, bits)); - v[3] = 0; - } - - bin.append(reinterpret_cast(v), sizeof(v)); - } - } - - if (bits > 8) - { - StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16, true, 8, filter}; - return format; - } - else - { - StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_8, true, 4, filter}; - return format; - } - } - else if (stream.type == cgltf_attribute_type_tangent) - { - if (!settings.quantize) - return writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4); - - bool oct = settings.compressmore && stream.target == 0; - int bits = (settings.nrm_bits > 8) ? 8 : settings.nrm_bits; - - StreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None; - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - float nx = a.f[0], ny = a.f[1], nz = a.f[2], nw = a.f[3]; - - int8_t v[4]; - - if (oct) - { - int fu, fv; - encodeOct(fu, fv, nx, ny, nz, bits); - - v[0] = int8_t(fu); - v[1] = int8_t(fv); - v[2] = int8_t(meshopt_quantizeSnorm(1.f, bits)); - v[3] = int8_t(meshopt_quantizeSnorm(nw, bits)); - } - else - { - v[0] = int8_t(meshopt_quantizeSnorm(nx, bits)); - v[1] = int8_t(meshopt_quantizeSnorm(ny, bits)); - v[2] = int8_t(meshopt_quantizeSnorm(nz, bits)); - v[3] = int8_t(meshopt_quantizeSnorm(nw, bits)); - } - - bin.append(reinterpret_cast(v), sizeof(v)); - } - - cgltf_type type = (stream.target == 0) ? cgltf_type_vec4 : cgltf_type_vec3; - - StreamFormat format = {type, cgltf_component_type_r_8, true, 4, filter}; - return format; - } - else if (stream.type == cgltf_attribute_type_color) - { - int bits = settings.col_bits; - - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - if (bits > 8) - { - uint16_t v[4] = { - uint16_t(quantizeColor(a.f[0], 16, bits)), - uint16_t(quantizeColor(a.f[1], 16, bits)), - uint16_t(quantizeColor(a.f[2], 16, bits)), - uint16_t(quantizeColor(a.f[3], 16, bits))}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - else - { - uint8_t v[4] = { - uint8_t(quantizeColor(a.f[0], 8, bits)), - uint8_t(quantizeColor(a.f[1], 8, bits)), - uint8_t(quantizeColor(a.f[2], 8, bits)), - uint8_t(quantizeColor(a.f[3], 8, bits))}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - } - - if (bits > 8) - { - StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, true, 8}; - return format; - } - else - { - StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4}; - return format; - } - } - else if (stream.type == cgltf_attribute_type_weights) - { - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - float ws = a.f[0] + a.f[1] + a.f[2] + a.f[3]; - float wsi = (ws == 0.f) ? 0.f : 1.f / ws; - - uint8_t v[4] = { - uint8_t(meshopt_quantizeUnorm(a.f[0] * wsi, 8)), - uint8_t(meshopt_quantizeUnorm(a.f[1] * wsi, 8)), - uint8_t(meshopt_quantizeUnorm(a.f[2] * wsi, 8)), - uint8_t(meshopt_quantizeUnorm(a.f[3] * wsi, 8))}; - - if (wsi != 0.f) - renormalizeWeights(v); - - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4}; - return format; - } - else if (stream.type == cgltf_attribute_type_joints) - { - unsigned int maxj = 0; - - for (size_t i = 0; i < stream.data.size(); ++i) - maxj = std::max(maxj, unsigned(stream.data[i].f[0])); - - assert(maxj <= 65535); - - if (maxj <= 255) - { - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - uint8_t v[4] = { - uint8_t(a.f[0]), - uint8_t(a.f[1]), - uint8_t(a.f[2]), - uint8_t(a.f[3])}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, false, 4}; - return format; - } - else - { - for (size_t i = 0; i < stream.data.size(); ++i) - { - const Attr& a = stream.data[i]; - - uint16_t v[4] = { - uint16_t(a.f[0]), - uint16_t(a.f[1]), - uint16_t(a.f[2]), - uint16_t(a.f[3])}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, false, 8}; - return format; - } - } - else - { - return writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4); - } -} - -StreamFormat writeIndexStream(std::string& bin, const std::vector& stream) -{ - unsigned int maxi = 0; - for (size_t i = 0; i < stream.size(); ++i) - maxi = std::max(maxi, stream[i]); - - // save 16-bit indices if we can; note that we can't use restart index (65535) - if (maxi < 65535) - { - for (size_t i = 0; i < stream.size(); ++i) - { - uint16_t v[1] = {uint16_t(stream[i])}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_16u, false, 2}; - return format; - } - else - { - for (size_t i = 0; i < stream.size(); ++i) - { - uint32_t v[1] = {stream[i]}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32u, false, 4}; - return format; - } -} - -StreamFormat writeTimeStream(std::string& bin, const std::vector& data) -{ - for (size_t i = 0; i < data.size(); ++i) - { - float v[1] = {data[i]}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4}; - return format; -} - -static void encodeQuat(int16_t v[4], const Attr& a, int bits) -{ - const float scaler = sqrtf(2.f); - - // establish maximum quaternion component - int qc = 0; - qc = fabsf(a.f[1]) > fabsf(a.f[qc]) ? 1 : qc; - qc = fabsf(a.f[2]) > fabsf(a.f[qc]) ? 2 : qc; - qc = fabsf(a.f[3]) > fabsf(a.f[qc]) ? 3 : qc; - - // we use double-cover properties to discard the sign - float sign = a.f[qc] < 0.f ? -1.f : 1.f; - - // note: we always encode a cyclical swizzle to be able to recover the order via rotation - v[0] = int16_t(meshopt_quantizeSnorm(a.f[(qc + 1) & 3] * scaler * sign, bits)); - v[1] = int16_t(meshopt_quantizeSnorm(a.f[(qc + 2) & 3] * scaler * sign, bits)); - v[2] = int16_t(meshopt_quantizeSnorm(a.f[(qc + 3) & 3] * scaler * sign, bits)); - v[3] = int16_t((meshopt_quantizeSnorm(1.f, bits) & ~3) | qc); -} - -static void encodeExpShared(uint32_t v[3], const Attr& a, int bits) -{ - // get exponents from all components - int ex, ey, ez; - frexp(a.f[0], &ex); - frexp(a.f[1], &ey); - frexp(a.f[2], &ez); - - // use maximum exponent to encode values; this guarantess that mantissa is [-1, 1] - // note that we additionally scale the mantissa to make it a K-bit signed integer (K-1 bits for magnitude) - int exp = std::max(ex, std::max(ey, ez)) - (bits - 1); - - // compute renormalized rounded mantissas for each component - int mx = int(ldexp(a.f[0], -exp) + (a.f[0] >= 0 ? 0.5f : -0.5f)); - int my = int(ldexp(a.f[1], -exp) + (a.f[1] >= 0 ? 0.5f : -0.5f)); - int mz = int(ldexp(a.f[2], -exp) + (a.f[2] >= 0 ? 0.5f : -0.5f)); - - int mmask = (1 << 24) - 1; - - // encode exponent & mantissa into each resulting value - v[0] = (mx & mmask) | (unsigned(exp) << 24); - v[1] = (my & mmask) | (unsigned(exp) << 24); - v[2] = (mz & mmask) | (unsigned(exp) << 24); -} - -StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector& data, const Settings& settings) -{ - if (type == cgltf_animation_path_type_rotation) - { - StreamFormat::Filter filter = settings.compressmore ? StreamFormat::Filter_Quat : StreamFormat::Filter_None; - - for (size_t i = 0; i < data.size(); ++i) - { - const Attr& a = data[i]; - - int16_t v[4]; - - if (filter == StreamFormat::Filter_Quat) - { - encodeQuat(v, a, settings.rot_bits); - } - else - { - v[0] = int16_t(meshopt_quantizeSnorm(a.f[0], 16)); - v[1] = int16_t(meshopt_quantizeSnorm(a.f[1], 16)); - v[2] = int16_t(meshopt_quantizeSnorm(a.f[2], 16)); - v[3] = int16_t(meshopt_quantizeSnorm(a.f[3], 16)); - } - - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16, true, 8, filter}; - return format; - } - else if (type == cgltf_animation_path_type_weights) - { - for (size_t i = 0; i < data.size(); ++i) - { - const Attr& a = data[i]; - - uint8_t v[1] = {uint8_t(meshopt_quantizeUnorm(a.f[0], 8))}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_8u, true, 1}; - return format; - } - else if (type == cgltf_animation_path_type_translation || type == cgltf_animation_path_type_scale) - { - StreamFormat::Filter filter = settings.compressmore ? StreamFormat::Filter_Exp : StreamFormat::Filter_None; - int bits = (type == cgltf_animation_path_type_translation) ? settings.trn_bits : settings.scl_bits; - - for (size_t i = 0; i < data.size(); ++i) - { - const Attr& a = data[i]; - - if (filter == StreamFormat::Filter_Exp) - { - uint32_t v[3]; - encodeExpShared(v, a, bits); - bin.append(reinterpret_cast(v), sizeof(v)); - } - else - { - float v[3] = {a.f[0], a.f[1], a.f[2]}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - } - - StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_32f, false, 12, filter}; - return format; - } - else - { - for (size_t i = 0; i < data.size(); ++i) - { - const Attr& a = data[i]; - - float v[4] = {a.f[0], a.f[1], a.f[2], a.f[3]}; - bin.append(reinterpret_cast(v), sizeof(v)); - } - - StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_32f, false, 16}; - return format; - } -} - -void compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride) -{ - assert(data.size() == count * stride); - - std::vector compressed(meshopt_encodeVertexBufferBound(count, stride)); - size_t size = meshopt_encodeVertexBuffer(&compressed[0], compressed.size(), data.c_str(), count, stride); - - bin.append(reinterpret_cast(&compressed[0]), size); -} - -void compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride) -{ - assert(stride == 2 || stride == 4); - assert(data.size() == count * stride); - assert(count % 3 == 0); - - std::vector compressed(meshopt_encodeIndexBufferBound(count, count)); - size_t size = 0; - - if (stride == 2) - size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); - else - size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); - - bin.append(reinterpret_cast(&compressed[0]), size); -} - -void compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride) -{ - assert(stride == 2 || stride == 4); - assert(data.size() == count * stride); - - std::vector compressed(meshopt_encodeIndexSequenceBound(count, count)); - size_t size = 0; - - if (stride == 2) - size = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); - else - size = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast(data.c_str()), count); - - bin.append(reinterpret_cast(&compressed[0]), size); -} diff --git a/Dependencies/meshoptimizer/gltf/wasistubs.cpp b/Dependencies/meshoptimizer/gltf/wasistubs.cpp deleted file mode 100644 index 2573b112..00000000 --- a/Dependencies/meshoptimizer/gltf/wasistubs.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifdef __wasi__ -#include -#include - -#include - -extern "C" void __cxa_throw(void* ptr, void* type, void* destructor) -{ - abort(); -} - -extern "C" void* __cxa_allocate_exception(size_t thrown_size) -{ - abort(); -} - -extern "C" int32_t __wasi_path_open32(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8) - __attribute__(( - __import_module__("wasi_snapshot_preview1"), - __import_name__("path_open32"), - __warn_unused_result__)); - -extern "C" int32_t __imported_wasi_snapshot_preview1_path_open(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int64_t arg5, int64_t arg6, int32_t arg7, int32_t arg8) -{ - return __wasi_path_open32(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); -} - -extern "C" int32_t __wasi_fd_seek32(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) - __attribute__(( - __import_module__("wasi_snapshot_preview1"), - __import_name__("fd_seek32"), - __warn_unused_result__)); - -extern "C" int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3) -{ - *(uint64_t*)arg3 = 0; - return __wasi_fd_seek32(arg0, arg1, arg2, arg3); -} - -extern "C" int32_t __imported_wasi_snapshot_preview1_clock_time_get(int32_t arg0, int64_t arg1, int32_t arg2) -{ - return __WASI_ERRNO_NOSYS; -} - -#endif diff --git a/Dependencies/meshoptimizer/gltf/wasistubs.txt b/Dependencies/meshoptimizer/gltf/wasistubs.txt deleted file mode 100644 index dc29d467..00000000 --- a/Dependencies/meshoptimizer/gltf/wasistubs.txt +++ /dev/null @@ -1,2 +0,0 @@ -__wasi_path_open32 -__wasi_fd_seek32 diff --git a/Dependencies/meshoptimizer/gltf/write.cpp b/Dependencies/meshoptimizer/gltf/write.cpp deleted file mode 100644 index fbc3b4c9..00000000 --- a/Dependencies/meshoptimizer/gltf/write.cpp +++ /dev/null @@ -1,1604 +0,0 @@ -// This file is part of gltfpack; see gltfpack.h for version/license details -#include "gltfpack.h" - -#include -#include -#include -#include - -static const char* componentType(cgltf_component_type type) -{ - switch (type) - { - case cgltf_component_type_r_8: - return "5120"; - case cgltf_component_type_r_8u: - return "5121"; - case cgltf_component_type_r_16: - return "5122"; - case cgltf_component_type_r_16u: - return "5123"; - case cgltf_component_type_r_32u: - return "5125"; - case cgltf_component_type_r_32f: - return "5126"; - default: - return "0"; - } -} - -static const char* shapeType(cgltf_type type) -{ - switch (type) - { - case cgltf_type_scalar: - return "SCALAR"; - case cgltf_type_vec2: - return "VEC2"; - case cgltf_type_vec3: - return "VEC3"; - case cgltf_type_vec4: - return "VEC4"; - case cgltf_type_mat2: - return "MAT2"; - case cgltf_type_mat3: - return "MAT3"; - case cgltf_type_mat4: - return "MAT4"; - default: - return ""; - } -} - -const char* attributeType(cgltf_attribute_type type) -{ - switch (type) - { - case cgltf_attribute_type_position: - return "POSITION"; - case cgltf_attribute_type_normal: - return "NORMAL"; - case cgltf_attribute_type_tangent: - return "TANGENT"; - case cgltf_attribute_type_texcoord: - return "TEXCOORD"; - case cgltf_attribute_type_color: - return "COLOR"; - case cgltf_attribute_type_joints: - return "JOINTS"; - case cgltf_attribute_type_weights: - return "WEIGHTS"; - default: - return "ATTRIBUTE"; - } -} - -const char* animationPath(cgltf_animation_path_type type) -{ - switch (type) - { - case cgltf_animation_path_type_translation: - return "translation"; - case cgltf_animation_path_type_rotation: - return "rotation"; - case cgltf_animation_path_type_scale: - return "scale"; - case cgltf_animation_path_type_weights: - return "weights"; - default: - return ""; - } -} - -static const char* lightType(cgltf_light_type type) -{ - switch (type) - { - case cgltf_light_type_directional: - return "directional"; - case cgltf_light_type_point: - return "point"; - case cgltf_light_type_spot: - return "spot"; - default: - return ""; - } -} - -static const char* alphaMode(cgltf_alpha_mode mode) -{ - switch (mode) - { - case cgltf_alpha_mode_opaque: - return "OPAQUE"; - - case cgltf_alpha_mode_mask: - return "MASK"; - - case cgltf_alpha_mode_blend: - return "BLEND"; - - default: - return ""; - } -} - -static const char* compressionMode(BufferView::Compression mode) -{ - switch (mode) - { - case BufferView::Compression_Attribute: - return "ATTRIBUTES"; - - case BufferView::Compression_Index: - return "TRIANGLES"; - - case BufferView::Compression_IndexSequence: - return "INDICES"; - - default: - return ""; - } -} - -static const char* compressionFilter(StreamFormat::Filter filter) -{ - switch (filter) - { - case StreamFormat::Filter_None: - return "NONE"; - - case StreamFormat::Filter_Oct: - return "OCTAHEDRAL"; - - case StreamFormat::Filter_Quat: - return "QUATERNION"; - - case StreamFormat::Filter_Exp: - return "EXPONENTIAL"; - - default: - return ""; - } -} - -static void writeTextureInfo(std::string& json, const cgltf_data* data, const cgltf_texture_view& view, const QuantizationTexture* qt, const char* scale = NULL) -{ - assert(view.texture); - - bool has_transform = false; - cgltf_texture_transform transform = {}; - transform.scale[0] = transform.scale[1] = 1.f; - - if (hasValidTransform(view)) - { - transform = view.transform; - has_transform = true; - } - - if (qt) - { - transform.offset[0] += qt->offset[0]; - transform.offset[1] += qt->offset[1]; - transform.scale[0] *= qt->scale[0] / float((1 << qt->bits) - 1); - transform.scale[1] *= qt->scale[1] / float((1 << qt->bits) - 1); - has_transform = true; - } - - append(json, "{\"index\":"); - append(json, size_t(view.texture - data->textures)); - if (view.texcoord != 0) - { - append(json, ",\"texCoord\":"); - append(json, size_t(view.texcoord)); - } - if (scale && view.scale != 1) - { - append(json, ",\""); - append(json, scale); - append(json, "\":"); - append(json, view.scale); - } - if (has_transform) - { - append(json, ",\"extensions\":{\"KHR_texture_transform\":{"); - append(json, "\"offset\":["); - append(json, transform.offset[0]); - append(json, ","); - append(json, transform.offset[1]); - append(json, "],\"scale\":["); - append(json, transform.scale[0]); - append(json, ","); - append(json, transform.scale[1]); - append(json, "]"); - if (transform.rotation != 0.f) - { - append(json, ",\"rotation\":"); - append(json, transform.rotation); - } - append(json, "}}"); - } - append(json, "}"); -} - -static const float white[4] = {1, 1, 1, 1}; -static const float black[4] = {0, 0, 0, 0}; - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_metallic_roughness& pbr, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"pbrMetallicRoughness\":{"); - if (memcmp(pbr.base_color_factor, white, 16) != 0) - { - comma(json); - append(json, "\"baseColorFactor\":["); - append(json, pbr.base_color_factor[0]); - append(json, ","); - append(json, pbr.base_color_factor[1]); - append(json, ","); - append(json, pbr.base_color_factor[2]); - append(json, ","); - append(json, pbr.base_color_factor[3]); - append(json, "]"); - } - if (pbr.base_color_texture.texture) - { - comma(json); - append(json, "\"baseColorTexture\":"); - writeTextureInfo(json, data, pbr.base_color_texture, qt); - } - if (pbr.metallic_factor != 1) - { - comma(json); - append(json, "\"metallicFactor\":"); - append(json, pbr.metallic_factor); - } - if (pbr.roughness_factor != 1) - { - comma(json); - append(json, "\"roughnessFactor\":"); - append(json, pbr.roughness_factor); - } - if (pbr.metallic_roughness_texture.texture) - { - comma(json); - append(json, "\"metallicRoughnessTexture\":"); - writeTextureInfo(json, data, pbr.metallic_roughness_texture, qt); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_specular_glossiness& pbr, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"KHR_materials_pbrSpecularGlossiness\":{"); - if (pbr.diffuse_texture.texture) - { - comma(json); - append(json, "\"diffuseTexture\":"); - writeTextureInfo(json, data, pbr.diffuse_texture, qt); - } - if (pbr.specular_glossiness_texture.texture) - { - comma(json); - append(json, "\"specularGlossinessTexture\":"); - writeTextureInfo(json, data, pbr.specular_glossiness_texture, qt); - } - if (memcmp(pbr.diffuse_factor, white, 16) != 0) - { - comma(json); - append(json, "\"diffuseFactor\":["); - append(json, pbr.diffuse_factor[0]); - append(json, ","); - append(json, pbr.diffuse_factor[1]); - append(json, ","); - append(json, pbr.diffuse_factor[2]); - append(json, ","); - append(json, pbr.diffuse_factor[3]); - append(json, "]"); - } - if (memcmp(pbr.specular_factor, white, 12) != 0) - { - comma(json); - append(json, "\"specularFactor\":["); - append(json, pbr.specular_factor[0]); - append(json, ","); - append(json, pbr.specular_factor[1]); - append(json, ","); - append(json, pbr.specular_factor[2]); - append(json, "]"); - } - if (pbr.glossiness_factor != 1) - { - comma(json); - append(json, "\"glossinessFactor\":"); - append(json, pbr.glossiness_factor); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_clearcoat& cc, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"KHR_materials_clearcoat\":{"); - if (cc.clearcoat_texture.texture) - { - comma(json); - append(json, "\"clearcoatTexture\":"); - writeTextureInfo(json, data, cc.clearcoat_texture, qt); - } - if (cc.clearcoat_roughness_texture.texture) - { - comma(json); - append(json, "\"clearcoatRoughnessTexture\":"); - writeTextureInfo(json, data, cc.clearcoat_roughness_texture, qt); - } - if (cc.clearcoat_normal_texture.texture) - { - comma(json); - append(json, "\"clearcoatNormalTexture\":"); - writeTextureInfo(json, data, cc.clearcoat_normal_texture, qt, "scale"); - } - if (cc.clearcoat_factor != 0) - { - comma(json); - append(json, "\"clearcoatFactor\":"); - append(json, cc.clearcoat_factor); - } - if (cc.clearcoat_factor != 0) - { - comma(json); - append(json, "\"clearcoatRoughnessFactor\":"); - append(json, cc.clearcoat_roughness_factor); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_transmission& tm, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"KHR_materials_transmission\":{"); - if (tm.transmission_texture.texture) - { - comma(json); - append(json, "\"transmissionTexture\":"); - writeTextureInfo(json, data, tm.transmission_texture, qt); - } - if (tm.transmission_factor != 0) - { - comma(json); - append(json, "\"transmissionFactor\":"); - append(json, tm.transmission_factor); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_ior& tm, const QuantizationTexture* qt) -{ - (void)data; - (void)qt; - - comma(json); - append(json, "\"KHR_materials_ior\":{"); - append(json, "\"ior\":"); - append(json, tm.ior); - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_specular& tm, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"KHR_materials_specular\":{"); - if (tm.specular_texture.texture) - { - comma(json); - append(json, "\"specularTexture\":"); - writeTextureInfo(json, data, tm.specular_texture, qt); - } - if (tm.specular_color_texture.texture) - { - comma(json); - append(json, "\"specularColorTexture\":"); - writeTextureInfo(json, data, tm.specular_color_texture, qt); - } - if (tm.specular_factor != 1) - { - comma(json); - append(json, "\"specularFactor\":"); - append(json, tm.specular_factor); - } - if (memcmp(tm.specular_color_factor, white, 12) != 0) - { - comma(json); - append(json, "\"specularColorFactor\":["); - append(json, tm.specular_color_factor[0]); - append(json, ","); - append(json, tm.specular_color_factor[1]); - append(json, ","); - append(json, tm.specular_color_factor[2]); - append(json, "]"); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_sheen& tm, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"KHR_materials_sheen\":{"); - if (tm.sheen_color_texture.texture) - { - comma(json); - append(json, "\"sheenColorTexture\":"); - writeTextureInfo(json, data, tm.sheen_color_texture, qt); - } - if (tm.sheen_roughness_texture.texture) - { - comma(json); - append(json, "\"sheenRoughnessTexture\":"); - writeTextureInfo(json, data, tm.sheen_roughness_texture, qt); - } - if (memcmp(tm.sheen_color_factor, black, 12) != 0) - { - comma(json); - append(json, "\"sheenColorFactor\":["); - append(json, tm.sheen_color_factor[0]); - append(json, ","); - append(json, tm.sheen_color_factor[1]); - append(json, ","); - append(json, tm.sheen_color_factor[2]); - append(json, "]"); - } - if (tm.sheen_roughness_factor != 0) - { - comma(json); - append(json, "\"sheenRoughnessFactor\":"); - append(json, tm.sheen_roughness_factor); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_volume& tm, const QuantizationPosition* qp, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"KHR_materials_volume\":{"); - if (tm.thickness_texture.texture) - { - comma(json); - append(json, "\"thicknessTexture\":"); - writeTextureInfo(json, data, tm.thickness_texture, qt); - } - if (tm.thickness_factor != 0) - { - // thickness is in mesh coordinate space which is rescaled by quantization - float node_scale = qp ? qp->scale / float((1 << qp->bits) - 1) : 1.f; - - comma(json); - append(json, "\"thicknessFactor\":"); - append(json, tm.thickness_factor / node_scale); - } - if (memcmp(tm.attenuation_color, white, 12) != 0) - { - comma(json); - append(json, "\"attenuationColor\":["); - append(json, tm.attenuation_color[0]); - append(json, ","); - append(json, tm.attenuation_color[1]); - append(json, ","); - append(json, tm.attenuation_color[2]); - append(json, "]"); - } - if (tm.attenuation_distance != FLT_MAX) - { - comma(json); - append(json, "\"attenuationDistance\":"); - append(json, tm.attenuation_distance); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_emissive_strength& tm) -{ - (void)data; - - comma(json); - append(json, "\"KHR_materials_emissive_strength\":{"); - if (tm.emissive_strength != 1) - { - comma(json); - append(json, "\"emissiveStrength\":"); - append(json, tm.emissive_strength); - } - append(json, "}"); -} - -static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_iridescence& tm, const QuantizationTexture* qt) -{ - comma(json); - append(json, "\"KHR_materials_iridescence\":{"); - if (tm.iridescence_factor != 0) - { - comma(json); - append(json, "\"iridescenceFactor\":"); - append(json, tm.iridescence_factor); - } - if (tm.iridescence_texture.texture) - { - comma(json); - append(json, "\"iridescenceTexture\":"); - writeTextureInfo(json, data, tm.iridescence_texture, qt); - } - if (tm.iridescence_ior != 1.3f) - { - comma(json); - append(json, "\"iridescenceIor\":"); - append(json, tm.iridescence_ior); - } - if (tm.iridescence_thickness_min != 100.f) - { - comma(json); - append(json, "\"iridescenceThicknessMinimum\":"); - append(json, tm.iridescence_thickness_min); - } - if (tm.iridescence_thickness_max != 400.f) - { - comma(json); - append(json, "\"iridescenceThicknessMaximum\":"); - append(json, tm.iridescence_thickness_max); - } - if (tm.iridescence_thickness_texture.texture) - { - comma(json); - append(json, "\"iridescenceThicknessTexture\":"); - writeTextureInfo(json, data, tm.iridescence_thickness_texture, qt); - } - append(json, "}"); -} - -void writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt) -{ - if (material.name && *material.name) - { - comma(json); - append(json, "\"name\":\""); - append(json, material.name); - append(json, "\""); - } - - if (material.has_pbr_metallic_roughness) - { - writeMaterialComponent(json, data, material.pbr_metallic_roughness, qt); - } - - if (material.normal_texture.texture) - { - comma(json); - append(json, "\"normalTexture\":"); - writeTextureInfo(json, data, material.normal_texture, qt, "scale"); - } - - if (material.occlusion_texture.texture) - { - comma(json); - append(json, "\"occlusionTexture\":"); - writeTextureInfo(json, data, material.occlusion_texture, qt, "strength"); - } - - if (material.emissive_texture.texture) - { - comma(json); - append(json, "\"emissiveTexture\":"); - writeTextureInfo(json, data, material.emissive_texture, qt); - } - - if (memcmp(material.emissive_factor, black, 12) != 0) - { - comma(json); - append(json, "\"emissiveFactor\":["); - append(json, material.emissive_factor[0]); - append(json, ","); - append(json, material.emissive_factor[1]); - append(json, ","); - append(json, material.emissive_factor[2]); - append(json, "]"); - } - - if (material.alpha_mode != cgltf_alpha_mode_opaque) - { - comma(json); - append(json, "\"alphaMode\":\""); - append(json, alphaMode(material.alpha_mode)); - append(json, "\""); - } - - if (material.alpha_cutoff != 0.5f) - { - comma(json); - append(json, "\"alphaCutoff\":"); - append(json, material.alpha_cutoff); - } - - if (material.double_sided) - { - comma(json); - append(json, "\"doubleSided\":true"); - } - - if (material.has_pbr_specular_glossiness || material.has_clearcoat || material.has_transmission || material.has_ior || material.has_specular || material.has_sheen || material.has_volume || material.has_emissive_strength || material.has_iridescence || material.unlit) - { - comma(json); - append(json, "\"extensions\":{"); - - if (material.has_pbr_specular_glossiness) - { - writeMaterialComponent(json, data, material.pbr_specular_glossiness, qt); - } - - if (material.has_clearcoat) - { - writeMaterialComponent(json, data, material.clearcoat, qt); - } - - if (material.has_transmission) - { - writeMaterialComponent(json, data, material.transmission, qt); - } - - if (material.has_ior) - { - writeMaterialComponent(json, data, material.ior, qt); - } - - if (material.has_specular) - { - writeMaterialComponent(json, data, material.specular, qt); - } - - if (material.has_sheen) - { - writeMaterialComponent(json, data, material.sheen, qt); - } - - if (material.has_volume) - { - writeMaterialComponent(json, data, material.volume, qp, qt); - } - - if (material.has_emissive_strength) - { - writeMaterialComponent(json, data, material.emissive_strength); - } - - if (material.has_iridescence) - { - writeMaterialComponent(json, data, material.iridescence, qt); - } - - if (material.unlit) - { - comma(json); - append(json, "\"KHR_materials_unlit\":{}"); - } - - append(json, "}"); - } -} - -size_t getBufferView(std::vector& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant) -{ - if (variant >= 0) - { - for (size_t i = 0; i < views.size(); ++i) - { - BufferView& v = views[i]; - - if (v.kind == kind && v.filter == filter && v.compression == compression && v.stride == stride && v.variant == variant) - return i; - } - } - - BufferView view = {kind, filter, compression, stride, variant}; - views.push_back(view); - - return views.size() - 1; -} - -void writeBufferView(std::string& json, BufferView::Kind kind, StreamFormat::Filter filter, size_t count, size_t stride, size_t bin_offset, size_t bin_size, BufferView::Compression compression, size_t compressed_offset, size_t compressed_size) -{ - assert(bin_size == count * stride); - - // when compression is enabled, we store uncompressed data in buffer 1 and compressed data in buffer 0 - // when compression is disabled, we store uncompressed data in buffer 0 - size_t buffer = compression != BufferView::Compression_None ? 1 : 0; - - append(json, "{\"buffer\":"); - append(json, buffer); - append(json, ",\"byteOffset\":"); - append(json, bin_offset); - append(json, ",\"byteLength\":"); - append(json, bin_size); - if (kind == BufferView::Kind_Vertex) - { - append(json, ",\"byteStride\":"); - append(json, stride); - } - if (kind == BufferView::Kind_Vertex || kind == BufferView::Kind_Index) - { - append(json, ",\"target\":"); - append(json, (kind == BufferView::Kind_Vertex) ? "34962" : "34963"); - } - if (compression != BufferView::Compression_None) - { - append(json, ",\"extensions\":{"); - append(json, "\"EXT_meshopt_compression\":{"); - append(json, "\"buffer\":0"); - append(json, ",\"byteOffset\":"); - append(json, size_t(compressed_offset)); - append(json, ",\"byteLength\":"); - append(json, size_t(compressed_size)); - append(json, ",\"byteStride\":"); - append(json, stride); - append(json, ",\"mode\":\""); - append(json, compressionMode(compression)); - append(json, "\""); - if (filter != StreamFormat::Filter_None) - { - append(json, ",\"filter\":\""); - append(json, compressionFilter(filter)); - append(json, "\""); - } - append(json, ",\"count\":"); - append(json, count); - append(json, "}}"); - } - append(json, "}"); -} - -static void writeAccessor(std::string& json, size_t view, size_t offset, cgltf_type type, cgltf_component_type component_type, bool normalized, size_t count, const float* min = 0, const float* max = 0, size_t numminmax = 0) -{ - append(json, "{\"bufferView\":"); - append(json, view); - append(json, ",\"byteOffset\":"); - append(json, offset); - append(json, ",\"componentType\":"); - append(json, componentType(component_type)); - append(json, ",\"count\":"); - append(json, count); - append(json, ",\"type\":\""); - append(json, shapeType(type)); - append(json, "\""); - - if (normalized) - { - append(json, ",\"normalized\":true"); - } - - if (min && max) - { - assert(numminmax); - - append(json, ",\"min\":["); - for (size_t k = 0; k < numminmax; ++k) - { - comma(json); - append(json, min[k]); - } - append(json, "],\"max\":["); - for (size_t k = 0; k < numminmax; ++k) - { - comma(json); - append(json, max[k]); - } - append(json, "]"); - } - - append(json, "}"); -} - -static void writeEmbeddedImage(std::string& json, std::vector& views, const char* data, size_t size, const char* mime_type, TextureKind kind) -{ - size_t view = getBufferView(views, BufferView::Kind_Image, StreamFormat::Filter_None, BufferView::Compression_None, 1, -1 - kind); - - assert(views[view].data.empty()); - views[view].data.assign(data, size); - - append(json, "\"bufferView\":"); - append(json, view); - append(json, ",\"mimeType\":\""); - append(json, mime_type); - append(json, "\""); -} - -static std::string decodeUri(const char* uri) -{ - std::string result = uri; - - if (!result.empty()) - { - cgltf_decode_uri(&result[0]); - result.resize(strlen(result.c_str())); - } - - return result; -} - -void writeSampler(std::string& json, const cgltf_sampler& sampler) -{ - if (sampler.mag_filter != 0) - { - comma(json); - append(json, "\"magFilter\":"); - append(json, size_t(sampler.mag_filter)); - } - if (sampler.min_filter != 0) - { - comma(json); - append(json, "\"minFilter\":"); - append(json, size_t(sampler.min_filter)); - } - if (sampler.wrap_s != 10497) - { - comma(json); - append(json, "\"wrapS\":"); - append(json, size_t(sampler.wrap_s)); - } - if (sampler.wrap_t != 10497) - { - comma(json); - append(json, "\"wrapT\":"); - append(json, size_t(sampler.wrap_t)); - } -} - -void writeImage(std::string& json, std::vector& views, const cgltf_image& image, const ImageInfo& info, size_t index, const char* input_path, const Settings& settings) -{ - bool dataUri = image.uri && strncmp(image.uri, "data:", 5) == 0; - - if (image.uri && !dataUri && !settings.texture_embed && !settings.texture_ktx2) - { - // fast-path: we don't need to read the image to memory - append(json, "\"uri\":\""); - append(json, image.uri); - append(json, "\""); - return; - } - - std::string img_data; - std::string mime_type; - if (!readImage(image, input_path, img_data, mime_type)) - { - fprintf(stderr, "Warning: unable to read image %d (%s), skipping\n", int(index), image.uri ? image.uri : "?"); - return; - } - - writeEmbeddedImage(json, views, img_data.c_str(), img_data.size(), mime_type.c_str(), info.kind); -} - -void writeEncodedImage(std::string& json, std::vector& views, const cgltf_image& image, const std::string& encoded, const ImageInfo& info, const char* output_path, const Settings& settings) -{ - bool dataUri = image.uri && strncmp(image.uri, "data:", 5) == 0; - - if (!settings.texture_embed && image.uri && !dataUri && output_path) - { - std::string ktx_uri = getFileName(image.uri) + ".ktx2"; - std::string ktx_full_path = getFullPath(decodeUri(ktx_uri.c_str()).c_str(), output_path); - - if (writeFile(ktx_full_path.c_str(), encoded)) - { - append(json, "\"uri\":\""); - append(json, ktx_uri); - append(json, "\""); - } - else - { - fprintf(stderr, "Warning: unable to save encoded image %s, skipping\n", image.uri); - } - } - else - { - writeEmbeddedImage(json, views, encoded.c_str(), encoded.size(), "image/ktx2", info.kind); - } -} - -void writeTexture(std::string& json, const cgltf_texture& texture, cgltf_data* data, const Settings& settings) -{ - if (texture.image) - { - if (texture.sampler) - { - append(json, "\"sampler\":"); - append(json, size_t(texture.sampler - data->samplers)); - append(json, ","); - } - - if (settings.texture_ktx2) - { - append(json, "\"extensions\":{\"KHR_texture_basisu\":{\"source\":"); - append(json, size_t(texture.image - data->images)); - append(json, "}}"); - } - else - { - append(json, "\"source\":"); - append(json, size_t(texture.image - data->images)); - } - } -} - -void writeMeshAttributes(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings) -{ - std::string scratch; - - for (size_t j = 0; j < mesh.streams.size(); ++j) - { - const Stream& stream = mesh.streams[j]; - - if (stream.target != target) - continue; - - scratch.clear(); - StreamFormat format = writeVertexStream(scratch, stream, qp, qt, settings); - BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; - - size_t view = getBufferView(views, BufferView::Kind_Vertex, format.filter, compression, format.stride, stream.type); - size_t offset = views[view].data.size(); - views[view].data += scratch; - - comma(json_accessors); - if (stream.type == cgltf_attribute_type_position) - { - float min[3] = {}; - float max[3] = {}; - getPositionBounds(min, max, stream, settings.quantize ? &qp : NULL); - - writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size(), min, max, 3); - } - else - { - writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size()); - } - - size_t vertex_accr = accr_offset++; - - comma(json); - append(json, "\""); - append(json, attributeType(stream.type)); - if (stream.type != cgltf_attribute_type_position && stream.type != cgltf_attribute_type_normal && stream.type != cgltf_attribute_type_tangent) - { - append(json, "_"); - append(json, size_t(stream.index)); - } - append(json, "\":"); - append(json, vertex_accr); - } -} - -size_t writeMeshIndices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const Settings& settings) -{ - std::string scratch; - StreamFormat format = writeIndexStream(scratch, mesh.indices); - BufferView::Compression compression = settings.compress ? (mesh.type == cgltf_primitive_type_triangles ? BufferView::Compression_Index : BufferView::Compression_IndexSequence) : BufferView::Compression_None; - - size_t view = getBufferView(views, BufferView::Kind_Index, StreamFormat::Filter_None, compression, format.stride); - size_t offset = views[view].data.size(); - views[view].data += scratch; - - comma(json_accessors); - writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, mesh.indices.size()); - - size_t index_accr = accr_offset++; - - return index_accr; -} - -static size_t writeAnimationTime(std::vector& views, std::string& json_accessors, size_t& accr_offset, float mint, int frames, float period, const Settings& settings) -{ - std::vector time(frames); - - for (int j = 0; j < frames; ++j) - time[j] = mint + float(j) * period; - - std::string scratch; - StreamFormat format = writeTimeStream(scratch, time); - BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; - - size_t view = getBufferView(views, BufferView::Kind_Time, StreamFormat::Filter_None, compression, format.stride); - size_t offset = views[view].data.size(); - views[view].data += scratch; - - comma(json_accessors); - writeAccessor(json_accessors, view, offset, cgltf_type_scalar, format.component_type, format.normalized, frames, &time.front(), &time.back(), 1); - - size_t time_accr = accr_offset++; - - return time_accr; -} - -size_t writeJointBindMatrices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings) -{ - std::string scratch; - - for (size_t j = 0; j < skin.joints_count; ++j) - { - float transform[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; - - if (skin.inverse_bind_matrices) - { - cgltf_accessor_read_float(skin.inverse_bind_matrices, j, transform, 16); - } - - if (settings.quantize) - { - float node_scale = qp.scale / float((1 << qp.bits) - 1); - - // pos_offset has to be applied first, thus it results in an offset rotated by the bind matrix - transform[12] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8]; - transform[13] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9]; - transform[14] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10]; - - // node_scale will be applied before the rotation/scale from transform - for (int k = 0; k < 12; ++k) - transform[k] *= node_scale; - } - - scratch.append(reinterpret_cast(transform), sizeof(transform)); - } - - BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; - - size_t view = getBufferView(views, BufferView::Kind_Skin, StreamFormat::Filter_None, compression, 64); - size_t offset = views[view].data.size(); - views[view].data += scratch; - - comma(json_accessors); - writeAccessor(json_accessors, view, offset, cgltf_type_mat4, cgltf_component_type_r_32f, false, skin.joints_count); - - size_t matrix_accr = accr_offset++; - - return matrix_accr; -} - -static void writeInstanceData(std::vector& views, std::string& json_accessors, cgltf_animation_path_type type, const std::vector& data, const Settings& settings) -{ - BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; - - std::string scratch; - StreamFormat format = writeKeyframeStream(scratch, type, data, settings); - - size_t view = getBufferView(views, BufferView::Kind_Instance, format.filter, compression, format.stride, type); - size_t offset = views[view].data.size(); - views[view].data += scratch; - - comma(json_accessors); - writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, data.size()); -} - -size_t writeInstances(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& transforms, const QuantizationPosition& qp, const Settings& settings) -{ - std::vector position, rotation, scale; - position.resize(transforms.size()); - rotation.resize(transforms.size()); - scale.resize(transforms.size()); - - for (size_t i = 0; i < transforms.size(); ++i) - { - decomposeTransform(position[i].f, rotation[i].f, scale[i].f, transforms[i].data); - - if (settings.quantize) - { - const float* transform = transforms[i].data; - - float node_scale = qp.scale / float((1 << qp.bits) - 1); - - // pos_offset has to be applied first, thus it results in an offset rotated by the instance matrix - position[i].f[0] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8]; - position[i].f[1] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9]; - position[i].f[2] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10]; - - // node_scale will be applied before the rotation/scale from transform - scale[i].f[0] *= node_scale; - scale[i].f[1] *= node_scale; - scale[i].f[2] *= node_scale; - } - } - - writeInstanceData(views, json_accessors, cgltf_animation_path_type_translation, position, settings); - writeInstanceData(views, json_accessors, cgltf_animation_path_type_rotation, rotation, settings); - writeInstanceData(views, json_accessors, cgltf_animation_path_type_scale, scale, settings); - - size_t result = accr_offset; - accr_offset += 3; - return result; -} - -void writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp) -{ - comma(json); - append(json, "{\"mesh\":"); - append(json, mesh_offset); - if (skin) - { - comma(json); - append(json, "\"skin\":"); - append(json, size_t(skin - data->skins)); - } - if (qp) - { - float node_scale = qp->scale / float((1 << qp->bits) - 1); - - append(json, ",\"translation\":["); - append(json, qp->offset[0]); - append(json, ","); - append(json, qp->offset[1]); - append(json, ","); - append(json, qp->offset[2]); - append(json, "],\"scale\":["); - append(json, node_scale); - append(json, ","); - append(json, node_scale); - append(json, ","); - append(json, node_scale); - append(json, "]"); - } - if (node && node->weights_count) - { - append(json, ",\"weights\":["); - for (size_t j = 0; j < node->weights_count; ++j) - { - comma(json); - append(json, node->weights[j]); - } - append(json, "]"); - } - append(json, "}"); -} - -void writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset) -{ - comma(json); - append(json, "{\"mesh\":"); - append(json, mesh_offset); - append(json, ",\"extensions\":{\"EXT_mesh_gpu_instancing\":{\"attributes\":{"); - - comma(json); - append(json, "\"TRANSLATION\":"); - append(json, accr_offset + 0); - - comma(json); - append(json, "\"ROTATION\":"); - append(json, accr_offset + 1); - - comma(json); - append(json, "\"SCALE\":"); - append(json, accr_offset + 2); - - append(json, "}}}"); - append(json, "}"); -} - -void writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector& nodes, cgltf_data* data) -{ - comma(json); - append(json, "{"); - append(json, "\"joints\":["); - for (size_t j = 0; j < skin.joints_count; ++j) - { - comma(json); - append(json, size_t(nodes[skin.joints[j] - data->nodes].remap)); - } - append(json, "]"); - append(json, ",\"inverseBindMatrices\":"); - append(json, matrix_accr); - if (skin.skeleton) - { - comma(json); - append(json, "\"skeleton\":"); - append(json, size_t(nodes[skin.skeleton - data->nodes].remap)); - } - append(json, "}"); -} - -void writeNode(std::string& json, const cgltf_node& node, const std::vector& nodes, cgltf_data* data) -{ - const NodeInfo& ni = nodes[&node - data->nodes]; - - if (node.name && *node.name) - { - comma(json); - append(json, "\"name\":\""); - append(json, node.name); - append(json, "\""); - } - if (node.has_translation) - { - comma(json); - append(json, "\"translation\":["); - append(json, node.translation[0]); - append(json, ","); - append(json, node.translation[1]); - append(json, ","); - append(json, node.translation[2]); - append(json, "]"); - } - if (node.has_rotation) - { - comma(json); - append(json, "\"rotation\":["); - append(json, node.rotation[0]); - append(json, ","); - append(json, node.rotation[1]); - append(json, ","); - append(json, node.rotation[2]); - append(json, ","); - append(json, node.rotation[3]); - append(json, "]"); - } - if (node.has_scale) - { - comma(json); - append(json, "\"scale\":["); - append(json, node.scale[0]); - append(json, ","); - append(json, node.scale[1]); - append(json, ","); - append(json, node.scale[2]); - append(json, "]"); - } - if (node.has_matrix) - { - comma(json); - append(json, "\"matrix\":["); - for (int k = 0; k < 16; ++k) - { - comma(json); - append(json, node.matrix[k]); - } - append(json, "]"); - } - - bool has_children = !ni.meshes.empty(); - for (size_t j = 0; j < node.children_count; ++j) - has_children |= nodes[node.children[j] - data->nodes].keep; - - if (has_children) - { - comma(json); - append(json, "\"children\":["); - for (size_t j = 0; j < node.children_count; ++j) - { - const NodeInfo& ci = nodes[node.children[j] - data->nodes]; - - if (ci.keep) - { - comma(json); - append(json, size_t(ci.remap)); - } - } - for (size_t j = 0; j < ni.meshes.size(); ++j) - { - comma(json); - append(json, ni.meshes[j]); - } - append(json, "]"); - } - if (node.camera) - { - comma(json); - append(json, "\"camera\":"); - append(json, size_t(node.camera - data->cameras)); - } - if (node.light) - { - comma(json); - append(json, "\"extensions\":{\"KHR_lights_punctual\":{\"light\":"); - append(json, size_t(node.light - data->lights)); - append(json, "}}"); - } -} - -void writeAnimation(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector& nodes, const Settings& settings) -{ - std::vector tracks; - - for (size_t j = 0; j < animation.tracks.size(); ++j) - { - const Track& track = animation.tracks[j]; - - const NodeInfo& ni = nodes[track.node - data->nodes]; - - if (!ni.keep) - continue; - - if (!settings.anim_const && (ni.animated_paths & (1 << track.path)) == 0) - continue; - - tracks.push_back(&track); - } - - if (tracks.empty()) - { - char index[16]; - sprintf(index, "%d", int(i)); - - fprintf(stderr, "Warning: ignoring animation %s because it has no tracks with motion; use -ac to override\n", animation.name && *animation.name ? animation.name : index); - return; - } - - bool needs_time = false; - bool needs_pose = false; - - for (size_t j = 0; j < tracks.size(); ++j) - { - const Track& track = *tracks[j]; - - assert(track.time.empty()); - assert(track.data.size() == track.components * (track.constant ? 1 : animation.frames)); - - needs_time = needs_time || !track.constant; - needs_pose = needs_pose || track.constant; - } - - bool needs_range = needs_pose && !needs_time && animation.frames > 1; - - needs_pose = needs_pose && !(needs_range && tracks.size() == 1); - - assert(int(needs_time) + int(needs_pose) + int(needs_range) <= 2); - - float animation_period = 1.f / float(settings.anim_freq); - float animation_length = float(animation.frames - 1) * animation_period; - - size_t time_accr = needs_time ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, animation.frames, animation_period, settings) : 0; - size_t pose_accr = needs_pose ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 1, 0.f, settings) : 0; - size_t range_accr = needs_range ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 2, animation_length, settings) : 0; - - std::string json_samplers; - std::string json_channels; - - size_t track_offset = 0; - - for (size_t j = 0; j < tracks.size(); ++j) - { - const Track& track = *tracks[j]; - - bool range = needs_range && j == 0; - int range_size = range ? 2 : 1; - - std::string scratch; - StreamFormat format = writeKeyframeStream(scratch, track.path, track.data, settings); - - if (range) - { - assert(range_size == 2); - scratch += scratch; - } - - BufferView::Compression compression = settings.compress && track.path != cgltf_animation_path_type_weights ? BufferView::Compression_Attribute : BufferView::Compression_None; - - size_t view = getBufferView(views, BufferView::Kind_Keyframe, format.filter, compression, format.stride, track.path); - size_t offset = views[view].data.size(); - views[view].data += scratch; - - comma(json_accessors); - writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, track.data.size() * range_size); - - size_t data_accr = accr_offset++; - - comma(json_samplers); - append(json_samplers, "{\"input\":"); - append(json_samplers, range ? range_accr : track.constant ? pose_accr : time_accr); - append(json_samplers, ",\"output\":"); - append(json_samplers, data_accr); - if (track.interpolation == cgltf_interpolation_type_step) - append(json_samplers, ",\"interpolation\":\"STEP\""); - append(json_samplers, "}"); - - const NodeInfo& tni = nodes[track.node - data->nodes]; - size_t target_node = size_t(tni.remap); - - if (track.path == cgltf_animation_path_type_weights) - { - assert(tni.meshes.size() == 1); - target_node = tni.meshes[0]; - } - - comma(json_channels); - append(json_channels, "{\"sampler\":"); - append(json_channels, track_offset); - append(json_channels, ",\"target\":{\"node\":"); - append(json_channels, target_node); - append(json_channels, ",\"path\":\""); - append(json_channels, animationPath(track.path)); - append(json_channels, "\"}}"); - - track_offset++; - } - - comma(json); - append(json, "{"); - if (animation.name && *animation.name) - { - append(json, "\"name\":\""); - append(json, animation.name); - append(json, "\","); - } - append(json, "\"samplers\":["); - append(json, json_samplers); - append(json, "],\"channels\":["); - append(json, json_channels); - append(json, "]}"); -} - -void writeCamera(std::string& json, const cgltf_camera& camera) -{ - comma(json); - append(json, "{"); - - switch (camera.type) - { - case cgltf_camera_type_perspective: - append(json, "\"type\":\"perspective\",\"perspective\":{"); - append(json, "\"yfov\":"); - append(json, camera.data.perspective.yfov); - append(json, ",\"znear\":"); - append(json, camera.data.perspective.znear); - if (camera.data.perspective.aspect_ratio != 0.f) - { - append(json, ",\"aspectRatio\":"); - append(json, camera.data.perspective.aspect_ratio); - } - if (camera.data.perspective.zfar != 0.f) - { - append(json, ",\"zfar\":"); - append(json, camera.data.perspective.zfar); - } - append(json, "}"); - break; - - case cgltf_camera_type_orthographic: - append(json, "\"type\":\"orthographic\",\"orthographic\":{"); - append(json, "\"xmag\":"); - append(json, camera.data.orthographic.xmag); - append(json, ",\"ymag\":"); - append(json, camera.data.orthographic.ymag); - append(json, ",\"znear\":"); - append(json, camera.data.orthographic.znear); - append(json, ",\"zfar\":"); - append(json, camera.data.orthographic.zfar); - append(json, "}"); - break; - - default: - fprintf(stderr, "Warning: skipping camera of unknown type\n"); - } - - append(json, "}"); -} - -void writeLight(std::string& json, const cgltf_light& light) -{ - comma(json); - append(json, "{\"type\":\""); - append(json, lightType(light.type)); - append(json, "\""); - if (memcmp(light.color, white, 12) != 0) - { - comma(json); - append(json, "\"color\":["); - append(json, light.color[0]); - append(json, ","); - append(json, light.color[1]); - append(json, ","); - append(json, light.color[2]); - append(json, "]"); - } - if (light.intensity != 1.f) - { - comma(json); - append(json, "\"intensity\":"); - append(json, light.intensity); - } - if (light.range != 0.f) - { - comma(json); - append(json, "\"range\":"); - append(json, light.range); - } - if (light.type == cgltf_light_type_spot) - { - comma(json); - append(json, "\"spot\":{"); - append(json, "\"innerConeAngle\":"); - append(json, light.spot_inner_cone_angle); - append(json, ",\"outerConeAngle\":"); - append(json, light.spot_outer_cone_angle == 0.f ? 0.78539816339f : light.spot_outer_cone_angle); - append(json, "}"); - } - append(json, "}"); -} - -void writeArray(std::string& json, const char* name, const std::string& contents) -{ - if (contents.empty()) - return; - - comma(json); - append(json, "\""); - append(json, name); - append(json, "\":["); - append(json, contents); - append(json, "]"); -} - -void writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count) -{ - bool used_extensions = false; - bool required_extensions = false; - - for (size_t i = 0; i < count; ++i) - { - used_extensions |= extensions[i].used; - required_extensions |= extensions[i].used && extensions[i].required; - } - - if (used_extensions) - { - comma(json); - append(json, "\"extensionsUsed\":["); - for (size_t i = 0; i < count; ++i) - if (extensions[i].used) - { - comma(json); - append(json, "\""); - append(json, extensions[i].name); - append(json, "\""); - } - append(json, "]"); - } - - if (required_extensions) - { - comma(json); - append(json, "\"extensionsRequired\":["); - for (size_t i = 0; i < count; ++i) - if (extensions[i].used && extensions[i].required) - { - comma(json); - append(json, "\""); - append(json, extensions[i].name); - append(json, "\""); - } - append(json, "]"); - } -} - -void writeExtras(std::string& json, const std::string& data, const cgltf_extras& extras) -{ - if (extras.start_offset == extras.end_offset) - return; - - assert(extras.start_offset < data.size()); - assert(extras.end_offset <= data.size()); - - comma(json); - append(json, "\"extras\":"); - appendJson(json, data.c_str() + extras.start_offset, data.c_str() + extras.end_offset); -} - -void writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots) -{ - comma(json); - append(json, "{"); - if (scene.name && *scene.name) - { - append(json, "\"name\":\""); - append(json, scene.name); - append(json, "\""); - } - if (!roots.empty()) - { - comma(json); - append(json, "\"nodes\":["); - append(json, roots); - append(json, "]"); - } - append(json, "}"); -} diff --git a/Dependencies/meshoptimizer/js/README.md b/Dependencies/meshoptimizer/js/README.md deleted file mode 100644 index 9fab69d8..00000000 --- a/Dependencies/meshoptimizer/js/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# meshoptimizer.js - -This folder contains JavaScript/WebAssembly modules that can be used to access parts of functionality of meshoptimizer library. While normally these would be used internally by glTF loaders, processors and other Web optimization tools, they can also be used directly if needed. The modules are available as an [NPM package](https://www.npmjs.com/package/meshoptimizer) but can also be redistributed individually on a file-by-file basis. - -## Structure - -Each component comes in two variants: - -- `meshopt_component.js` uses a UMD-style module declaration and can be used by a wide variety of JavaScript module loaders, including node.js require(), AMD, Common.JS, and can also be loaded into the web page directly via a `