objFileLoader.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import { Vector2 } from "@babylonjs/core/Maths/math.vector.js";
  2. import { Tools } from "@babylonjs/core/Misc/tools.js";
  3. import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader.js";
  4. import { AssetContainer } from "@babylonjs/core/assetContainer.js";
  5. import { MTLFileLoader } from "./mtlFileLoader.js";
  6. import { SolidParser } from "./solidParser.js";
  7. /**
  8. * OBJ file type loader.
  9. * This is a babylon scene loader plugin.
  10. */
  11. export class OBJFileLoader {
  12. /**
  13. * Invert Y-Axis of referenced textures on load
  14. */
  15. static get INVERT_TEXTURE_Y() {
  16. return MTLFileLoader.INVERT_TEXTURE_Y;
  17. }
  18. static set INVERT_TEXTURE_Y(value) {
  19. MTLFileLoader.INVERT_TEXTURE_Y = value;
  20. }
  21. /**
  22. * Creates loader for .OBJ files
  23. *
  24. * @param loadingOptions options for loading and parsing OBJ/MTL files.
  25. */
  26. constructor(loadingOptions) {
  27. /**
  28. * Defines the name of the plugin.
  29. */
  30. this.name = "obj";
  31. /**
  32. * Defines the extension the plugin is able to load.
  33. */
  34. this.extensions = ".obj";
  35. this._assetContainer = null;
  36. this._loadingOptions = loadingOptions || OBJFileLoader._DefaultLoadingOptions;
  37. }
  38. static get _DefaultLoadingOptions() {
  39. return {
  40. computeNormals: OBJFileLoader.COMPUTE_NORMALS,
  41. optimizeNormals: OBJFileLoader.OPTIMIZE_NORMALS,
  42. importVertexColors: OBJFileLoader.IMPORT_VERTEX_COLORS,
  43. invertY: OBJFileLoader.INVERT_Y,
  44. invertTextureY: OBJFileLoader.INVERT_TEXTURE_Y,
  45. // eslint-disable-next-line @typescript-eslint/naming-convention
  46. UVScaling: OBJFileLoader.UV_SCALING,
  47. materialLoadingFailsSilently: OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY,
  48. optimizeWithUV: OBJFileLoader.OPTIMIZE_WITH_UV,
  49. skipMaterials: OBJFileLoader.SKIP_MATERIALS,
  50. useLegacyBehavior: OBJFileLoader.USE_LEGACY_BEHAVIOR,
  51. };
  52. }
  53. /**
  54. * Calls synchronously the MTL file attached to this obj.
  55. * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
  56. * Without this function materials are not displayed in the first frame (but displayed after).
  57. * In consequence it is impossible to get material information in your HTML file
  58. *
  59. * @param url The URL of the MTL file
  60. * @param rootUrl defines where to load data from
  61. * @param onSuccess Callback function to be called when the MTL file is loaded
  62. * @param onFailure
  63. */
  64. _loadMTL(url, rootUrl, onSuccess, onFailure) {
  65. //The complete path to the mtl file
  66. const pathOfFile = rootUrl + url;
  67. // Loads through the babylon tools to allow fileInput search.
  68. Tools.LoadFile(pathOfFile, onSuccess, undefined, undefined, false, (request, exception) => {
  69. onFailure(pathOfFile, exception);
  70. });
  71. }
  72. /**
  73. * Instantiates a OBJ file loader plugin.
  74. * @returns the created plugin
  75. */
  76. createPlugin() {
  77. return new OBJFileLoader(OBJFileLoader._DefaultLoadingOptions);
  78. }
  79. /**
  80. * If the data string can be loaded directly.
  81. * @returns if the data can be loaded directly
  82. */
  83. canDirectLoad() {
  84. return false;
  85. }
  86. /**
  87. * Imports one or more meshes from the loaded OBJ data and adds them to the scene
  88. * @param meshesNames a string or array of strings of the mesh names that should be loaded from the file
  89. * @param scene the scene the meshes should be added to
  90. * @param data the OBJ data to load
  91. * @param rootUrl root url to load from
  92. * @returns a promise containing the loaded meshes, particles, skeletons and animations
  93. */
  94. importMeshAsync(meshesNames, scene, data, rootUrl) {
  95. //get the meshes from OBJ file
  96. return this._parseSolid(meshesNames, scene, data, rootUrl).then((meshes) => {
  97. return {
  98. meshes: meshes,
  99. particleSystems: [],
  100. skeletons: [],
  101. animationGroups: [],
  102. transformNodes: [],
  103. geometries: [],
  104. lights: [],
  105. spriteManagers: [],
  106. };
  107. });
  108. }
  109. /**
  110. * Imports all objects from the loaded OBJ data and adds them to the scene
  111. * @param scene the scene the objects should be added to
  112. * @param data the OBJ data to load
  113. * @param rootUrl root url to load from
  114. * @returns a promise which completes when objects have been loaded to the scene
  115. */
  116. loadAsync(scene, data, rootUrl) {
  117. //Get the 3D model
  118. return this.importMeshAsync(null, scene, data, rootUrl).then(() => {
  119. // return void
  120. });
  121. }
  122. /**
  123. * Load into an asset container.
  124. * @param scene The scene to load into
  125. * @param data The data to import
  126. * @param rootUrl The root url for scene and resources
  127. * @returns The loaded asset container
  128. */
  129. loadAssetContainerAsync(scene, data, rootUrl) {
  130. const container = new AssetContainer(scene);
  131. this._assetContainer = container;
  132. return this.importMeshAsync(null, scene, data, rootUrl)
  133. .then((result) => {
  134. result.meshes.forEach((mesh) => container.meshes.push(mesh));
  135. result.meshes.forEach((mesh) => {
  136. const material = mesh.material;
  137. if (material) {
  138. // Materials
  139. if (container.materials.indexOf(material) == -1) {
  140. container.materials.push(material);
  141. // Textures
  142. const textures = material.getActiveTextures();
  143. textures.forEach((t) => {
  144. if (container.textures.indexOf(t) == -1) {
  145. container.textures.push(t);
  146. }
  147. });
  148. }
  149. }
  150. });
  151. this._assetContainer = null;
  152. return container;
  153. })
  154. .catch((ex) => {
  155. this._assetContainer = null;
  156. throw ex;
  157. });
  158. }
  159. /**
  160. * Read the OBJ file and create an Array of meshes.
  161. * Each mesh contains all information given by the OBJ and the MTL file.
  162. * i.e. vertices positions and indices, optional normals values, optional UV values, optional material
  163. * @param meshesNames defines a string or array of strings of the mesh names that should be loaded from the file
  164. * @param scene defines the scene where are displayed the data
  165. * @param data defines the content of the obj file
  166. * @param rootUrl defines the path to the folder
  167. * @returns the list of loaded meshes
  168. */
  169. _parseSolid(meshesNames, scene, data, rootUrl) {
  170. let fileToLoad = ""; //The name of the mtlFile to load
  171. const materialsFromMTLFile = new MTLFileLoader();
  172. const materialToUse = [];
  173. const babylonMeshesArray = []; //The mesh for babylon
  174. // Main function
  175. const solidParser = new SolidParser(materialToUse, babylonMeshesArray, this._loadingOptions);
  176. solidParser.parse(meshesNames, data, scene, this._assetContainer, (fileName) => {
  177. fileToLoad = fileName;
  178. });
  179. // load the materials
  180. const mtlPromises = [];
  181. // Check if we have a file to load
  182. if (fileToLoad !== "" && !this._loadingOptions.skipMaterials) {
  183. //Load the file synchronously
  184. mtlPromises.push(new Promise((resolve, reject) => {
  185. this._loadMTL(fileToLoad, rootUrl, (dataLoaded) => {
  186. try {
  187. //Create materials thanks MTLLoader function
  188. materialsFromMTLFile.parseMTL(scene, dataLoaded, rootUrl, this._assetContainer);
  189. //Look at each material loaded in the mtl file
  190. for (let n = 0; n < materialsFromMTLFile.materials.length; n++) {
  191. //Three variables to get all meshes with the same material
  192. let startIndex = 0;
  193. const _indices = [];
  194. let _index;
  195. //The material from MTL file is used in the meshes loaded
  196. //Push the indice in an array
  197. //Check if the material is not used for another mesh
  198. while ((_index = materialToUse.indexOf(materialsFromMTLFile.materials[n].name, startIndex)) > -1) {
  199. _indices.push(_index);
  200. startIndex = _index + 1;
  201. }
  202. //If the material is not used dispose it
  203. if (_index === -1 && _indices.length === 0) {
  204. //If the material is not needed, remove it
  205. materialsFromMTLFile.materials[n].dispose();
  206. }
  207. else {
  208. for (let o = 0; o < _indices.length; o++) {
  209. //Apply the material to the Mesh for each mesh with the material
  210. const mesh = babylonMeshesArray[_indices[o]];
  211. const material = materialsFromMTLFile.materials[n];
  212. mesh.material = material;
  213. if (!mesh.getTotalIndices()) {
  214. // No indices, we need to turn on point cloud
  215. material.pointsCloud = true;
  216. }
  217. }
  218. }
  219. }
  220. resolve();
  221. }
  222. catch (e) {
  223. Tools.Warn(`Error processing MTL file: '${fileToLoad}'`);
  224. if (this._loadingOptions.materialLoadingFailsSilently) {
  225. resolve();
  226. }
  227. else {
  228. reject(e);
  229. }
  230. }
  231. }, (pathOfFile, exception) => {
  232. Tools.Warn(`Error downloading MTL file: '${fileToLoad}'`);
  233. if (this._loadingOptions.materialLoadingFailsSilently) {
  234. resolve();
  235. }
  236. else {
  237. reject(exception);
  238. }
  239. });
  240. }));
  241. }
  242. //Return an array with all Mesh
  243. return Promise.all(mtlPromises).then(() => {
  244. return babylonMeshesArray;
  245. });
  246. }
  247. }
  248. /**
  249. * Defines if UVs are optimized by default during load.
  250. */
  251. OBJFileLoader.OPTIMIZE_WITH_UV = true;
  252. /**
  253. * Invert model on y-axis (does a model scaling inversion)
  254. */
  255. OBJFileLoader.INVERT_Y = false;
  256. /**
  257. * Include in meshes the vertex colors available in some OBJ files. This is not part of OBJ standard.
  258. */
  259. OBJFileLoader.IMPORT_VERTEX_COLORS = false;
  260. /**
  261. * Compute the normals for the model, even if normals are present in the file.
  262. */
  263. OBJFileLoader.COMPUTE_NORMALS = false;
  264. /**
  265. * Optimize the normals for the model. Lighting can be uneven if you use OptimizeWithUV = true because new vertices can be created for the same location if they pertain to different faces.
  266. * Using OptimizehNormals = true will help smoothing the lighting by averaging the normals of those vertices.
  267. */
  268. OBJFileLoader.OPTIMIZE_NORMALS = false;
  269. /**
  270. * Defines custom scaling of UV coordinates of loaded meshes.
  271. */
  272. OBJFileLoader.UV_SCALING = new Vector2(1, 1);
  273. /**
  274. * Skip loading the materials even if defined in the OBJ file (materials are ignored).
  275. */
  276. OBJFileLoader.SKIP_MATERIALS = false;
  277. /**
  278. * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
  279. *
  280. * Defaults to true for backwards compatibility.
  281. */
  282. OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY = true;
  283. /**
  284. * Loads assets without handedness conversions. This flag is for compatibility. Use it only if absolutely required. Defaults to false.
  285. */
  286. OBJFileLoader.USE_LEGACY_BEHAVIOR = false;
  287. if (SceneLoader) {
  288. //Add this loader into the register plugin
  289. SceneLoader.RegisterPlugin(new OBJFileLoader());
  290. }
  291. //# sourceMappingURL=objFileLoader.js.map