My method to load .obj, .mtl and image from local hard disk overriding CORS restriction: just ask the user to select all the needed files, then process them accordingly to create proper filecontents in memory and load into babylon scene. (Note: AI helped a lot in this!)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Carica un OBJ in Babylon.js</title>
<script src="babylon.js"></script>
<script src="babylonjs.loaders.min.js"></script>
</head>
<body>
<input type="file" id="fileInput" multiple accept=".obj,.mtl,.png,.jpg.jpeg,.gif,.bmp,.webp" />
<canvas id="renderCanvas" style="width: 100%; height: 100%;"></canvas>
<script>
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
// Crea una scena di base
const createScene = () => {
const scene = new BABYLON.Scene(engine);
scene.useRightHandedSystem = true; // Fix babylon reference frame
const axes = new BABYLON.AxesViewer(scene, 2); // Add cartesian axes
// Camera
const camera = new BABYLON.ArcRotateCamera("camera1", 0, 0, 0, new BABYLON.Vector3(0, 0, -0), scene);
camera.setPosition(new BABYLON.Vector3(0, 0, -10));
camera.attachControl(canvas, true);
camera.wheelDeltaPercentage = 0.01;
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
const light2 = new BABYLON.PointLight("light", new BABYLON.Vector3(0, 1, 0), scene);
scene.addLight(light);
scene.addLight(light2);
return scene;
};
const scene = createScene();
////////////////////////////
const readFileAsText = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsText(file);
});
};
// Read a file and convert into base64 format:
const fileToDataURL = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
};
///////////////////////////////
// Process loaded files
const processFiles = async (files) => {
// Receive a "files" array from fileInput control in HTML page and process them
// It's mandatory to provide 3 files: .obj, .mtl and an image file
fileMap = {};
for (let file of files) {
fileMap[file.name] = file;
}
// Find files .obj, .mtl e .png
objFile = Object.keys(fileMap).find(name => name.endsWith(".obj"));
mtlFile = Object.keys(fileMap).find(name => name.endsWith(".mtl"));
const supportedExtensions = [".png", ".gif", ".bmp", ".jpeg", ".jpg", ".webp"];
textureFile = Object.keys(fileMap).find(name =>
supportedExtensions.some(ext => name.toLowerCase().endsWith(ext))
);
//textureFile = Object.keys(fileMap).find(name => name.endsWith(".png"));
if (!objFile || !mtlFile || !textureFile) {
alert("Please load a file .obj, a file .mtl and a texture (.png).");
return;
}
let objContent = await readFileAsText(fileMap[objFile]);
objContent = ZdownToZin(objContent);
let mtlContent = await readFileAsText(fileMap[mtlFile]);
const textureBase64 = await fileToDataURL(fileMap[textureFile]);
// Replace image filename in .mtl file (after "map_Kd") by BASE64 coding of the image <<<<<<------------
mtlContent = mtlContent.replace(/map_Kd\s+[^\s]+/, `map_Kd ${textureBase64}`);
const numMeshes = scene.meshes.length; // Pre-load number of existing meshes
// Load .obj file; may raise error due to "mtllib" directive, but execution keeps going <<<<<<-----------
BABYLON.SceneLoader.Append("", "data:" + objContent, scene, (scene) => {}, undefined, undefined, ".obj");
// Put in babylon scene material taken from modified .mtl file
var mtlLoader = new BABYLON.MTLFileLoader().parseMTL(scene, mtlContent, "");
//////// Assign material to object
finalMat = null;
materialName = "texture_" + files[0].name.split(".")[0];
scene.materials.forEach(mat => {
if (mat.id === materialName) {
finalMat = mat;
}
});
scene.meshes[numMeshes].material = finalMat; // Material is assigned to the last-loaded mesh
scene.meshes[numMeshes].material.backFaceCulling = false; // Make material visible on both faces
};
////////////////////////////
// Listener for file loading
const fileInput = document.getElementById("fileInput");
fileInput.addEventListener("change", async (event) => {
const files = event.target.files;
if (files.length > 0) {
await processFiles(files);
}
});
// Avvia il rendering
engine.runRenderLoop(() => {
scene.render();
});
window.addEventListener("resize", () => {
engine.resize();
});
function ZdownToZin(objFileContent) {
/* Convert from reference system with Z+ downward, Y right and X into screen to
* reference system with Z+ to inside screen and Y+ upward (babylon).
*/
const lines = objFileContent.split("\n");
// Map rows
const transformedLines = lines.map(line => {
// Vertex line starts by "v"
if (line.startsWith("v ")) {
const parts = line.split(" ");
//////// Swap
const x = parts[1];
const y = parts[2];
const z = parts[3];
return `v ${x} ${-z} ${y}`;
}
// Return unchanged lines
return line;
});
//Merge evwerything
return transformedLines.join("\n");
}
</script>
</body>
</html>