Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions dynamicresourcefilelocation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Dynamic Resource File Location Fix - ReactPhysics3D Issue #416

## Problem Description

The ReactPhysics3D testbed application used hardcoded relative paths for resource files (shaders and .obj meshes), which caused issues when different compilers or IDEs placed the executable in different directories relative to the source tree. This particularly affected developers on Windows with Visual Studio Code and other build environments.

### Original Issues
- Hardcoded paths like `"shaders/depth.vert"`, `"meshes/castle.obj"` in multiple files
- Application would crash with "Cannot open file" errors when run from different build directories
- Different build systems (CMake, Visual Studio, etc.) place executables in varying directory structures

## Solution Overview

Implemented a comprehensive `ResourceManager` class that provides dynamic resource path discovery while maintaining full backwards compatibility.

## Implementation Details

### Core Components

#### 1. ResourceManager Class (`testbed/common/ResourceManager.h` & `.cpp`)

**Key Features:**
- **Dynamic Discovery**: Searches up to 4 parent directories from executable location
- **Multiple Search Patterns**: Looks for both `testbed/shaders` + `testbed/meshes` and direct `shaders` + `meshes`
- **Path Caching**: Caches discovered paths for performance optimization
- **Environment Override**: Supports `RP3D_RESOURCE_PATH` environment variable
- **Backwards Compatibility**: Falls back to relative paths if discovery fails
- **Cross-Platform**: Uses `std::filesystem` for proper path handling

**Public API:**
```cpp
static std::string getShaderPath(const std::string& shaderFilename);
static std::string getMeshPath(const std::string& meshFilename);
static std::string getMeshDirectoryPath();
static bool fileExists(const std::string& filepath);
static void setResourceBaseDirectory(const std::string& baseDir);
```

#### 2. Search Algorithm

1. Check for `RP3D_RESOURCE_PATH` environment variable
2. Start from current working directory
3. For each directory level (up to MAX_SEARCH_DEPTH=4):
- Look for `testbed/shaders` and `testbed/meshes`
- Look for direct `shaders` and `meshes` directories
- Move up one parent directory if not found
4. Fallback to current directory with warning if nothing found

### Files Modified

#### Core Framework Files
- **`testbed/src/SceneDemo.cpp`**
- Updated shader paths: `mDepthShader`, `mPhongShader`, `mColorShader`, `mQuadShader`
- Updated mesh folder path: `mMeshFolderPath`
- Added ResourceManager include

#### Common Object Files
- **`testbed/common/Dumbbell.cpp`** - Updated dumbbell.obj path
- **`testbed/common/Box.cpp`** - Updated cube.obj path
- **`testbed/common/Capsule.cpp`** - Updated capsule.obj path
- **`testbed/common/Sphere.cpp`** - Updated sphere.obj path
- **`testbed/common/VisualContactPoint.cpp`** - Updated sphere.obj path

#### Scene Files
- **`testbed/scenes/concavemesh/ConcaveMeshScene.cpp`** - Updated castle.obj and convexmesh.obj paths
- **`testbed/scenes/collisiondetection/CollisionDetectionScene.cpp`** - Updated castle.obj and convexmesh.obj paths
- **`testbed/scenes/raycast/RaycastScene.cpp`** - Updated castle.obj and convexmesh.obj paths
- **`testbed/scenes/pile/PileScene.cpp`** - Updated convexmesh.obj path
- **`testbed/scenes/heightfield/HeightFieldScene.cpp`** - Updated convexmesh.obj path
- **`testbed/scenes/collisionshapes/CollisionShapesScene.cpp`** - Updated convexmesh.obj path

#### Build System
- **`testbed/CMakeLists.txt`** - Added ResourceManager.h and ResourceManager.cpp to COMMON_SOURCES

## Usage Examples

### Basic Usage
```cpp
// Old hardcoded approach
std::string shaderPath = "shaders/phong.vert";
std::string meshPath = meshFolderPath + "castle.obj";

// New dynamic approach
std::string shaderPath = ResourceManager::getShaderPath("phong.vert");
std::string meshPath = ResourceManager::getMeshPath("castle.obj");
```

### Environment Variable Override
```bash
# Set custom resource path
export RP3D_RESOURCE_PATH="/custom/path/to/resources"
./testbed
```

### Manual Override (for testing)
```cpp
ResourceManager::setResourceBaseDirectory("/custom/path");
```

## Benefits

1. **Cross-Platform Compatibility**: Works across different operating systems and build environments
2. **Build System Agnostic**: Compatible with CMake, Visual Studio, Code::Blocks, etc.
3. **Developer Friendly**: No manual path configuration required
4. **Backwards Compatible**: Existing setups continue to work unchanged
5. **Flexible**: Supports custom paths via environment variables
6. **Performance Optimized**: Caches discovered paths to avoid repeated filesystem searches
7. **Robust Error Handling**: Graceful fallback with informative warnings

## Testing Results

The implementation was successfully tested by:
1. Building the testbed application with CMake
2. Running from the build directory
3. Verifying dynamic resource discovery: "ResourceManager: Found resources at: /path/to/build/testbed"
4. Confirming all mesh and shader files load successfully
5. Application completed without crashes (exit code 0)

## Migration Notes

For developers extending the testbed:
- Replace hardcoded `"meshes/filename.obj"` with `ResourceManager::getMeshPath("filename.obj")`
- Replace hardcoded `"shaders/filename.ext"` with `ResourceManager::getShaderPath("filename.ext")`
- Replace hardcoded `"meshes/"` directory with `ResourceManager::getMeshDirectoryPath()`

## Future Enhancements

Potential improvements for future versions:
- Support for additional resource types (textures, sounds, etc.)
- Configuration file support
- Resource validation and integrity checking
- Resource hot-reloading for development

---

**Issue Reference**: ReactPhysics3D Issue #416 - Dynamic Resource File Location
**Implementation Date**: January 2025
**Status**: ✅ Complete and Tested
2 changes: 2 additions & 0 deletions testbed/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ set(COMMON_SOURCES
common/PerlinNoise.cpp
common/AABB.h
common/AABB.cpp
common/ResourceManager.h
common/ResourceManager.cpp
)

# Examples scenes source files
Expand Down
3 changes: 2 additions & 1 deletion testbed/common/Box.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

// Libraries
#include "Box.h"
#include "ResourceManager.h"

// Macros
#define MEMBER_OFFSET(s,m) ((char *)nullptr + (offsetof(s,m)))
Expand All @@ -40,7 +41,7 @@ int Box::totalNbBoxes = 0;
// Constructor
Box::Box(reactphysics3d::BodyType type, bool isSimulationCollider, const openglframework::Vector3& size, reactphysics3d::PhysicsCommon& physicsCommon, reactphysics3d::PhysicsWorld* world,
const std::string& meshFolderPath)
: PhysicsObject(physicsCommon, meshFolderPath + "cube.obj"), mPhysicsWorld(world) {
: PhysicsObject(physicsCommon, ResourceManager::getMeshPath("cube.obj")), mPhysicsWorld(world) {

// Initialize the size of the box
mSize[0] = size.x * 0.5f;
Expand Down
3 changes: 2 additions & 1 deletion testbed/common/Capsule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

// Libraries
#include "Capsule.h"
#include "ResourceManager.h"

openglframework::VertexBufferObject Capsule::mVBOVertices(GL_ARRAY_BUFFER);
openglframework::VertexBufferObject Capsule::mVBONormals(GL_ARRAY_BUFFER);
Expand All @@ -36,7 +37,7 @@ int Capsule::totalNbCapsules = 0;
// Constructor
Capsule::Capsule(reactphysics3d::BodyType type, bool isSimulationCollider, float radius, float height, reactphysics3d::PhysicsCommon& physicsCommon, rp3d::PhysicsWorld* physicsWorld,
const std::string& meshFolderPath)
: PhysicsObject(physicsCommon, meshFolderPath + "capsule.obj"), mRadius(radius), mHeight(height), mPhysicsWorld(physicsWorld) {
: PhysicsObject(physicsCommon, ResourceManager::getMeshPath("capsule.obj")), mRadius(radius), mHeight(height), mPhysicsWorld(physicsWorld) {

// Compute the scaling matrix
mScalingMatrix = openglframework::Matrix4(mRadius, 0, 0, 0,
Expand Down
3 changes: 2 additions & 1 deletion testbed/common/Dumbbell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

// Libraries
#include "Dumbbell.h"
#include "ResourceManager.h"

openglframework::VertexBufferObject Dumbbell::mVBOVertices(GL_ARRAY_BUFFER);
openglframework::VertexBufferObject Dumbbell::mVBONormals(GL_ARRAY_BUFFER);
Expand All @@ -35,7 +36,7 @@ int Dumbbell::totalNbDumbbells = 0;

// Constructor
Dumbbell::Dumbbell(reactphysics3d::BodyType type, bool isSimulationCollider, rp3d::PhysicsCommon& physicsCommon, rp3d::PhysicsWorld* physicsWorld, const std::string& meshFolderPath)
: PhysicsObject(physicsCommon, meshFolderPath + "dumbbell.obj"), mPhysicsWorld(physicsWorld) {
: PhysicsObject(physicsCommon, ResourceManager::getMeshPath("dumbbell.obj")), mPhysicsWorld(physicsWorld) {

// Identity scaling matrix
mScalingMatrix.setToIdentity();
Expand Down
141 changes: 141 additions & 0 deletions testbed/common/ResourceManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/********************************************************************************
* ReactPhysics3D physics library, http://www.reactphysics3d.com *
* Copyright (c) 2010-2016 Daniel Chappuis *
********************************************************************************/

// Libraries
#include "ResourceManager.h"
#include <iostream>
#include <cstdlib>

// Static member initialization
std::unordered_map<std::string, std::string> ResourceManager::mResourcePaths;
std::string ResourceManager::mResourceBaseDir;
bool ResourceManager::mIsInitialized = false;

// Initialize resource paths
void ResourceManager::initialize() {

if (mIsInitialized) return;

// Check for environment variable override first
const char* envPath = std::getenv("RP3D_RESOURCE_PATH");
if (envPath != nullptr) {
mResourceBaseDir = std::string(envPath);
std::cout << "ResourceManager: Using environment path: " << mResourceBaseDir << std::endl;
} else {
// Dynamic discovery starting from executable directory
std::filesystem::path currentPath = std::filesystem::current_path();

// Search for testbed directory structure
bool found = false;
std::filesystem::path searchPath = currentPath;

for (int depth = 0; depth <= MAX_SEARCH_DEPTH && !found; ++depth) {

// Look for testbed/shaders and testbed/meshes directories
std::filesystem::path testbedPath = searchPath / "testbed";
std::filesystem::path shadersPath = testbedPath / "shaders";
std::filesystem::path meshesPath = testbedPath / "meshes";

if (std::filesystem::exists(shadersPath) && std::filesystem::exists(meshesPath)) {
mResourceBaseDir = testbedPath.string();
found = true;
std::cout << "ResourceManager: Found resources at: " << mResourceBaseDir << std::endl;
} else {
// Try direct shaders/meshes in current search path
shadersPath = searchPath / "shaders";
meshesPath = searchPath / "meshes";

if (std::filesystem::exists(shadersPath) && std::filesystem::exists(meshesPath)) {
mResourceBaseDir = searchPath.string();
found = true;
std::cout << "ResourceManager: Found resources at: " << mResourceBaseDir << std::endl;
}
}

// Move up one directory level
if (!found && searchPath.has_parent_path()) {
searchPath = searchPath.parent_path();
}
}

if (!found) {
// Fallback to current directory (maintains backwards compatibility)
mResourceBaseDir = currentPath.string();
std::cout << "ResourceManager: Warning - Could not locate resource directories. "
<< "Using current directory: " << mResourceBaseDir << std::endl;
std::cout << "ResourceManager: You can set RP3D_RESOURCE_PATH environment variable "
<< "to specify the resource directory manually." << std::endl;
}
}

// Cache common directory paths
std::filesystem::path basePath(mResourceBaseDir);
mResourcePaths["shaders"] = (basePath / "shaders").string();
mResourcePaths["meshes"] = (basePath / "meshes").string();

mIsInitialized = true;
}

// Get shader file path
std::string ResourceManager::getShaderPath(const std::string& shaderFilename) {
initialize();

std::filesystem::path shaderPath = std::filesystem::path(mResourcePaths["shaders"]) / shaderFilename;
std::string fullPath = shaderPath.string();

// Check if file exists, fallback to relative path for backwards compatibility
if (!std::filesystem::exists(fullPath)) {
std::cout << "ResourceManager: Warning - Shader not found at: " << fullPath
<< ". Falling back to relative path." << std::endl;
return "shaders/" + shaderFilename;
}

return fullPath;
}

// Get mesh file path
std::string ResourceManager::getMeshPath(const std::string& meshFilename) {
initialize();

std::filesystem::path meshPath = std::filesystem::path(mResourcePaths["meshes"]) / meshFilename;
std::string fullPath = meshPath.string();

// Check if file exists, fallback to relative path for backwards compatibility
if (!std::filesystem::exists(fullPath)) {
std::cout << "ResourceManager: Warning - Mesh not found at: " << fullPath
<< ". Falling back to relative path." << std::endl;
return "meshes/" + meshFilename;
}

return fullPath;
}

// Get mesh directory path
std::string ResourceManager::getMeshDirectoryPath() {
initialize();

std::string meshDir = mResourcePaths["meshes"] + "/";

// Check if directory exists, fallback for backwards compatibility
if (!std::filesystem::exists(mResourcePaths["meshes"])) {
std::cout << "ResourceManager: Warning - Mesh directory not found at: " << mResourcePaths["meshes"]
<< ". Falling back to relative path." << std::endl;
return "meshes/";
}

return meshDir;
}

// Check if file exists
bool ResourceManager::fileExists(const std::string& filepath) {
return std::filesystem::exists(filepath);
}

// Set resource base directory manually
void ResourceManager::setResourceBaseDirectory(const std::string& baseDir) {
mResourceBaseDir = baseDir;
mIsInitialized = false; // Force re-initialization
initialize();
}
Loading