From e030a579e42bd6c88fa1419acb77428caa03a254 Mon Sep 17 00:00:00 2001 From: Jorrit Rouwe Date: Sun, 12 Jan 2025 16:20:30 +0100 Subject: [PATCH] Embedding assets into the application bundle on macOS (#1453) * All asset reads go through the AssetStream class * Implemented alert for macOS Fixes #1452 --- JoltViewer/JoltViewer.cmake | 2 +- Samples/Samples.cmake | 31 +++++++++- Samples/Tests/Character/CharacterBaseTest.cpp | 4 +- .../ConvexCollision/ConvexHullShrinkTest.cpp | 37 ++++++------ .../Tests/ConvexCollision/ConvexHullTest.cpp | 37 ++++++------ Samples/Tests/General/HighSpeedTest.cpp | 4 +- Samples/Tests/General/MultithreadedTest.cpp | 6 +- Samples/Tests/General/SensorTest.cpp | 6 +- Samples/Tests/Rig/BigWorldTest.cpp | 6 +- Samples/Tests/Rig/KinematicRigTest.cpp | 7 ++- Samples/Tests/Rig/LoadRigTest.cpp | 2 +- Samples/Tests/Rig/LoadSaveBinaryRigTest.cpp | 2 +- Samples/Tests/Rig/LoadSaveRigTest.cpp | 2 +- Samples/Tests/Rig/PoweredRigTest.cpp | 7 ++- Samples/Tests/Rig/RigPileTest.cpp | 9 ++- Samples/Tests/Rig/SkeletonMapperTest.cpp | 33 +++++++---- Samples/Tests/Shapes/HeightFieldShapeTest.cpp | 2 +- Samples/Tests/Vehicle/VehicleTest.cpp | 12 ++-- Samples/Utils/RagdollLoader.cpp | 4 +- TestFramework/Application/DebugUI.cpp | 8 +-- TestFramework/Renderer/DX12/RendererDX12.cpp | 9 +-- TestFramework/Renderer/Font.cpp | 2 +- TestFramework/Renderer/MTL/RendererMTL.mm | 7 ++- TestFramework/Renderer/VK/RendererVK.cpp | 4 +- TestFramework/TestFramework.cmake | 26 ++++++++- TestFramework/Utils/AssetStream.cpp | 19 +++++++ TestFramework/Utils/AssetStream.h | 27 +++++++++ TestFramework/Utils/AssetStream.mm | 24 ++++++++ TestFramework/Utils/Log.mm | 57 +++++++++++++++++++ TestFramework/Utils/ReadData.cpp | 6 +- TestFramework/Window/ApplicationWindowMacOS.h | 4 +- 31 files changed, 307 insertions(+), 99 deletions(-) create mode 100644 TestFramework/Utils/AssetStream.cpp create mode 100644 TestFramework/Utils/AssetStream.h create mode 100644 TestFramework/Utils/AssetStream.mm create mode 100644 TestFramework/Utils/Log.mm diff --git a/JoltViewer/JoltViewer.cmake b/JoltViewer/JoltViewer.cmake index 189f38be2..b32fdb116 100644 --- a/JoltViewer/JoltViewer.cmake +++ b/JoltViewer/JoltViewer.cmake @@ -13,7 +13,7 @@ source_group(TREE ${JOLT_VIEWER_ROOT} FILES ${JOLT_VIEWER_SRC_FILES}) # Create JoltViewer executable if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") - add_executable(JoltViewer MACOSX_BUNDLE ${JOLT_VIEWER_SRC_FILES}) + add_executable(JoltViewer MACOSX_BUNDLE ${JOLT_VIEWER_SRC_FILES} ${TEST_FRAMEWORK_ASSETS}) set_property(TARGET JoltViewer PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/iOS/JoltViewerInfo.plist") set_property(TARGET JoltViewer PROPERTY XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.joltphysics.joltviewer") else() diff --git a/Samples/Samples.cmake b/Samples/Samples.cmake index 92918509d..2fd18897c 100644 --- a/Samples/Samples.cmake +++ b/Samples/Samples.cmake @@ -308,12 +308,41 @@ if (ENABLE_OBJECT_STREAM) ) endif() +# Assets used by the samples +set(SAMPLES_ASSETS + ${PHYSICS_REPO_ROOT}/Assets/convex_hulls.bin + ${PHYSICS_REPO_ROOT}/Assets/heightfield1.bin + ${PHYSICS_REPO_ROOT}/Assets/Human/dead_pose1.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/dead_pose2.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/dead_pose3.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/dead_pose4.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/jog_hd.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/neutral.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/neutral_hd.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/skeleton_hd.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/sprint.tof + ${PHYSICS_REPO_ROOT}/Assets/Human/walk.tof + ${PHYSICS_REPO_ROOT}/Assets/Human.tof + ${PHYSICS_REPO_ROOT}/Assets/Racetracks/Zandvoort.csv + ${PHYSICS_REPO_ROOT}/Assets/terrain1.bof + ${PHYSICS_REPO_ROOT}/Assets/terrain2.bof +) + # Group source files source_group(TREE ${SAMPLES_ROOT} FILES ${SAMPLES_SRC_FILES}) # Create Samples executable if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") - add_executable(Samples MACOSX_BUNDLE ${SAMPLES_SRC_FILES}) + # macOS configuration + add_executable(Samples MACOSX_BUNDLE ${SAMPLES_SRC_FILES} ${TEST_FRAMEWORK_ASSETS} ${SAMPLES_ASSETS}) + + # Make sure that all samples assets move to the Resources folder in the package + foreach(ASSET_FILE ${SAMPLES_ASSETS}) + string(REPLACE ${PHYSICS_REPO_ROOT}/Assets "Resources" ASSET_DST ${ASSET_FILE}) + get_filename_component(ASSET_DST ${ASSET_DST} DIRECTORY) + set_source_files_properties(${ASSET_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION ${ASSET_DST}) + endforeach() + set_property(TARGET Samples PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/iOS/SamplesInfo.plist") set_property(TARGET Samples PROPERTY XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.joltphysics.samples") else() diff --git a/Samples/Tests/Character/CharacterBaseTest.cpp b/Samples/Tests/Character/CharacterBaseTest.cpp index 0ef3114af..93e1f4a81 100644 --- a/Samples/Tests/Character/CharacterBaseTest.cpp +++ b/Samples/Tests/Character/CharacterBaseTest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include JPH_IMPLEMENT_RTTI_ABSTRACT(CharacterBaseTest) @@ -563,7 +564,8 @@ void CharacterBaseTest::Initialize() { // Load scene Ref scene; - if (!ObjectStreamIn::sReadObject((String("Assets/") + sSceneName + ".bof").c_str(), scene)) + AssetStream stream(String(sSceneName) + ".bof", std::ios::in | std::ios::binary); + if (!ObjectStreamIn::sReadObject(stream.Get(), scene)) FatalError("Failed to load scene"); scene->FixInvalidScales(); for (BodyCreationSettings &settings : scene->GetBodies()) diff --git a/Samples/Tests/ConvexCollision/ConvexHullShrinkTest.cpp b/Samples/Tests/ConvexCollision/ConvexHullShrinkTest.cpp index 3ca38c3d8..43ec89876 100644 --- a/Samples/Tests/ConvexCollision/ConvexHullShrinkTest.cpp +++ b/Samples/Tests/ConvexCollision/ConvexHullShrinkTest.cpp @@ -10,6 +10,7 @@ #include #include #include +#include JPH_SUPPRESS_WARNINGS_STD_BEGIN #include @@ -91,29 +92,27 @@ void ConvexHullShrinkTest::Initialize() // Open the external file with hulls // A stream containing predefined convex hulls - ifstream points_stream("Assets/convex_hulls.bin", std::ios::binary); - if (points_stream.is_open()) + AssetStream points_asset_stream("convex_hulls.bin", std::ios::in | std::ios::binary); + std::istream &points_stream = points_asset_stream.Get(); + for (;;) { - for (;;) - { - // Read the length of the next point cloud - uint32 len = 0; - points_stream.read((char *)&len, sizeof(len)); - if (points_stream.eof()) - break; + // Read the length of the next point cloud + uint32 len = 0; + points_stream.read((char *)&len, sizeof(len)); + if (points_stream.eof()) + break; - // Read the points - if (len > 0) + // Read the points + if (len > 0) + { + Points p; + for (uint32 i = 0; i < len; ++i) { - Points p; - for (uint32 i = 0; i < len; ++i) - { - Float3 v; - points_stream.read((char *)&v, sizeof(v)); - p.push_back(Vec3(v)); - } - mPoints.push_back(std::move(p)); + Float3 v; + points_stream.read((char *)&v, sizeof(v)); + p.push_back(Vec3(v)); } + mPoints.push_back(std::move(p)); } } } diff --git a/Samples/Tests/ConvexCollision/ConvexHullTest.cpp b/Samples/Tests/ConvexCollision/ConvexHullTest.cpp index 2e9ed118d..a22379212 100644 --- a/Samples/Tests/ConvexCollision/ConvexHullTest.cpp +++ b/Samples/Tests/ConvexCollision/ConvexHullTest.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include JPH_SUPPRESS_WARNINGS_STD_BEGIN @@ -457,29 +458,27 @@ void ConvexHullTest::Initialize() // Open the external file with hulls // A stream containing predefined convex hulls - ifstream points_stream("Assets/convex_hulls.bin", std::ios::binary); - if (points_stream.is_open()) + AssetStream points_asset_stream("convex_hulls.bin", std::ios::in | std::ios::binary); + std::istream &points_stream = points_asset_stream.Get(); + for (;;) { - for (;;) + // Read the length of the next point cloud + uint32 len = 0; + points_stream.read((char *)&len, sizeof(len)); + if (points_stream.eof()) + break; + + // Read the points + if (len > 0) { - // Read the length of the next point cloud - uint32 len = 0; - points_stream.read((char *)&len, sizeof(len)); - if (points_stream.eof()) - break; - - // Read the points - if (len > 0) + Points p; + for (uint32 i = 0; i < len; ++i) { - Points p; - for (uint32 i = 0; i < len; ++i) - { - Float3 v; - points_stream.read((char *)&v, sizeof(v)); - p.push_back(Vec3(v)); - } - mPoints.push_back(std::move(p)); + Float3 v; + points_stream.read((char *)&v, sizeof(v)); + p.push_back(Vec3(v)); } + mPoints.push_back(std::move(p)); } } } diff --git a/Samples/Tests/General/HighSpeedTest.cpp b/Samples/Tests/General/HighSpeedTest.cpp index f76878880..b20188bee 100644 --- a/Samples/Tests/General/HighSpeedTest.cpp +++ b/Samples/Tests/General/HighSpeedTest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include JPH_IMPLEMENT_RTTI_VIRTUAL(HighSpeedTest) @@ -330,7 +331,8 @@ void HighSpeedTest::CreateConvexOnTerrain1() #ifdef JPH_OBJECT_STREAM // Load scene Ref scene; - if (!ObjectStreamIn::sReadObject("Assets/terrain1.bof", scene)) + AssetStream stream("terrain1.bof", std::ios::in | std::ios::binary); + if (!ObjectStreamIn::sReadObject(stream.Get(), scene)) FatalError("Failed to load scene"); for (BodyCreationSettings &body : scene->GetBodies()) body.mObjectLayer = Layers::NON_MOVING; diff --git a/Samples/Tests/General/MultithreadedTest.cpp b/Samples/Tests/General/MultithreadedTest.cpp index bfa375b10..c66832b45 100644 --- a/Samples/Tests/General/MultithreadedTest.cpp +++ b/Samples/Tests/General/MultithreadedTest.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include JPH_IMPLEMENT_RTTI_VIRTUAL(MultithreadedTest) @@ -137,7 +138,7 @@ void MultithreadedTest::RagdollSpawner() #ifdef JPH_OBJECT_STREAM // Load ragdoll - Ref ragdoll_settings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + Ref ragdoll_settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); if (ragdoll_settings == nullptr) FatalError("Could not load ragdoll"); #else @@ -151,7 +152,8 @@ void MultithreadedTest::RagdollSpawner() { #ifdef JPH_OBJECT_STREAM Ref animation; - if (!ObjectStreamIn::sReadObject("Assets/Human/dead_pose1.tof", animation)) + AssetStream stream("Human/dead_pose1.tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), animation)) FatalError("Could not open animation"); animation->Sample(0.0f, ragdoll_pose); #else diff --git a/Samples/Tests/General/SensorTest.cpp b/Samples/Tests/General/SensorTest.cpp index 3e86c1af5..edf0722d3 100644 --- a/Samples/Tests/General/SensorTest.cpp +++ b/Samples/Tests/General/SensorTest.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -71,7 +72,7 @@ void SensorTest::Initialize() #ifdef JPH_OBJECT_STREAM // Load ragdoll - Ref ragdoll_settings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + Ref ragdoll_settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); if (ragdoll_settings == nullptr) FatalError("Could not load ragdoll"); #else @@ -84,7 +85,8 @@ void SensorTest::Initialize() { #ifdef JPH_OBJECT_STREAM Ref animation; - if (!ObjectStreamIn::sReadObject("Assets/Human/dead_pose1.tof", animation)) + AssetStream stream("Human/dead_pose1.tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), animation)) FatalError("Could not open animation"); animation->Sample(0.0f, ragdoll_pose); #else diff --git a/Samples/Tests/Rig/BigWorldTest.cpp b/Samples/Tests/Rig/BigWorldTest.cpp index 9065096f9..cf668cb94 100644 --- a/Samples/Tests/Rig/BigWorldTest.cpp +++ b/Samples/Tests/Rig/BigWorldTest.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include JPH_IMPLEMENT_RTTI_VIRTUAL(BigWorldTest) @@ -39,11 +40,12 @@ void BigWorldTest::Initialize() RefConst shape = floor.GetShape(); // Load ragdoll - Ref settings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + Ref settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); // Load animation Ref animation; - if (!ObjectStreamIn::sReadObject("Assets/Human/dead_pose1.tof", animation)) + AssetStream stream("Human/dead_pose1.tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), animation)) FatalError("Could not open animation"); SkeletonPose pose; pose.SetSkeleton(settings->GetSkeleton()); diff --git a/Samples/Tests/Rig/KinematicRigTest.cpp b/Samples/Tests/Rig/KinematicRigTest.cpp index 71b33568a..44f278a05 100644 --- a/Samples/Tests/Rig/KinematicRigTest.cpp +++ b/Samples/Tests/Rig/KinematicRigTest.cpp @@ -11,6 +11,7 @@ #include #include #include +#include JPH_IMPLEMENT_RTTI_VIRTUAL(KinematicRigTest) { @@ -50,15 +51,15 @@ void KinematicRigTest::Initialize() } // Load ragdoll - mRagdollSettings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Kinematic); + mRagdollSettings = RagdollLoader::sLoad("Human.tof", EMotionType::Kinematic); // Create ragdoll mRagdoll = mRagdollSettings->CreateRagdoll(0, 0, mPhysicsSystem); mRagdoll->AddToPhysicsSystem(EActivation::Activate); // Load animation - String filename = String("Assets/Human/") + sAnimationName + ".tof"; - if (!ObjectStreamIn::sReadObject(filename.c_str(), mAnimation)) + AssetStream stream(String("Human/") + sAnimationName + ".tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), mAnimation)) FatalError("Could not open animation"); // Initialize pose diff --git a/Samples/Tests/Rig/LoadRigTest.cpp b/Samples/Tests/Rig/LoadRigTest.cpp index c37629f4d..b6d977d32 100644 --- a/Samples/Tests/Rig/LoadRigTest.cpp +++ b/Samples/Tests/Rig/LoadRigTest.cpp @@ -35,7 +35,7 @@ void LoadRigTest::Initialize() CreateFloor(); // Load ragdoll - mRagdollSettings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic, sConstraintType); + mRagdollSettings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic, sConstraintType); // Create ragdoll mRagdoll = mRagdollSettings->CreateRagdoll(0, 0, mPhysicsSystem); diff --git a/Samples/Tests/Rig/LoadSaveBinaryRigTest.cpp b/Samples/Tests/Rig/LoadSaveBinaryRigTest.cpp index 7a796af8d..00835c460 100644 --- a/Samples/Tests/Rig/LoadSaveBinaryRigTest.cpp +++ b/Samples/Tests/Rig/LoadSaveBinaryRigTest.cpp @@ -29,7 +29,7 @@ void LoadSaveBinaryRigTest::Initialize() { // Load ragdoll - Ref settings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + Ref settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); // Add an additional constraint between the left and right arm to test loading/saving of additional constraints const Skeleton *skeleton = settings->GetSkeleton(); diff --git a/Samples/Tests/Rig/LoadSaveRigTest.cpp b/Samples/Tests/Rig/LoadSaveRigTest.cpp index 2c44f5b82..6a2c40247 100644 --- a/Samples/Tests/Rig/LoadSaveRigTest.cpp +++ b/Samples/Tests/Rig/LoadSaveRigTest.cpp @@ -30,7 +30,7 @@ void LoadSaveRigTest::Initialize() { // Load ragdoll - Ref settings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + Ref settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); // Add an additional constraint between the left and right arm to test loading/saving of additional constraints const Skeleton *skeleton = settings->GetSkeleton(); diff --git a/Samples/Tests/Rig/PoweredRigTest.cpp b/Samples/Tests/Rig/PoweredRigTest.cpp index 9cb7caee8..263d3f069 100644 --- a/Samples/Tests/Rig/PoweredRigTest.cpp +++ b/Samples/Tests/Rig/PoweredRigTest.cpp @@ -10,6 +10,7 @@ #include #include #include +#include JPH_IMPLEMENT_RTTI_VIRTUAL(PoweredRigTest) { @@ -40,15 +41,15 @@ void PoweredRigTest::Initialize() CreateFloor(); // Load ragdoll - mRagdollSettings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + mRagdollSettings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); // Create ragdoll mRagdoll = mRagdollSettings->CreateRagdoll(0, 0, mPhysicsSystem); mRagdoll->AddToPhysicsSystem(EActivation::Activate); // Load animation - String filename = String("Assets/Human/") + sAnimationName + ".tof"; - if (!ObjectStreamIn::sReadObject(filename.c_str(), mAnimation)) + AssetStream stream(String("Human/") + sAnimationName + ".tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), mAnimation)) FatalError("Could not open animation"); // Initialize pose diff --git a/Samples/Tests/Rig/RigPileTest.cpp b/Samples/Tests/Rig/RigPileTest.cpp index a3d6e977c..ee398ccca 100644 --- a/Samples/Tests/Rig/RigPileTest.cpp +++ b/Samples/Tests/Rig/RigPileTest.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include JPH_IMPLEMENT_RTTI_VIRTUAL(RigPileTest) @@ -64,7 +65,8 @@ void RigPileTest::Initialize() { // Load scene Ref scene; - if (!ObjectStreamIn::sReadObject((String("Assets/") + sSceneName + ".bof").c_str(), scene)) + AssetStream stream(String(sSceneName) + ".bof", std::ios::in | std::ios::binary); + if (!ObjectStreamIn::sReadObject(stream.Get(), scene)) FatalError("Failed to load scene"); for (BodyCreationSettings &body : scene->GetBodies()) body.mObjectLayer = Layers::NON_MOVING; @@ -73,14 +75,15 @@ void RigPileTest::Initialize() } // Load ragdoll - Ref settings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + Ref settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); // Load animation const int cAnimationCount = 4; Ref animation[cAnimationCount]; for (int i = 0; i < cAnimationCount; ++i) { - if (!ObjectStreamIn::sReadObject(StringFormat("Assets/Human/dead_pose%d.tof", i + 1).c_str(), animation[i])) + AssetStream stream(StringFormat("Human/dead_pose%d.tof", i + 1), std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), animation[i])) FatalError("Could not open animation"); } diff --git a/Samples/Tests/Rig/SkeletonMapperTest.cpp b/Samples/Tests/Rig/SkeletonMapperTest.cpp index b9dcb9556..e88adbfc8 100644 --- a/Samples/Tests/Rig/SkeletonMapperTest.cpp +++ b/Samples/Tests/Rig/SkeletonMapperTest.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include JPH_IMPLEMENT_RTTI_VIRTUAL(SkeletonMapperTest) @@ -28,7 +29,7 @@ void SkeletonMapperTest::Initialize() CreateFloor(); // Load ragdoll - mRagdollSettings = RagdollLoader::sLoad("Assets/Human.tof", EMotionType::Dynamic); + mRagdollSettings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic); // Create ragdoll mRagdoll = mRagdollSettings->CreateRagdoll(0, 0, mPhysicsSystem); @@ -36,23 +37,35 @@ void SkeletonMapperTest::Initialize() // Load neutral animation for ragdoll Ref neutral_ragdoll; - if (!ObjectStreamIn::sReadObject("Assets/Human/neutral.tof", neutral_ragdoll)) - FatalError("Could not open neutral animation"); + { + AssetStream stream("Human/neutral.tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), neutral_ragdoll)) + FatalError("Could not open neutral animation"); + } // Load animation skeleton Ref animation_skeleton; - if (!ObjectStreamIn::sReadObject("Assets/Human/skeleton_hd.tof", animation_skeleton)) - FatalError("Could not open skeleton_hd"); - animation_skeleton->CalculateParentJointIndices(); + { + AssetStream stream("Human/skeleton_hd.tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), animation_skeleton)) + FatalError("Could not open skeleton_hd"); + animation_skeleton->CalculateParentJointIndices(); + } // Load neutral animation Ref neutral_animation; - if (!ObjectStreamIn::sReadObject("Assets/Human/neutral_hd.tof", neutral_animation)) - FatalError("Could not open neutral_hd animation"); + { + AssetStream stream("Human/neutral_hd.tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), neutral_animation)) + FatalError("Could not open neutral_hd animation"); + } // Load test animation - if (!ObjectStreamIn::sReadObject("Assets/Human/jog_hd.tof", mAnimation)) - FatalError("Could not open jog_hd animation"); + { + AssetStream stream("Human/jog_hd.tof", std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), mAnimation)) + FatalError("Could not open jog_hd animation"); + } // Initialize pose mAnimatedPose.SetSkeleton(animation_skeleton); diff --git a/Samples/Tests/Shapes/HeightFieldShapeTest.cpp b/Samples/Tests/Shapes/HeightFieldShapeTest.cpp index 306e08fde..327870934 100644 --- a/Samples/Tests/Shapes/HeightFieldShapeTest.cpp +++ b/Samples/Tests/Shapes/HeightFieldShapeTest.cpp @@ -90,7 +90,7 @@ void HeightFieldShapeTest::Initialize() const float cell_size = 0.5f; // Get height samples - Array data = ReadData("Assets/heightfield1.bin"); + Array data = ReadData("heightfield1.bin"); if (data.size() != sizeof(float) * n * n) FatalError("Invalid file size"); mTerrainSize = n; diff --git a/Samples/Tests/Vehicle/VehicleTest.cpp b/Samples/Tests/Vehicle/VehicleTest.cpp index 4f74aec12..2b632b602 100644 --- a/Samples/Tests/Vehicle/VehicleTest.cpp +++ b/Samples/Tests/Vehicle/VehicleTest.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include JPH_IMPLEMENT_RTTI_VIRTUAL(VehicleTest) @@ -49,7 +50,7 @@ void VehicleTest::Initialize() mBodyInterface->AddBody(floor.GetID(), EActivation::DontActivate); // Load a race track to have something to assess speed and steering behavior - LoadRaceTrack("Assets/Racetracks/Zandvoort.csv"); + LoadRaceTrack("Racetracks/Zandvoort.csv"); } else if (strcmp(sSceneName, "Flat With Slope") == 0) { @@ -164,7 +165,8 @@ void VehicleTest::Initialize() { // Load scene Ref scene; - if (!ObjectStreamIn::sReadObject((String("Assets/") + sSceneName + ".bof").c_str(), scene)) + AssetStream stream(String(sSceneName) + ".bof", std::ios::in | std::ios::binary); + if (!ObjectStreamIn::sReadObject(stream.Get(), scene)) FatalError("Failed to load scene"); for (BodyCreationSettings &body : scene->GetBodies()) body.mObjectLayer = Layers::NON_MOVING; @@ -262,10 +264,8 @@ void VehicleTest::CreateRubble() void VehicleTest::LoadRaceTrack(const char *inFileName) { // Open the track file - std::ifstream stream; - stream.open(inFileName, std::ifstream::in); - if (!stream.is_open()) - return; + AssetStream asset_stream(inFileName, std::ios::in); + std::istream &stream = asset_stream.Get(); // Ignore header line String line; diff --git a/Samples/Utils/RagdollLoader.cpp b/Samples/Utils/RagdollLoader.cpp index 7e9536226..d152b067a 100644 --- a/Samples/Utils/RagdollLoader.cpp +++ b/Samples/Utils/RagdollLoader.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #ifdef JPH_OBJECT_STREAM @@ -24,7 +25,8 @@ RagdollSettings *RagdollLoader::sLoad(const char *inFileName, EMotionType inMoti { // Read the ragdoll RagdollSettings *ragdoll = nullptr; - if (!ObjectStreamIn::sReadObject(inFileName, ragdoll)) + AssetStream stream(inFileName, std::ios::in); + if (!ObjectStreamIn::sReadObject(stream.Get(), ragdoll)) FatalError("Unable to read ragdoll"); for (RagdollSettings::Part &p : ragdoll->mParts) diff --git a/TestFramework/Application/DebugUI.cpp b/TestFramework/Application/DebugUI.cpp index 577c8d27e..078954b5c 100644 --- a/TestFramework/Application/DebugUI.cpp +++ b/TestFramework/Application/DebugUI.cpp @@ -15,6 +15,7 @@ #include #include #include +#include JPH_SUPPRESS_WARNINGS_STD_BEGIN #include @@ -25,11 +26,8 @@ DebugUI::DebugUI(UIManager *inUIManager, const Font *inFont) : mFont(inFont) { // Load UI texture with commonly used UI elements - ifstream texture_stream; - texture_stream.open("Assets/UI.tga", ifstream::binary); - if (texture_stream.fail()) - FatalError("Failed to open UI.tga"); - Ref texture_surface = LoadTGA(texture_stream); + AssetStream texture_stream("UI.tga", std::ios::in | std::ios::binary); + Ref texture_surface = LoadTGA(texture_stream.Get()); if (texture_surface == nullptr) FatalError("Failed to load UI.tga"); mUITexture = mUI->GetRenderer()->CreateTexture(texture_surface); diff --git a/TestFramework/Renderer/DX12/RendererDX12.cpp b/TestFramework/Renderer/DX12/RendererDX12.cpp index e1f4578b7..e3c7a208a 100644 --- a/TestFramework/Renderer/DX12/RendererDX12.cpp +++ b/TestFramework/Renderer/DX12/RendererDX12.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #ifdef JPH_DEBUG @@ -525,14 +526,14 @@ Ref RendererDX12::CreateVertexShader(const char *inName) }; // Read shader source file - String file_name = String("Assets/Shaders/DX/") + inName + ".hlsl"; + String file_name = String("Shaders/DX/") + inName + ".hlsl"; Array data = ReadData(file_name.c_str()); // Compile source ComPtr shader_blob, error_blob; HRESULT hr = D3DCompile(&data[0], (uint)data.size(), - file_name.c_str(), + (AssetStream::sGetAssetsBasePath() + file_name).c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", @@ -565,14 +566,14 @@ Ref RendererDX12::CreatePixelShader(const char *inName) }; // Read shader source file - String file_name = String("Assets/Shaders/DX/") + inName + ".hlsl"; + String file_name = String("Shaders/DX/") + inName + ".hlsl"; Array data = ReadData(file_name.c_str()); // Compile source ComPtr shader_blob, error_blob; HRESULT hr = D3DCompile(&data[0], (uint)data.size(), - file_name.c_str(), + (AssetStream::sGetAssetsBasePath() + file_name).c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", diff --git a/TestFramework/Renderer/Font.cpp b/TestFramework/Renderer/Font.cpp index 54d465373..a6ab58027 100644 --- a/TestFramework/Renderer/Font.cpp +++ b/TestFramework/Renderer/Font.cpp @@ -41,7 +41,7 @@ bool Font::Create(const char *inFontName, int inCharHeight) constexpr int cSpacingV = 2; // Number of pixels to put vertically between characters // Read font data - Array font_data = ReadData((String("Assets/Fonts/") + inFontName + ".ttf").c_str()); + Array font_data = ReadData((String("Fonts/") + inFontName + ".ttf").c_str()); // Construct a font info stbtt_fontinfo font; diff --git a/TestFramework/Renderer/MTL/RendererMTL.mm b/TestFramework/Renderer/MTL/RendererMTL.mm index 1685e3459..b01136ef3 100644 --- a/TestFramework/Renderer/MTL/RendererMTL.mm +++ b/TestFramework/Renderer/MTL/RendererMTL.mm @@ -14,6 +14,7 @@ #include #include #include +#include #include RendererMTL::~RendererMTL() @@ -33,7 +34,7 @@ // Load the shader library containing all shaders for the test framework NSError *error = nullptr; - NSURL *url = [NSURL URLWithString: @"Assets/Shaders/MTL/Shaders.metallib"]; + NSURL *url = [NSURL URLWithString: [NSString stringWithCString: (AssetStream::sGetAssetsBasePath() + "Shaders/MTL/Shaders.metallib").c_str() encoding: NSUTF8StringEncoding]]; mShaderLibrary = [device newLibraryWithURL: url error: &error]; FatalErrorIfFailed(error); @@ -144,7 +145,7 @@ Ref RendererMTL::CreateVertexShader(const char *inName) { - id function = [mShaderLibrary newFunctionWithName: [[[NSString alloc] initWithUTF8String: inName] autorelease]]; + id function = [mShaderLibrary newFunctionWithName: [NSString stringWithCString: inName encoding: NSUTF8StringEncoding]]; if (function == nil) FatalError("Vertex shader %s not found", inName); return new VertexShaderMTL(function); @@ -152,7 +153,7 @@ Ref RendererMTL::CreatePixelShader(const char *inName) { - id function = [mShaderLibrary newFunctionWithName: [[[NSString alloc] initWithUTF8String: inName] autorelease]]; + id function = [mShaderLibrary newFunctionWithName: [NSString stringWithCString: inName encoding: NSUTF8StringEncoding]]; if (function == nil) FatalError("Pixel shader %s not found", inName); return new PixelShaderMTL(function); diff --git a/TestFramework/Renderer/VK/RendererVK.cpp b/TestFramework/Renderer/VK/RendererVK.cpp index 1ce83f419..c997c2c2c 100644 --- a/TestFramework/Renderer/VK/RendererVK.cpp +++ b/TestFramework/Renderer/VK/RendererVK.cpp @@ -967,7 +967,7 @@ Ref RendererVK::CreateTexture(const Surface *inSurface) Ref RendererVK::CreateVertexShader(const char *inName) { - Array data = ReadData((String("Assets/Shaders/VK/") + inName + ".vert.spv").c_str()); + Array data = ReadData((String("Shaders/VK/") + inName + ".vert.spv").c_str()); VkShaderModuleCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; @@ -981,7 +981,7 @@ Ref RendererVK::CreateVertexShader(const char *inName) Ref RendererVK::CreatePixelShader(const char *inName) { - Array data = ReadData((String("Assets/Shaders/VK/") + inName + ".frag.spv").c_str()); + Array data = ReadData((String("Shaders/VK/") + inName + ".frag.spv").c_str()); VkShaderModuleCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; diff --git a/TestFramework/TestFramework.cmake b/TestFramework/TestFramework.cmake index de45a15df..bf3eb0a83 100644 --- a/TestFramework/TestFramework.cmake +++ b/TestFramework/TestFramework.cmake @@ -75,7 +75,7 @@ if (NOT CROSS_COMPILE_ARM AND (Vulkan_FOUND OR WIN32 OR ("${CMAKE_SYSTEM_NAME}" ${TEST_FRAMEWORK_ROOT}/UI/UIVerticalStack.h ${TEST_FRAMEWORK_ROOT}/Utils/CustomMemoryHook.cpp ${TEST_FRAMEWORK_ROOT}/Utils/CustomMemoryHook.h - ${TEST_FRAMEWORK_ROOT}/Utils/Log.cpp + ${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.h ${TEST_FRAMEWORK_ROOT}/Utils/Log.h ${TEST_FRAMEWORK_ROOT}/Utils/ReadData.cpp ${TEST_FRAMEWORK_ROOT}/Utils/ReadData.h @@ -108,6 +108,8 @@ if (NOT CROSS_COMPILE_ARM AND (Vulkan_FOUND OR WIN32 OR ("${CMAKE_SYSTEM_NAME}" ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.cpp ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/TextureDX12.h ${TEST_FRAMEWORK_ROOT}/Renderer/DX12/VertexShaderDX12.h + ${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.cpp + ${TEST_FRAMEWORK_ROOT}/Utils/Log.cpp ${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowWin.cpp ${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowWin.h ) @@ -147,6 +149,8 @@ if (NOT CROSS_COMPILE_ARM AND (Vulkan_FOUND OR WIN32 OR ("${CMAKE_SYSTEM_NAME}" ${TEST_FRAMEWORK_ROOT}/Input/Linux/KeyboardLinux.h ${TEST_FRAMEWORK_ROOT}/Input/Linux/MouseLinux.cpp ${TEST_FRAMEWORK_ROOT}/Input/Linux/MouseLinux.h + ${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.cpp + ${TEST_FRAMEWORK_ROOT}/Utils/Log.cpp ${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowLinux.cpp ${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowLinux.h ) @@ -174,6 +178,8 @@ if (NOT CROSS_COMPILE_ARM AND (Vulkan_FOUND OR WIN32 OR ("${CMAKE_SYSTEM_NAME}" ${TEST_FRAMEWORK_ROOT}/Input/MacOS/KeyboardMacOS.h ${TEST_FRAMEWORK_ROOT}/Input/MacOS/MouseMacOS.mm ${TEST_FRAMEWORK_ROOT}/Input/MacOS/MouseMacOS.h + ${TEST_FRAMEWORK_ROOT}/Utils/AssetStream.mm + ${TEST_FRAMEWORK_ROOT}/Utils/Log.mm ${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowMacOS.mm ${TEST_FRAMEWORK_ROOT}/Window/ApplicationWindowMacOS.h ) @@ -262,6 +268,17 @@ if (NOT CROSS_COMPILE_ARM AND (Vulkan_FOUND OR WIN32 OR ("${CMAKE_SYSTEM_NAME}" endforeach() endif() + # Assets used by the test framework + set(TEST_FRAMEWORK_ASSETS + ${PHYSICS_REPO_ROOT}/Assets/Fonts/Roboto-Regular.ttf + ${PHYSICS_REPO_ROOT}/Assets/UI.tga + ${TEST_FRAMEWORK_SRC_FILES_SHADERS} + ${TEST_FRAMEWORK_HLSL_VERTEX_SHADERS} + ${TEST_FRAMEWORK_HLSL_PIXEL_SHADERS} + ${TEST_FRAMEWORK_SPV_SHADERS} + ${TEST_FRAMEWORK_METAL_LIB} + ) + # Group source files source_group(TREE ${TEST_FRAMEWORK_ROOT} FILES ${TEST_FRAMEWORK_SRC_FILES}) @@ -296,6 +313,13 @@ if (NOT CROSS_COMPILE_ARM AND (Vulkan_FOUND OR WIN32 OR ("${CMAKE_SYSTEM_NAME}" # macOS configuration target_link_libraries(TestFramework LINK_PUBLIC Jolt "-framework Cocoa -framework Metal -framework MetalKit -framework GameController") + # Make sure that all test framework assets move to the Resources folder in the package + foreach(ASSET_FILE ${TEST_FRAMEWORK_ASSETS}) + string(REPLACE ${PHYSICS_REPO_ROOT}/Assets "Resources" ASSET_DST ${ASSET_FILE}) + get_filename_component(ASSET_DST ${ASSET_DST} DIRECTORY) + set_source_files_properties(${ASSET_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION ${ASSET_DST}) + endforeach() + # Ignore PCH files for .mm files foreach(SRC_FILE ${TEST_FRAMEWORK_SRC_FILES}) if (SRC_FILE MATCHES "\.mm") diff --git a/TestFramework/Utils/AssetStream.cpp b/TestFramework/Utils/AssetStream.cpp new file mode 100644 index 000000000..d1247e197 --- /dev/null +++ b/TestFramework/Utils/AssetStream.cpp @@ -0,0 +1,19 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2025 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include +#include +#include + +String AssetStream::sGetAssetsBasePath() +{ + return "Assets/"; +} + +AssetStream::AssetStream(const char *inFileName, std::ios_base::openmode inOpenMode) : + mStream((sGetAssetsBasePath() + inFileName).c_str(), inOpenMode) +{ + if (!mStream.is_open()) + FatalError("Failed to open file %s", inFileName); +} diff --git a/TestFramework/Utils/AssetStream.h b/TestFramework/Utils/AssetStream.h new file mode 100644 index 000000000..4f0268f7f --- /dev/null +++ b/TestFramework/Utils/AssetStream.h @@ -0,0 +1,27 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2025 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +JPH_SUPPRESS_WARNINGS_STD_BEGIN +#include +JPH_SUPPRESS_WARNINGS_STD_END + +/// An istream interface that reads data from a file in the Assets folder +class AssetStream +{ +public: + /// Constructor + AssetStream(const char *inFileName, std::ios_base::openmode inOpenMode); + AssetStream(const String &inFileName, std::ios_base::openmode inOpenMode) : AssetStream(inFileName.c_str(), inOpenMode) { } + + /// Get the path to the assets folder + static String sGetAssetsBasePath(); + + /// Get the stream + std::istream & Get() { return mStream; } + +private: + std::ifstream mStream; +}; diff --git a/TestFramework/Utils/AssetStream.mm b/TestFramework/Utils/AssetStream.mm new file mode 100644 index 000000000..845d7c056 --- /dev/null +++ b/TestFramework/Utils/AssetStream.mm @@ -0,0 +1,24 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2025 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include + +String AssetStream::sGetAssetsBasePath() +{ + NSBundle *bundle = [NSBundle mainBundle]; + String path = [[[bundle resourceURL] path] cStringUsingEncoding: NSUTF8StringEncoding]; + path += "/"; + return path; +} + +AssetStream::AssetStream(const char *inFileName, std::ios_base::openmode inOpenMode) : + mStream((sGetAssetsBasePath() + inFileName).c_str(), inOpenMode) +{ + if (!mStream.is_open()) + FatalError("Failed to open file %s", inFileName); +} diff --git a/TestFramework/Utils/Log.mm b/TestFramework/Utils/Log.mm new file mode 100644 index 000000000..44b3970ff --- /dev/null +++ b/TestFramework/Utils/Log.mm @@ -0,0 +1,57 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2025 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include + +// Trace to TTY +void TraceImpl(const char *inFMT, ...) +{ + // Format the message + va_list list; + va_start(list, inFMT); + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), inFMT, list); + va_end(list); + + // Log to the console + printf("%s\n", buffer); +} + +void Alert(const char *inFMT, ...) +{ + // Format the message + va_list list; + va_start(list, inFMT); + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), inFMT, list); + va_end(list); + + Trace("Alert: %s", buffer); + + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + alert.messageText = [NSString stringWithCString: buffer encoding: NSUTF8StringEncoding]; + [alert runModal]; +} + +void FatalError [[noreturn]] (const char *inFMT, ...) +{ + // Format the message + va_list list; + va_start(list, inFMT); + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), inFMT, list); + va_end(list); + + Trace("Fatal Error: %s", buffer); + + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + alert.messageText = [NSString stringWithCString: buffer encoding: NSUTF8StringEncoding]; + [alert runModal]; + + exit(1); +} diff --git a/TestFramework/Utils/ReadData.cpp b/TestFramework/Utils/ReadData.cpp index fa3ba1100..1743d6b3a 100644 --- a/TestFramework/Utils/ReadData.cpp +++ b/TestFramework/Utils/ReadData.cpp @@ -5,6 +5,7 @@ #include #include #include +#include JPH_SUPPRESS_WARNINGS_STD_BEGIN #include @@ -14,9 +15,8 @@ JPH_SUPPRESS_WARNINGS_STD_END Array ReadData(const char *inFileName) { Array data; - ifstream input(inFileName, std::ios::binary); - if (!input) - FatalError("Unable to open file: %s", inFileName); + AssetStream asset_stream(inFileName, std::ios::in | std::ios::binary); + std::istream &input = asset_stream.Get(); input.seekg(0, ios_base::end); ifstream::pos_type length = input.tellg(); input.seekg(0, ios_base::beg); diff --git a/TestFramework/Window/ApplicationWindowMacOS.h b/TestFramework/Window/ApplicationWindowMacOS.h index e71525211..6f7e02e7e 100644 --- a/TestFramework/Window/ApplicationWindowMacOS.h +++ b/TestFramework/Window/ApplicationWindowMacOS.h @@ -32,8 +32,8 @@ class ApplicationWindowMacOS : public ApplicationWindow virtual void MainLoop(RenderCallback inRenderCallback) override; /// Call the render callback - bool RenderCallback() { return mRenderCallback(); } - + bool RenderCallback() { return mRenderCallback && mRenderCallback(); } + /// Subscribe to mouse move callbacks that supply window coordinates using MouseMovedCallback = function; void SetMouseMovedCallback(MouseMovedCallback inCallback) { mMouseMovedCallback = inCallback; }