2017-03-05 14:47:28 +00:00
/**************************************************************************/
/* resource_importer_obj.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
2018-01-04 23:50:27 +00:00
2017-02-03 03:08:50 +00:00
# include "resource_importer_obj.h"
2021-06-11 12:51:48 +00:00
# include "core/io/file_access.h"
2018-09-11 16:13:45 +00:00
# include "core/io/resource_saver.h"
2021-09-21 01:24:31 +00:00
# include "scene/3d/importer_mesh_instance_3d.h"
2020-03-26 21:49:16 +00:00
# include "scene/3d/mesh_instance_3d.h"
# include "scene/3d/node_3d.h"
2021-08-13 16:42:45 +00:00
# include "scene/resources/3d/importer_mesh.h"
2017-02-03 03:08:50 +00:00
# include "scene/resources/mesh.h"
# include "scene/resources/surface_tool.h"
2017-07-18 00:05:38 +00:00
uint32_t EditorOBJImporter : : get_import_flags ( ) const {
return IMPORT_SCENE ;
2017-02-03 03:08:50 +00:00
}
2022-05-13 13:04:37 +00:00
static Error _parse_material_library ( const String & p_path , HashMap < String , Ref < StandardMaterial3D > > & material_map , List < String > * r_missing_deps ) {
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( p_path , FileAccess : : READ ) ;
ERR_FAIL_COND_V_MSG ( f . is_null ( ) , ERR_CANT_OPEN , vformat ( " Couldn't open MTL file '%s', it may not exist or not be readable. " , p_path ) ) ;
2017-02-03 03:08:50 +00:00
2019-09-15 04:01:52 +00:00
Ref < StandardMaterial3D > current ;
2017-07-18 00:05:38 +00:00
String current_name ;
String base_path = p_path . get_base_dir ( ) ;
while ( true ) {
String l = f - > get_line ( ) . strip_edges ( ) ;
2017-02-03 03:08:50 +00:00
2017-07-18 00:05:38 +00:00
if ( l . begins_with ( " newmtl " ) ) {
//vertex
2017-02-03 03:08:50 +00:00
2017-07-18 00:05:38 +00:00
current_name = l . replace ( " newmtl " , " " ) . strip_edges ( ) ;
2021-06-17 22:03:09 +00:00
current . instantiate ( ) ;
2017-11-25 15:32:02 +00:00
current - > set_name ( current_name ) ;
2017-07-18 00:05:38 +00:00
material_map [ current_name ] = current ;
} else if ( l . begins_with ( " Ka " ) ) {
//uv
2019-11-07 08:44:15 +00:00
WARN_PRINT ( " OBJ: Ambient light for material ' " + current_name + " ' is ignored in PBR " ) ;
2017-02-03 03:08:50 +00:00
2017-07-18 00:05:38 +00:00
} else if ( l . begins_with ( " Kd " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
Vector < String > v = l . split ( " " , false ) ;
ERR_FAIL_COND_V ( v . size ( ) < 4 , ERR_INVALID_DATA ) ;
Color c = current - > get_albedo ( ) ;
c . r = v [ 1 ] . to_float ( ) ;
c . g = v [ 2 ] . to_float ( ) ;
c . b = v [ 3 ] . to_float ( ) ;
current - > set_albedo ( c ) ;
} else if ( l . begins_with ( " Ks " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
Vector < String > v = l . split ( " " , false ) ;
ERR_FAIL_COND_V ( v . size ( ) < 4 , ERR_INVALID_DATA ) ;
float r = v [ 1 ] . to_float ( ) ;
float g = v [ 2 ] . to_float ( ) ;
float b = v [ 3 ] . to_float ( ) ;
float metalness = MAX ( r , MAX ( g , b ) ) ;
current - > set_metallic ( metalness ) ;
} else if ( l . begins_with ( " Ns " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
Vector < String > v = l . split ( " " , false ) ;
ERR_FAIL_COND_V ( v . size ( ) ! = 2 , ERR_INVALID_DATA ) ;
float s = v [ 1 ] . to_float ( ) ;
current - > set_metallic ( ( 1000.0 - s ) / 1000.0 ) ;
} else if ( l . begins_with ( " d " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
Vector < String > v = l . split ( " " , false ) ;
ERR_FAIL_COND_V ( v . size ( ) ! = 2 , ERR_INVALID_DATA ) ;
float d = v [ 1 ] . to_float ( ) ;
Color c = current - > get_albedo ( ) ;
c . a = d ;
current - > set_albedo ( c ) ;
if ( c . a < 0.99 ) {
2019-09-15 04:01:52 +00:00
current - > set_transparency ( StandardMaterial3D : : TRANSPARENCY_ALPHA ) ;
2017-07-18 00:05:38 +00:00
}
} else if ( l . begins_with ( " Tr " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
Vector < String > v = l . split ( " " , false ) ;
ERR_FAIL_COND_V ( v . size ( ) ! = 2 , ERR_INVALID_DATA ) ;
float d = v [ 1 ] . to_float ( ) ;
Color c = current - > get_albedo ( ) ;
c . a = 1.0 - d ;
current - > set_albedo ( c ) ;
if ( c . a < 0.99 ) {
2019-09-15 04:01:52 +00:00
current - > set_transparency ( StandardMaterial3D : : TRANSPARENCY_ALPHA ) ;
2017-07-18 00:05:38 +00:00
}
} else if ( l . begins_with ( " map_Ka " ) ) {
//uv
2019-11-07 08:44:15 +00:00
WARN_PRINT ( " OBJ: Ambient light texture for material ' " + current_name + " ' is ignored in PBR " ) ;
2017-07-18 00:05:38 +00:00
} else if ( l . begins_with ( " map_Kd " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
String p = l . replace ( " map_Kd " , " " ) . replace ( " \\ " , " / " ) . strip_edges ( ) ;
2018-09-17 02:23:40 +00:00
String path ;
2021-06-03 13:41:22 +00:00
if ( p . is_absolute_path ( ) ) {
2018-09-17 02:23:40 +00:00
path = p ;
} else {
2022-08-30 00:34:01 +00:00
path = base_path . path_join ( p ) ;
2018-09-17 02:23:40 +00:00
}
2017-07-18 00:05:38 +00:00
2019-06-11 18:43:37 +00:00
Ref < Texture2D > texture = ResourceLoader : : load ( path ) ;
2017-07-18 00:05:38 +00:00
if ( texture . is_valid ( ) ) {
2019-09-15 04:01:52 +00:00
current - > set_texture ( StandardMaterial3D : : TEXTURE_ALBEDO , texture ) ;
2017-09-29 23:38:27 +00:00
} else if ( r_missing_deps ) {
2017-07-18 00:05:38 +00:00
r_missing_deps - > push_back ( path ) ;
}
} else if ( l . begins_with ( " map_Ks " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
String p = l . replace ( " map_Ks " , " " ) . replace ( " \\ " , " / " ) . strip_edges ( ) ;
2018-09-17 02:23:40 +00:00
String path ;
2021-06-03 13:41:22 +00:00
if ( p . is_absolute_path ( ) ) {
2018-09-17 02:23:40 +00:00
path = p ;
} else {
2022-08-30 00:34:01 +00:00
path = base_path . path_join ( p ) ;
2018-09-17 02:23:40 +00:00
}
2017-07-18 00:05:38 +00:00
2019-06-11 18:43:37 +00:00
Ref < Texture2D > texture = ResourceLoader : : load ( path ) ;
2017-07-18 00:05:38 +00:00
if ( texture . is_valid ( ) ) {
2019-09-15 04:01:52 +00:00
current - > set_texture ( StandardMaterial3D : : TEXTURE_METALLIC , texture ) ;
2017-09-29 23:38:27 +00:00
} else if ( r_missing_deps ) {
2017-07-18 00:05:38 +00:00
r_missing_deps - > push_back ( path ) ;
}
} else if ( l . begins_with ( " map_Ns " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
String p = l . replace ( " map_Ns " , " " ) . replace ( " \\ " , " / " ) . strip_edges ( ) ;
2018-09-17 02:23:40 +00:00
String path ;
2021-06-03 13:41:22 +00:00
if ( p . is_absolute_path ( ) ) {
2018-09-17 02:23:40 +00:00
path = p ;
} else {
2022-08-30 00:34:01 +00:00
path = base_path . path_join ( p ) ;
2018-09-17 02:23:40 +00:00
}
2017-07-18 00:05:38 +00:00
2019-06-11 18:43:37 +00:00
Ref < Texture2D > texture = ResourceLoader : : load ( path ) ;
2017-07-18 00:05:38 +00:00
if ( texture . is_valid ( ) ) {
2019-09-15 04:01:52 +00:00
current - > set_texture ( StandardMaterial3D : : TEXTURE_ROUGHNESS , texture ) ;
2017-09-29 23:38:27 +00:00
} else if ( r_missing_deps ) {
2017-07-18 00:05:38 +00:00
r_missing_deps - > push_back ( path ) ;
}
} else if ( l . begins_with ( " map_bump " ) ) {
//normal
ERR_FAIL_COND_V ( current . is_null ( ) , ERR_FILE_CORRUPT ) ;
String p = l . replace ( " map_bump " , " " ) . replace ( " \\ " , " / " ) . strip_edges ( ) ;
2022-08-30 00:34:01 +00:00
String path = base_path . path_join ( p ) ;
2017-02-03 03:08:50 +00:00
2019-06-11 18:43:37 +00:00
Ref < Texture2D > texture = ResourceLoader : : load ( path ) ;
2017-07-18 00:05:38 +00:00
if ( texture . is_valid ( ) ) {
2019-09-15 04:01:52 +00:00
current - > set_feature ( StandardMaterial3D : : FEATURE_NORMAL_MAPPING , true ) ;
current - > set_texture ( StandardMaterial3D : : TEXTURE_NORMAL , texture ) ;
2017-09-29 23:38:27 +00:00
} else if ( r_missing_deps ) {
2017-07-18 00:05:38 +00:00
r_missing_deps - > push_back ( path ) ;
}
} else if ( f - > eof_reached ( ) ) {
break ;
}
}
return OK ;
2017-02-03 03:08:50 +00:00
}
2024-07-08 23:10:06 +00:00
static Error _parse_obj ( const String & p_path , List < Ref < ImporterMesh > > & r_meshes , bool p_single_mesh , bool p_generate_tangents , bool p_generate_lods , bool p_generate_shadow_mesh , bool p_generate_lightmap_uv2 , float p_generate_lightmap_uv2_texel_size , const PackedByteArray & p_src_lightmap_cache , Vector3 p_scale_mesh , Vector3 p_offset_mesh , bool p_disable_compression , Vector < Vector < uint8_t > > & r_lightmap_caches , List < String > * r_missing_deps ) {
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( p_path , FileAccess : : READ ) ;
ERR_FAIL_COND_V_MSG ( f . is_null ( ) , ERR_CANT_OPEN , vformat ( " Couldn't open OBJ file '%s', it may not exist or not be readable. " , p_path ) ) ;
2017-07-18 00:05:38 +00:00
2024-07-08 23:10:06 +00:00
// Avoid trying to load/interpret potential build artifacts from Visual Studio (e.g. when compiling native plugins inside the project tree).
// This should only match if it's indeed a COFF file header.
2023-01-19 08:42:57 +00:00
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
const int first_bytes = f - > get_16 ( ) ;
static const Vector < int > coff_header_machines {
0x0 , // IMAGE_FILE_MACHINE_UNKNOWN
0x8664 , // IMAGE_FILE_MACHINE_AMD64
0x1c0 , // IMAGE_FILE_MACHINE_ARM
0x14c , // IMAGE_FILE_MACHINE_I386
0x200 , // IMAGE_FILE_MACHINE_IA64
} ;
2024-05-06 14:20:20 +00:00
ERR_FAIL_COND_V_MSG ( coff_header_machines . has ( first_bytes ) , ERR_FILE_CORRUPT , vformat ( " Couldn't read OBJ file '%s', it seems to be binary, corrupted, or empty. " , p_path ) ) ;
2023-01-19 08:42:57 +00:00
f - > seek ( 0 ) ;
2024-01-08 21:53:49 +00:00
Ref < ImporterMesh > mesh ;
2021-06-17 22:03:09 +00:00
mesh . instantiate ( ) ;
2017-02-03 03:08:50 +00:00
2017-09-29 23:38:27 +00:00
bool generate_tangents = p_generate_tangents ;
2018-02-12 10:36:40 +00:00
Vector3 scale_mesh = p_scale_mesh ;
2020-01-11 08:21:38 +00:00
Vector3 offset_mesh = p_offset_mesh ;
2018-06-30 22:58:37 +00:00
2017-02-03 03:08:50 +00:00
Vector < Vector3 > vertices ;
Vector < Vector3 > normals ;
Vector < Vector2 > uvs ;
2023-01-07 17:29:09 +00:00
Vector < Color > colors ;
2023-01-08 16:40:09 +00:00
const String default_name = " Mesh " ;
String name = default_name ;
2017-02-03 03:08:50 +00:00
2022-05-13 13:04:37 +00:00
HashMap < String , HashMap < String , Ref < StandardMaterial3D > > > material_map ;
2017-07-18 00:05:38 +00:00
2017-02-03 03:08:50 +00:00
Ref < SurfaceTool > surf_tool = memnew ( SurfaceTool ) ;
surf_tool - > begin ( Mesh : : PRIMITIVE_TRIANGLES ) ;
2017-07-18 00:05:38 +00:00
String current_material_library ;
String current_material ;
String current_group ;
2020-12-12 12:06:59 +00:00
uint32_t smooth_group = 0 ;
bool smoothing = true ;
2023-03-25 10:38:55 +00:00
const uint32_t no_smoothing_smooth_group = ( uint32_t ) - 1 ;
2017-02-03 03:08:50 +00:00
2024-09-21 02:22:37 +00:00
bool uses_uvs = false ;
2017-02-03 03:08:50 +00:00
while ( true ) {
String l = f - > get_line ( ) . strip_edges ( ) ;
2018-07-30 00:37:55 +00:00
while ( l . length ( ) & & l [ l . length ( ) - 1 ] = = ' \\ ' ) {
String add = f - > get_line ( ) . strip_edges ( ) ;
l + = add ;
2021-12-09 09:42:46 +00:00
if ( add . is_empty ( ) ) {
2018-07-30 00:37:55 +00:00
break ;
}
}
2017-02-03 03:08:50 +00:00
if ( l . begins_with ( " v " ) ) {
//vertex
Vector < String > v = l . split ( " " , false ) ;
2017-09-29 23:38:27 +00:00
ERR_FAIL_COND_V ( v . size ( ) < 4 , ERR_FILE_CORRUPT ) ;
2017-02-03 03:08:50 +00:00
Vector3 vtx ;
2020-01-11 08:21:38 +00:00
vtx . x = v [ 1 ] . to_float ( ) * scale_mesh . x + offset_mesh . x ;
vtx . y = v [ 2 ] . to_float ( ) * scale_mesh . y + offset_mesh . y ;
vtx . z = v [ 3 ] . to_float ( ) * scale_mesh . z + offset_mesh . z ;
2017-02-03 03:08:50 +00:00
vertices . push_back ( vtx ) ;
2023-01-07 17:29:09 +00:00
//vertex color
2023-05-13 20:37:38 +00:00
if ( v . size ( ) > = 7 ) {
2023-01-07 17:29:09 +00:00
while ( colors . size ( ) < vertices . size ( ) - 1 ) {
colors . push_back ( Color ( 1.0 , 1.0 , 1.0 ) ) ;
}
Color c ;
c . r = v [ 4 ] . to_float ( ) ;
c . g = v [ 5 ] . to_float ( ) ;
c . b = v [ 6 ] . to_float ( ) ;
colors . push_back ( c ) ;
} else if ( ! colors . is_empty ( ) ) {
colors . push_back ( Color ( 1.0 , 1.0 , 1.0 ) ) ;
}
2017-02-03 03:08:50 +00:00
} else if ( l . begins_with ( " vt " ) ) {
//uv
Vector < String > v = l . split ( " " , false ) ;
2017-09-29 23:38:27 +00:00
ERR_FAIL_COND_V ( v . size ( ) < 3 , ERR_FILE_CORRUPT ) ;
2017-02-03 03:08:50 +00:00
Vector2 uv ;
uv . x = v [ 1 ] . to_float ( ) ;
uv . y = 1.0 - v [ 2 ] . to_float ( ) ;
uvs . push_back ( uv ) ;
} else if ( l . begins_with ( " vn " ) ) {
//normal
Vector < String > v = l . split ( " " , false ) ;
2017-09-29 23:38:27 +00:00
ERR_FAIL_COND_V ( v . size ( ) < 4 , ERR_FILE_CORRUPT ) ;
2017-02-03 03:08:50 +00:00
Vector3 nrm ;
nrm . x = v [ 1 ] . to_float ( ) ;
nrm . y = v [ 2 ] . to_float ( ) ;
nrm . z = v [ 3 ] . to_float ( ) ;
normals . push_back ( nrm ) ;
2017-02-23 08:28:09 +00:00
} else if ( l . begins_with ( " f " ) ) {
2017-02-03 03:08:50 +00:00
//vertex
Vector < String > v = l . split ( " " , false ) ;
2017-09-29 23:38:27 +00:00
ERR_FAIL_COND_V ( v . size ( ) < 4 , ERR_FILE_CORRUPT ) ;
2017-02-03 03:08:50 +00:00
//not very fast, could be sped up
Vector < String > face [ 3 ] ;
face [ 0 ] = v [ 1 ] . split ( " / " ) ;
face [ 1 ] = v [ 2 ] . split ( " / " ) ;
2024-01-19 12:21:39 +00:00
ERR_FAIL_COND_V ( face [ 0 ] . is_empty ( ) , ERR_FILE_CORRUPT ) ;
2018-07-30 00:37:55 +00:00
2017-09-29 23:38:27 +00:00
ERR_FAIL_COND_V ( face [ 0 ] . size ( ) ! = face [ 1 ] . size ( ) , ERR_FILE_CORRUPT ) ;
2017-02-03 03:08:50 +00:00
for ( int i = 2 ; i < v . size ( ) - 1 ; i + + ) {
face [ 2 ] = v [ i + 1 ] . split ( " / " ) ;
2018-07-30 00:37:55 +00:00
2017-09-29 23:38:27 +00:00
ERR_FAIL_COND_V ( face [ 0 ] . size ( ) ! = face [ 2 ] . size ( ) , ERR_FILE_CORRUPT ) ;
2017-02-03 03:08:50 +00:00
for ( int j = 0 ; j < 3 ; j + + ) {
int idx = j ;
2019-06-20 14:59:48 +00:00
if ( idx < 2 ) {
2017-02-03 03:08:50 +00:00
idx = 1 ^ idx ;
}
2024-09-21 02:22:37 +00:00
// Check UVs before faces as we may need to generate dummy tangents if there are no UVs.
if ( face [ idx ] . size ( ) > = 2 & & ! face [ idx ] [ 1 ] . is_empty ( ) ) {
int uv = face [ idx ] [ 1 ] . to_int ( ) - 1 ;
if ( uv < 0 ) {
uv + = uvs . size ( ) + 1 ;
}
ERR_FAIL_INDEX_V ( uv , uvs . size ( ) , ERR_FILE_CORRUPT ) ;
surf_tool - > set_uv ( uvs [ uv ] ) ;
uses_uvs = true ;
}
2017-02-03 03:08:50 +00:00
if ( face [ idx ] . size ( ) = = 3 ) {
int norm = face [ idx ] [ 2 ] . to_int ( ) - 1 ;
2020-05-14 14:41:43 +00:00
if ( norm < 0 ) {
2017-04-27 08:24:09 +00:00
norm + = normals . size ( ) + 1 ;
2020-05-14 14:41:43 +00:00
}
2017-09-29 23:38:27 +00:00
ERR_FAIL_INDEX_V ( norm , normals . size ( ) , ERR_FILE_CORRUPT ) ;
2020-12-02 01:40:47 +00:00
surf_tool - > set_normal ( normals [ norm ] ) ;
2024-09-21 02:22:37 +00:00
if ( generate_tangents & & ! uses_uvs ) {
2023-10-31 14:51:07 +00:00
// We can't generate tangents without UVs, so create dummy tangents.
2024-02-23 21:54:20 +00:00
Vector3 tan = Vector3 ( normals [ norm ] . z , - normals [ norm ] . x , normals [ norm ] . y ) . cross ( normals [ norm ] . normalized ( ) ) . normalized ( ) ;
2023-10-31 14:51:07 +00:00
surf_tool - > set_tangent ( Plane ( tan . x , tan . y , tan . z , 1.0 ) ) ;
}
} else {
2024-02-23 21:54:20 +00:00
// No normals, use a dummy tangent since normals and tangents will be generated.
2024-09-21 02:22:37 +00:00
if ( generate_tangents & & ! uses_uvs ) {
2023-10-31 14:51:07 +00:00
// We can't generate tangents without UVs, so create dummy tangents.
surf_tool - > set_tangent ( Plane ( 1.0 , 0.0 , 0.0 , 1.0 ) ) ;
}
2017-02-03 03:08:50 +00:00
}
int vtx = face [ idx ] [ 0 ] . to_int ( ) - 1 ;
2020-05-14 14:41:43 +00:00
if ( vtx < 0 ) {
2017-04-27 08:24:09 +00:00
vtx + = vertices . size ( ) + 1 ;
2020-05-14 14:41:43 +00:00
}
2017-09-29 23:38:27 +00:00
ERR_FAIL_INDEX_V ( vtx , vertices . size ( ) , ERR_FILE_CORRUPT ) ;
2017-02-03 03:08:50 +00:00
Vector3 vertex = vertices [ vtx ] ;
2023-01-07 17:29:09 +00:00
if ( ! colors . is_empty ( ) ) {
surf_tool - > set_color ( colors [ vtx ] ) ;
}
2023-03-25 10:38:55 +00:00
surf_tool - > set_smooth_group ( smoothing ? smooth_group : no_smoothing_smooth_group ) ;
2017-02-03 03:08:50 +00:00
surf_tool - > add_vertex ( vertex ) ;
}
face [ 1 ] = face [ 2 ] ;
}
2017-07-18 00:05:38 +00:00
} else if ( l . begins_with ( " s " ) ) { //smoothing
2017-02-03 03:08:50 +00:00
String what = l . substr ( 2 , l . length ( ) ) . strip_edges ( ) ;
2020-12-12 12:06:59 +00:00
bool do_smooth ;
2020-05-14 14:41:43 +00:00
if ( what = = " off " ) {
2020-12-12 12:06:59 +00:00
do_smooth = false ;
2020-05-14 14:41:43 +00:00
} else {
2020-12-12 12:06:59 +00:00
do_smooth = true ;
}
if ( do_smooth ! = smoothing ) {
smoothing = do_smooth ;
2023-03-25 10:38:55 +00:00
if ( smoothing ) {
smooth_group + + ;
}
2020-05-14 14:41:43 +00:00
}
2017-07-29 03:03:54 +00:00
} else if ( /*l.begins_with("g ") | | */ l . begins_with ( " usemtl " ) | | ( l . begins_with ( " o " ) | | f - > eof_reached ( ) ) ) { //commit group to mesh
2023-10-14 03:59:56 +00:00
uint64_t mesh_flags = RS : : ARRAY_FLAG_COMPRESS_ATTRIBUTES ;
if ( p_disable_compression ) {
mesh_flags = 0 ;
2023-12-13 11:18:50 +00:00
} else {
bool is_mesh_2d = true ;
// Disable compression if all z equals 0 (the mesh is 2D).
for ( int i = 0 ; i < vertices . size ( ) ; i + + ) {
if ( ! Math : : is_zero_approx ( vertices [ i ] . z ) ) {
is_mesh_2d = false ;
break ;
}
}
if ( is_mesh_2d ) {
mesh_flags = 0 ;
}
2023-10-14 03:59:56 +00:00
}
2023-12-13 11:18:50 +00:00
2017-07-29 03:03:54 +00:00
//groups are too annoying
2017-07-18 00:05:38 +00:00
if ( surf_tool - > get_vertex_array ( ) . size ( ) ) {
//another group going on, commit it
if ( normals . size ( ) = = 0 ) {
2017-02-03 03:08:50 +00:00
surf_tool - > generate_normals ( ) ;
2017-07-18 00:05:38 +00:00
}
2024-09-21 02:22:37 +00:00
if ( generate_tangents & & uses_uvs ) {
2017-02-03 03:08:50 +00:00
surf_tool - > generate_tangents ( ) ;
2017-07-18 00:05:38 +00:00
}
2017-02-03 03:08:50 +00:00
surf_tool - > index ( ) ;
2017-07-18 00:05:38 +00:00
2018-08-24 07:35:07 +00:00
print_verbose ( " OBJ: Current material library " + current_material_library + " has " + itos ( material_map . has ( current_material_library ) ) ) ;
print_verbose ( " OBJ: Current material " + current_material + " has " + itos ( material_map . has ( current_material_library ) & & material_map [ current_material_library ] . has ( current_material ) ) ) ;
2024-01-08 21:53:49 +00:00
Ref < StandardMaterial3D > material ;
2017-07-18 00:05:38 +00:00
if ( material_map . has ( current_material_library ) & & material_map [ current_material_library ] . has ( current_material ) ) {
2024-01-08 21:53:49 +00:00
material = material_map [ current_material_library ] [ current_material ] ;
2023-01-07 22:45:28 +00:00
if ( ! colors . is_empty ( ) ) {
material - > set_flag ( StandardMaterial3D : : FLAG_SRGB_VERTEX_COLOR , true ) ;
}
surf_tool - > set_material ( material ) ;
2017-07-18 00:05:38 +00:00
}
2024-01-08 21:53:49 +00:00
Array array = surf_tool - > commit_to_arrays ( ) ;
2024-02-23 21:54:20 +00:00
2024-09-21 02:22:37 +00:00
if ( mesh_flags & RS : : ARRAY_FLAG_COMPRESS_ATTRIBUTES & & generate_tangents & & uses_uvs ) {
// Compression is enabled, so let's validate that the normals and generated tangents are correct.
2024-02-23 21:54:20 +00:00
Vector < Vector3 > norms = array [ Mesh : : ARRAY_NORMAL ] ;
Vector < float > tangents = array [ Mesh : : ARRAY_TANGENT ] ;
2024-09-21 02:22:37 +00:00
ERR_FAIL_COND_V ( tangents . is_empty ( ) , ERR_FILE_CORRUPT ) ;
2024-02-23 21:54:20 +00:00
for ( int vert = 0 ; vert < norms . size ( ) ; vert + + ) {
Vector3 tan = Vector3 ( tangents [ vert * 4 + 0 ] , tangents [ vert * 4 + 1 ] , tangents [ vert * 4 + 2 ] ) ;
if ( abs ( tan . dot ( norms [ vert ] ) ) > 0.0001 ) {
// Tangent is not perpendicular to the normal, so we can't use compression.
mesh_flags & = ~ RS : : ARRAY_FLAG_COMPRESS_ATTRIBUTES ;
}
}
}
2024-01-08 21:53:49 +00:00
mesh - > add_surface ( Mesh : : PRIMITIVE_TRIANGLES , array , TypedArray < Array > ( ) , Dictionary ( ) , material , name , mesh_flags ) ;
2024-07-08 23:10:06 +00:00
2024-01-08 21:53:49 +00:00
print_verbose ( " OBJ: Added surface : " + mesh - > get_surface_name ( mesh - > get_surface_count ( ) - 1 ) ) ;
2017-07-18 00:05:38 +00:00
2024-07-31 06:50:02 +00:00
if ( ! current_material . is_empty ( ) ) {
if ( mesh - > get_surface_count ( ) > = 1 ) {
mesh - > set_surface_name ( mesh - > get_surface_count ( ) - 1 , current_material . get_basename ( ) ) ;
}
} else if ( ! current_group . is_empty ( ) ) {
if ( mesh - > get_surface_count ( ) > = 1 ) {
mesh - > set_surface_name ( mesh - > get_surface_count ( ) - 1 , current_group ) ;
}
}
2017-02-03 03:08:50 +00:00
surf_tool - > clear ( ) ;
surf_tool - > begin ( Mesh : : PRIMITIVE_TRIANGLES ) ;
2024-09-21 02:22:37 +00:00
uses_uvs = false ;
2017-07-18 00:05:38 +00:00
}
if ( l . begins_with ( " o " ) | | f - > eof_reached ( ) ) {
2017-09-29 23:38:27 +00:00
if ( ! p_single_mesh ) {
2023-01-08 16:40:09 +00:00
if ( mesh - > get_surface_count ( ) > 0 ) {
mesh - > set_name ( name ) ;
r_meshes . push_back ( mesh ) ;
mesh . instantiate ( ) ;
}
name = default_name ;
2017-09-29 23:38:27 +00:00
current_group = " " ;
current_material = " " ;
}
2017-02-03 03:08:50 +00:00
}
2017-07-18 00:05:38 +00:00
if ( f - > eof_reached ( ) ) {
break ;
}
if ( l . begins_with ( " o " ) ) {
2017-02-03 03:08:50 +00:00
name = l . substr ( 2 , l . length ( ) ) . strip_edges ( ) ;
2017-07-18 00:05:38 +00:00
}
if ( l . begins_with ( " usemtl " ) ) {
current_material = l . replace ( " usemtl " , " " ) . strip_edges ( ) ;
}
if ( l . begins_with ( " g " ) ) {
current_group = l . substr ( 2 , l . length ( ) ) . strip_edges ( ) ;
}
} else if ( l . begins_with ( " mtllib " ) ) { //parse material
current_material_library = l . replace ( " mtllib " , " " ) . strip_edges ( ) ;
if ( ! material_map . has ( current_material_library ) ) {
2022-05-13 13:04:37 +00:00
HashMap < String , Ref < StandardMaterial3D > > lib ;
2021-11-07 02:12:36 +00:00
String lib_path = current_material_library ;
if ( lib_path . is_relative_path ( ) ) {
2022-08-30 00:34:01 +00:00
lib_path = p_path . get_base_dir ( ) . path_join ( current_material_library ) ;
2017-07-18 21:21:51 +00:00
}
2021-11-07 02:12:36 +00:00
Error err = _parse_material_library ( lib_path , lib , r_missing_deps ) ;
2017-07-18 00:05:38 +00:00
if ( err = = OK ) {
material_map [ current_material_library ] = lib ;
}
}
2017-02-03 03:08:50 +00:00
}
}
2024-07-08 23:10:06 +00:00
if ( p_generate_lightmap_uv2 ) {
Vector < uint8_t > lightmap_cache ;
mesh - > lightmap_unwrap_cached ( Transform3D ( ) , p_generate_lightmap_uv2_texel_size , p_src_lightmap_cache , lightmap_cache ) ;
if ( ! lightmap_cache . is_empty ( ) ) {
if ( r_lightmap_caches . is_empty ( ) ) {
r_lightmap_caches . push_back ( lightmap_cache ) ;
} else {
// MD5 is stored at the beginning of the cache data.
const String new_md5 = String : : md5 ( lightmap_cache . ptr ( ) ) ;
for ( int i = 0 ; i < r_lightmap_caches . size ( ) ; i + + ) {
const String md5 = String : : md5 ( r_lightmap_caches [ i ] . ptr ( ) ) ;
if ( new_md5 < md5 ) {
r_lightmap_caches . insert ( i , lightmap_cache ) ;
break ;
}
if ( new_md5 = = md5 ) {
break ;
}
}
}
}
}
if ( p_generate_lods ) {
// Use normal merge/split angles that match the defaults used for 3D scene importing.
2024-10-28 17:14:04 +00:00
mesh - > generate_lods ( 60.0f , { } ) ;
2024-07-08 23:10:06 +00:00
}
if ( p_generate_shadow_mesh ) {
mesh - > create_shadow_mesh ( ) ;
}
2024-11-03 19:57:07 +00:00
mesh - > optimize_indices ( ) ;
2024-03-22 11:37:03 +00:00
if ( p_single_mesh & & mesh - > get_surface_count ( ) > 0 ) {
2017-09-29 23:38:27 +00:00
r_meshes . push_back ( mesh ) ;
}
return OK ;
}
2022-11-14 19:14:52 +00:00
Node * EditorOBJImporter : : import_scene ( const String & p_path , uint32_t p_flags , const HashMap < StringName , Variant > & p_options , List < String > * r_missing_deps , Error * r_err ) {
2024-01-08 21:53:49 +00:00
List < Ref < ImporterMesh > > meshes ;
2017-09-29 23:38:27 +00:00
2024-07-08 23:10:06 +00:00
// LOD, shadow mesh and lightmap UV2 generation are handled by ResourceImporterScene in this case,
// so disable it within the OBJ mesh import.
Vector < Vector < uint8_t > > mesh_lightmap_caches ;
Error err = _parse_obj ( p_path , meshes , false , p_flags & IMPORT_GENERATE_TANGENT_ARRAYS , false , false , false , 0.2 , PackedByteArray ( ) , Vector3 ( 1 , 1 , 1 ) , Vector3 ( 0 , 0 , 0 ) , p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION , mesh_lightmap_caches , r_missing_deps ) ;
2017-09-29 23:38:27 +00:00
if ( err ! = OK ) {
if ( r_err ) {
* r_err = err ;
}
2020-04-01 23:20:12 +00:00
return nullptr ;
2017-09-29 23:38:27 +00:00
}
2020-03-26 21:49:16 +00:00
Node3D * scene = memnew ( Node3D ) ;
2017-09-29 23:38:27 +00:00
2024-01-08 21:53:49 +00:00
for ( Ref < ImporterMesh > m : meshes ) {
2021-09-21 01:24:31 +00:00
ImporterMeshInstance3D * mi = memnew ( ImporterMeshInstance3D ) ;
2024-01-08 21:53:49 +00:00
mi - > set_mesh ( m ) ;
2021-07-16 03:45:57 +00:00
mi - > set_name ( m - > get_name ( ) ) ;
2021-10-21 14:46:07 +00:00
scene - > add_child ( mi , true ) ;
2017-09-29 23:38:27 +00:00
mi - > set_owner ( scene ) ;
}
2017-02-03 03:08:50 +00:00
2017-09-29 23:38:27 +00:00
if ( r_err ) {
* r_err = OK ;
2017-02-03 03:08:50 +00:00
}
2017-07-18 00:05:38 +00:00
return scene ;
2017-02-03 03:08:50 +00:00
}
2020-05-14 12:29:06 +00:00
2017-09-29 23:38:27 +00:00
void EditorOBJImporter : : get_extensions ( List < String > * r_extensions ) const {
r_extensions - > push_back ( " obj " ) ;
}
2017-07-18 00:05:38 +00:00
EditorOBJImporter : : EditorOBJImporter ( ) {
2017-02-03 03:08:50 +00:00
}
2020-05-14 12:29:06 +00:00
2017-09-29 23:38:27 +00:00
////////////////////////////////////////////////////
String ResourceImporterOBJ : : get_importer_name ( ) const {
return " wavefront_obj " ;
}
2020-05-14 12:29:06 +00:00
2017-09-29 23:38:27 +00:00
String ResourceImporterOBJ : : get_visible_name ( ) const {
2023-10-23 03:03:52 +00:00
return " OBJ as Mesh " ;
2017-09-29 23:38:27 +00:00
}
2020-05-14 12:29:06 +00:00
2017-09-29 23:38:27 +00:00
void ResourceImporterOBJ : : get_recognized_extensions ( List < String > * p_extensions ) const {
p_extensions - > push_back ( " obj " ) ;
}
2020-05-14 12:29:06 +00:00
2017-09-29 23:38:27 +00:00
String ResourceImporterOBJ : : get_save_extension ( ) const {
return " mesh " ;
}
2020-05-14 12:29:06 +00:00
2017-09-29 23:38:27 +00:00
String ResourceImporterOBJ : : get_resource_type ( ) const {
return " Mesh " ;
}
2020-12-02 01:40:47 +00:00
int ResourceImporterOBJ : : get_format_version ( ) const {
return 1 ;
}
2017-09-29 23:38:27 +00:00
int ResourceImporterOBJ : : get_preset_count ( ) const {
return 0 ;
}
2020-05-14 12:29:06 +00:00
2017-09-29 23:38:27 +00:00
String ResourceImporterOBJ : : get_preset_name ( int p_idx ) const {
return " " ;
}
2021-11-14 17:02:38 +00:00
void ResourceImporterOBJ : : get_import_options ( const String & p_path , List < ImportOption > * r_options , int p_preset ) const {
2017-09-29 23:38:27 +00:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " generate_tangents " ) , true ) ) ;
2024-07-08 23:10:06 +00:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " generate_lods " ) , true ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " generate_shadow_mesh " ) , true ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " generate_lightmap_uv2 " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED ) , false ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : FLOAT , " generate_lightmap_uv2_texel_size " , PROPERTY_HINT_RANGE , " 0.001,100,0.001 " ) , 0.2 ) ) ;
2018-02-12 10:36:40 +00:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : VECTOR3 , " scale_mesh " ) , Vector3 ( 1 , 1 , 1 ) ) ) ;
2020-01-11 08:21:38 +00:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : VECTOR3 , " offset_mesh " ) , Vector3 ( 0 , 0 , 0 ) ) ) ;
2023-08-29 19:04:32 +00:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " force_disable_mesh_compression " ) , false ) ) ;
2017-09-29 23:38:27 +00:00
}
2020-05-14 12:29:06 +00:00
2022-05-13 13:04:37 +00:00
bool ResourceImporterOBJ : : get_option_visibility ( const String & p_path , const String & p_option , const HashMap < StringName , Variant > & p_options ) const {
2024-07-08 23:10:06 +00:00
if ( p_option = = " generate_lightmap_uv2_texel_size " & & ! p_options [ " generate_lightmap_uv2 " ] ) {
// Only display the lightmap texel size import option when lightmap UV2 generation is enabled.
return false ;
}
2017-09-29 23:38:27 +00:00
return true ;
}
2024-09-23 14:07:40 +00:00
Error ResourceImporterOBJ : : import ( ResourceUID : : ID p_source_id , const String & p_source_file , const String & p_save_path , const HashMap < StringName , Variant > & p_options , List < String > * r_platform_variants , List < String > * r_gen_files , Variant * r_metadata ) {
2024-01-08 21:53:49 +00:00
List < Ref < ImporterMesh > > meshes ;
2017-09-29 23:38:27 +00:00
2024-07-08 23:10:06 +00:00
Vector < uint8_t > src_lightmap_cache ;
Vector < Vector < uint8_t > > mesh_lightmap_caches ;
Error err ;
{
src_lightmap_cache = FileAccess : : get_file_as_bytes ( p_source_file + " .unwrap_cache " , & err ) ;
if ( err ! = OK ) {
src_lightmap_cache . clear ( ) ;
}
}
err = _parse_obj ( p_source_file , meshes , true , p_options [ " generate_tangents " ] , p_options [ " generate_lods " ] , p_options [ " generate_shadow_mesh " ] , p_options [ " generate_lightmap_uv2 " ] , p_options [ " generate_lightmap_uv2_texel_size " ] , src_lightmap_cache , p_options [ " scale_mesh " ] , p_options [ " offset_mesh " ] , p_options [ " force_disable_mesh_compression " ] , mesh_lightmap_caches , nullptr ) ;
if ( mesh_lightmap_caches . size ( ) ) {
Ref < FileAccess > f = FileAccess : : open ( p_source_file + " .unwrap_cache " , FileAccess : : WRITE ) ;
if ( f . is_valid ( ) ) {
f - > store_32 ( mesh_lightmap_caches . size ( ) ) ;
for ( int i = 0 ; i < mesh_lightmap_caches . size ( ) ; i + + ) {
String md5 = String : : md5 ( mesh_lightmap_caches [ i ] . ptr ( ) ) ;
f - > store_buffer ( mesh_lightmap_caches [ i ] . ptr ( ) , mesh_lightmap_caches [ i ] . size ( ) ) ;
}
}
}
err = OK ;
2017-09-29 23:38:27 +00:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
ERR_FAIL_COND_V ( meshes . size ( ) ! = 1 , ERR_BUG ) ;
String save_path = p_save_path + " .mesh " ;
2024-01-08 21:53:49 +00:00
err = ResourceSaver : : save ( meshes . front ( ) - > get ( ) - > get_mesh ( ) , save_path ) ;
2017-09-29 23:38:27 +00:00
2019-09-25 08:28:50 +00:00
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Cannot save Mesh to file ' " + save_path + " '. " ) ;
2017-09-29 23:38:27 +00:00
r_gen_files - > push_back ( save_path ) ;
return OK ;
}
ResourceImporterOBJ : : ResourceImporterOBJ ( ) {
}