2024-02-08 21:00:07 +01:00
/*************************************************************************/
/* merging_tool.cpp */
/*************************************************************************/
/* This file is part of: */
/* PANDEMONIUM ENGINE */
/* https://github.com/Relintai/pandemonium_engine */
/*************************************************************************/
/* Copyright (c) 2022-present Péter Magyar. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* Copyright (c) 2007-2022 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. */
/*************************************************************************/
# include "merging_tool.h"
# include "core/config/engine.h"
# include "core/os/os.h"
# include "scene/3d/mesh_instance.h"
# include "scene/resources/material/spatial_material.h"
# include "scene/resources/mesh/surface_tool.h"
# include "modules/modules_enabled.gen.h" // For csg.
# ifdef MODULE_CSG_ENABLED
# include "modules/csg/csg_shape.h"
# endif
2024-05-18 17:57:27 +02:00
bool MergingTool : : wrapped_split_by_surface ( const MeshInstance & p_source_mi , Vector < Variant > p_destination_mesh_instances , Mesh : : StorageMode p_storage_mode ) {
ERR_FAIL_COND_V_MSG ( ! p_source_mi . is_inside_tree ( ) , false , " Source MeshInstance must be inside the SceneTree. " ) ;
ERR_FAIL_COND_V_MSG ( ! p_source_mi . get_mesh ( ) . is_valid ( ) , false , " Source MeshInstance must have a valid mesh to split. " ) ;
// For simplicity we are requiring that the destination MeshInstances have the same parent
// as the source. This means we can use identical transforms.
Node * parent = p_source_mi . get_parent ( ) ;
ERR_FAIL_NULL_V_MSG ( parent , false , " Source MeshInstance must have a parent node. " ) ;
// Bound function only support variants, so we need to convert to a list of MeshInstances.
Vector < MeshInstance * > mis ;
for ( int n = 0 ; n < p_destination_mesh_instances . size ( ) ; n + + ) {
MeshInstance * mi = Object : : cast_to < MeshInstance > ( p_destination_mesh_instances [ n ] ) ;
ERR_FAIL_NULL_V_MSG ( mi , false , " Can only be split to MeshInstances. " ) ;
ERR_FAIL_COND_V_MSG ( mi = = & p_source_mi , false , " Source MeshInstance cannot be a destination. " ) ;
ERR_FAIL_COND_V_MSG ( mi - > get_parent ( ) ! = parent , false , " Destination MeshInstances must be siblings of the source MeshInstance. " ) ;
mis . push_back ( mi ) ;
}
ERR_FAIL_COND_V_MSG ( mis . size ( ) ! = p_source_mi . get_mesh ( ) - > get_surface_count ( ) , false , " Number of source surfaces and number of destination MeshInstances must match. " ) ;
// Go through each surface, and fill the relevant mesh instance.
const Mesh * source_mesh = p_source_mi . get_mesh ( ) . ptr ( ) ;
DEV_ASSERT ( source_mesh ) ;
ERR_FAIL_COND_V_MSG ( source_mesh - > get_surface_count ( ) < = 1 , false , " Source MeshInstance must contain multiple surfaces. " ) ;
for ( int s = 0 ; s < source_mesh - > get_surface_count ( ) ; s + + ) {
MeshInstance & dest_mi = * mis [ s ] ;
if ( split_surface_to_mesh_instance ( p_source_mi , s , dest_mi ) ) {
// Change storage mode if required.
# ifdef TOOLS_ENABLED
Ref < Mesh > rmesh = dest_mi . get_mesh ( ) ;
if ( rmesh . is_valid ( ) ) {
_mesh_set_storage_mode ( rmesh . ptr ( ) , p_storage_mode ) ;
}
# endif
}
}
return true ;
}
bool MergingTool : : wrapped_merge_meshes ( MeshInstance & r_dest_mi , Vector < Variant > p_list , bool p_use_global_space , bool p_check_compatibility , bool p_shadows_only , Mesh : : StorageMode p_storage_mode ) {
// Bound function only support variants, so we need to convert to a list of MeshInstances.
Vector < MeshInstance * > mis ;
for ( int n = 0 ; n < p_list . size ( ) ; n + + ) {
MeshInstance * mi = Object : : cast_to < MeshInstance > ( p_list [ n ] ) ;
if ( mi ) {
ERR_FAIL_COND_V_MSG ( mi = = & r_dest_mi , false , " Destination MeshInstance cannot be a source. " ) ;
mis . push_back ( mi ) ;
} else {
ERR_PRINT ( " Only MeshInstances can be merged. " ) ;
}
}
ERR_FAIL_COND_V_MSG ( ! mis . size ( ) , false , " Array contains no MeshInstances " ) ;
bool result ;
if ( p_shadows_only ) {
result = merge_shadow_meshes ( r_dest_mi , mis , p_use_global_space , p_check_compatibility ) ;
} else {
result = merge_meshes ( r_dest_mi , mis , p_use_global_space , p_check_compatibility ) ;
}
// Change storage mode if required.
if ( result ) {
# ifdef TOOLS_ENABLED
Ref < Mesh > rmesh = r_dest_mi . get_mesh ( ) ;
if ( rmesh . is_valid ( ) ) {
_mesh_set_storage_mode ( rmesh . ptr ( ) , p_storage_mode ) ;
}
# endif
}
return result ;
}
2024-02-08 21:00:07 +01:00
bool MergingTool : : _is_material_opaque ( const Ref < Material > & p_mat ) {
if ( p_mat . is_null ( ) ) {
return true ;
}
Ref < SpatialMaterial > material = p_mat ;
if ( material . is_null ( ) ) {
// Shaders not yet supported.
return false ;
}
if ( material - > get_feature ( SpatialMaterial : : FEATURE_TRANSPARENT ) ) {
return false ;
}
// Not sure if this can only occur with FEATURE_TRANSPARENT?
if ( material - > get_flag ( SpatialMaterial : : FLAG_USE_ALPHA_SCISSOR ) ) {
return false ;
}
// Only supporting default cull mode for now.
if ( material - > get_cull_mode ( ) ! = SpatialMaterial : : CULL_BACK ) {
return false ;
}
return true ;
}
bool MergingTool : : _is_shadow_mergeable ( const MeshInstance & p_mi ) {
if ( p_mi . get_cast_shadows_setting ( ) = = GeometryInstance : : ShadowCastingSetting : : SHADOW_CASTING_SETTING_OFF ) {
return false ;
}
if ( ! _is_material_opaque ( p_mi . get_material_overlay ( ) ) ) {
return false ;
}
if ( ! _is_material_opaque ( p_mi . get_material_override ( ) ) ) {
return false ;
}
int num_surfaces = p_mi . get_mesh ( ) - > get_surface_count ( ) ;
for ( int n = 0 ; n < num_surfaces ; n + + ) {
if ( ! _is_material_opaque ( p_mi . get_active_material ( n ) ) ) {
return false ;
}
}
return true ;
}
bool MergingTool : : is_shadow_mergeable_with ( const MeshInstance & p_mi , const MeshInstance & p_other ) {
// Various settings that must match.
if ( ! _is_mergeable_with_common ( p_mi , p_other ) ) {
return false ;
}
if ( ! _is_shadow_mergeable ( p_mi ) | | ! _is_shadow_mergeable ( p_other ) ) {
return false ;
}
return true ;
}
bool MergingTool : : _is_mergeable_with_common ( const MeshInstance & p_mi , const MeshInstance & p_other ) {
if ( ! p_mi . get_mesh ( ) . is_valid ( ) | | ! p_other . get_mesh ( ) . is_valid ( ) ) {
return false ;
}
if ( ! p_mi . is_merging_allowed ( ) | | ! p_other . is_merging_allowed ( ) ) {
return false ;
}
if ( p_mi . get_cast_shadows_setting ( ) ! = p_other . get_cast_shadows_setting ( ) ) {
return false ;
}
if ( p_mi . is_visible ( ) ! = p_other . is_visible ( ) ) {
return false ;
}
if ( p_mi . is_visible_in_tree ( ) ! = p_other . is_visible_in_tree ( ) ) {
return false ;
}
if ( p_mi . get_layer_mask ( ) ! = p_other . get_layer_mask ( ) ) {
return false ;
}
if ( p_mi . get_portal_mode ( ) ! = p_other . get_portal_mode ( ) ) {
return false ;
}
if ( p_mi . get_include_in_bound ( ) ! = p_other . get_include_in_bound ( ) ) {
return false ;
}
if ( p_mi . get_portal_autoplace_priority ( ) ! = p_other . get_portal_autoplace_priority ( ) ) {
return false ;
}
if ( p_mi . get_extra_cull_margin ( ) ! = p_other . get_extra_cull_margin ( ) ) {
return false ;
}
return true ;
}
bool MergingTool : : is_mergeable_with ( const MeshInstance & p_mi , const MeshInstance & p_other , bool p_check_surface_material_match ) {
if ( ! _is_mergeable_with_common ( p_mi , p_other ) ) {
return false ;
}
// Various settings that must match.
if ( p_mi . is_visible ( ) ! = p_other . is_visible ( ) ) {
return false ;
}
if ( p_mi . get_material_overlay ( ) ! = p_other . get_material_overlay ( ) ) {
return false ;
}
if ( p_mi . get_material_override ( ) ! = p_other . get_material_override ( ) ) {
return false ;
}
2024-07-15 19:15:47 +02:00
2024-02-08 21:00:07 +01:00
if ( p_mi . get_flag ( GeometryInstance : : FLAG_USE_BAKED_LIGHT ) ! = p_other . get_flag ( GeometryInstance : : FLAG_USE_BAKED_LIGHT ) ) {
return false ;
}
if ( p_mi . get_generate_lightmap ( ) ! = p_other . get_generate_lightmap ( ) ) {
return false ;
}
if ( p_mi . get_lightmap_scale ( ) ! = p_other . get_lightmap_scale ( ) ) {
return false ;
}
2024-07-15 19:15:47 +02:00
2024-02-08 21:00:07 +01:00
if ( p_check_surface_material_match ) {
Ref < Mesh > rmesh_a = p_mi . get_mesh ( ) ;
Ref < Mesh > rmesh_b = p_other . get_mesh ( ) ;
int num_surfaces = rmesh_a - > get_surface_count ( ) ;
if ( num_surfaces ! = rmesh_b - > get_surface_count ( ) ) {
return false ;
}
for ( int n = 0 ; n < num_surfaces ; n + + ) {
// Materials must match.
if ( p_mi . get_active_material ( n ) ! = p_other . get_active_material ( n ) ) {
return false ;
}
// Formats must match.
uint32_t format_a = rmesh_a - > surface_get_format ( n ) ;
uint32_t format_b = rmesh_b - > surface_get_format ( n ) ;
if ( format_a ! = format_b ) {
return false ;
}
}
}
// NOTE : These three commented out sections below are more conservative
// checks for whether to allow mesh merging. I am not absolutely sure a priori
// how conservative we need to be, so we can further enable this if testing
// shows they are required.
// if (get_surface_material_count() != p_other.get_surface_material_count()) {
// return false;
// }
// for (int n = 0; n < get_surface_material_count(); n++) {
// if (get_surface_material(n) != p_other.get_surface_material(n)) {
// return false;
// }
// }
// test only allow identical meshes
// if (get_mesh() != p_other.get_mesh()) {
// return false;
// }
return true ;
}
void MergingTool : : split_mesh_instance_by_locality ( MeshInstance & r_mi , const AABB & p_bound , uint32_t p_splits_horz , uint32_t p_splits_vert , uint32_t p_min_split_poly_count ) {
Ref < Mesh > rmesh = r_mi . get_mesh ( ) ;
if ( ! rmesh . is_valid ( ) ) {
return ;
}
// Need a parent to attach results to.
if ( ! r_mi . get_parent ( ) ) {
return ;
}
Vector3 cell_size = p_bound . size ;
cell_size . x / = p_splits_horz ;
cell_size . y / = p_splits_vert ;
cell_size . z / = p_splits_horz ;
DEV_ASSERT ( p_splits_horz ) ;
DEV_ASSERT ( p_splits_vert ) ;
int splits_horz_minus_one = p_splits_horz - 1 ;
int splits_vert_minus_one = p_splits_vert - 1 ;
// This is to prevent a warning as error in release builds, as this is only used
// for DEV_ASSERT
# ifdef DEV_ENABLED
uint32_t total_zones = p_splits_horz * p_splits_horz * p_splits_vert ;
# endif
AABB aabb ;
aabb . size = cell_size ;
ERR_FAIL_COND ( ! r_mi . is_inside_tree ( ) ) ;
Transform xform = r_mi . get_global_transform ( ) ;
SurfaceTool st_main ;
for ( int s = 0 ; s < rmesh - > get_surface_count ( ) ; s + + ) {
st_main . create_from ( rmesh , s ) ;
uint32_t tri_count = st_main . get_num_draw_vertices ( ) / 3 ;
// Bug .. we want to keep this surface in this case! and not delete the whole mesh instance?
// at the moment this ASSUMES there is only one surface.
if ( tri_count < p_min_split_poly_count ) {
continue ;
}
// Input for bounds routine should be deindexed.
st_main . deindex ( ) ;
// Assign each triangle to a split zone.
uint32_t num_tris = st_main . vertex_array . size ( ) / 3 ;
Vector3 v [ 3 ] ;
const SurfaceTool : : Vertex * input = st_main . vertex_array . ptr ( ) ;
LocalVector < uint32_t > tri_ids ;
tri_ids . resize ( num_tris ) ;
for ( uint32_t t = 0 ; t < num_tris ; t + + ) {
// Split in world space.
Vector3 center ;
for ( int c = 0 ; c < 3 ; c + + ) {
v [ c ] = input - > vertex ;
input + + ;
v [ c ] = xform . xform ( v [ c ] ) ;
center + = v [ c ] ;
}
center / = 3 ;
// Get relative to bound.
center - = p_bound . position ;
// Find the x y z .
center / = cell_size ;
int x = center . x ;
int y = center . y ;
int z = center . z ;
x = CLAMP ( x , 0 , splits_horz_minus_one ) ;
y = CLAMP ( y , 0 , splits_vert_minus_one ) ;
z = CLAMP ( z , 0 , splits_horz_minus_one ) ;
uint32_t id = ( x + ( z * p_splits_horz ) + ( y * p_splits_horz * p_splits_vert ) ) ;
tri_ids [ t ] = id ;
DEV_ASSERT ( id < total_zones ) ;
}
for ( uint32_t x = 0 ; x < p_splits_horz ; x + + ) {
for ( uint32_t y = 0 ; y < p_splits_vert ; y + + ) {
for ( uint32_t z = 0 ; z < p_splits_horz ; z + + ) {
uint32_t id = ( x + ( z * p_splits_horz ) + ( y * p_splits_horz * p_splits_vert ) ) ;
_split_mesh_instance_by_locality ( st_main , r_mi , tri_ids , id , s , x , y , z ) ;
}
}
}
} // for s
}
void MergingTool : : _split_mesh_instance_by_locality ( const SurfaceTool & p_st_main , const MeshInstance & p_source_mi , const LocalVector < uint32_t > & p_tri_ids , uint32_t p_local_id , uint32_t p_surface_id , uint32_t p_x , uint32_t p_y , uint32_t p_z ) {
SurfaceTool st ;
int num_inds = st . create_from_subset ( p_st_main , p_tri_ids , p_local_id ) ;
// This could be quite common, bounds with no triangles within.
if ( ! num_inds ) {
return ;
}
Node * parent = p_source_mi . get_parent ( ) ;
DEV_ASSERT ( parent ) ;
// Create a mesh instance to hold this "zone".
MeshInstance * sib = memnew ( MeshInstance ) ;
parent - > add_child ( sib ) ;
sib - > set_owner ( p_source_mi . get_owner ( ) ) ;
String new_name = String ( p_source_mi . get_name ( ) ) ;
if ( p_surface_id ) {
new_name + = " _surf_ " + itos ( p_surface_id ) ;
}
new_name + = " split ( " + itos ( p_x ) + " , " + itos ( p_y ) + " , " + itos ( p_z ) + " ) " ;
sib - > set_name ( new_name ) ;
# ifdef TOOLS_ENABLED
#if 0
_merge_log ( " _split_mesh_instance_by_locality " + itos ( num_inds ) + " inds : " + new_name ) ;
# endif
# endif
Ref < ArrayMesh > am ;
am . instance ( ) ;
Array arr = st . commit_to_arrays ( ) ;
am - > add_surface_from_arrays ( Mesh : : PRIMITIVE_TRIANGLES , arr , Array ( ) , Mesh : : ARRAY_COMPRESS_DEFAULT ) ;
// Set all the surfaces on the mesh.
sib - > set_mesh ( am ) ;
_copy_mesh_instance_settings ( p_source_mi , * sib , true , true ) ;
}
bool MergingTool : : clean_mesh_instance ( MeshInstance & p_mi ) {
Ref < Mesh > rmesh = p_mi . get_mesh ( ) ;
ERR_FAIL_COND_V ( ! rmesh . is_valid ( ) , false ) ;
ERR_FAIL_COND_V ( ! p_mi . is_inside_tree ( ) , false ) ;
Transform tr = p_mi . get_global_transform ( ) ;
String name = p_mi . get_name ( ) ;
bool data_changed = false ;
Ref < ArrayMesh > am ;
am . instance ( ) ;
int inds_removed = 0 ;
for ( int s = 0 ; s < rmesh - > get_surface_count ( ) ; s + + ) {
inds_removed + = _clean_mesh_surface ( name , tr , rmesh , s , am ) ;
}
if ( inds_removed ) {
_merge_log ( " cleaning MeshInstance \" " + p_mi . get_name ( ) + " \" removed " + itos ( inds_removed ) + " indices. " , 2 ) ;
p_mi . set_mesh ( am ) ;
}
return data_changed ;
}
int MergingTool : : _clean_mesh_surface ( const String & p_source_name , const Transform & p_xform , Ref < Mesh > & p_rmesh , int p_surface_id , Ref < ArrayMesh > r_dest_mesh ) {
Array arrays = p_rmesh - > surface_get_arrays ( p_surface_id ) ;
LocalVector < Vector3 > verts = PoolVector < Vector3 > ( arrays [ RS : : ARRAY_VERTEX ] ) ;
if ( ! verts . size ( ) ) {
// Early out if there are no vertices, no point in doing anything else.
return 0 ;
}
LocalVector < int > indices = PoolVector < int > ( arrays [ RS : : ARRAY_INDEX ] ) ;
// Transform verts to world space.
for ( uint32_t n = 0 ; n < verts . size ( ) ; n + + ) {
verts [ n ] = p_xform . xform ( verts [ n ] ) ;
}
// Special case, if no indices, create some.
unsigned int num_indices_before = indices . size ( ) ;
if ( ! _ensure_indices_valid ( indices , verts ) ) {
# ifdef TOOLS_ENABLED
_merge_log ( " \t ignoring INVALID TRIANGLES (duplicate indices or zero area triangle) detected in " + p_source_name + " , num inds before / after " + itos ( num_indices_before ) + " / " + itos ( indices . size ( ) ) ) ;
# endif
// Save the modified index array.
arrays [ RS : : ARRAY_INDEX ] = PoolVector < int > ( indices ) ;
// Note we aren't removing the unused verts here, to save hassle, but hopefully there won't be too many.
r_dest_mesh - > add_surface_from_arrays ( p_rmesh - > surface_get_primitive_type ( p_surface_id ) , arrays ) ;
r_dest_mesh - > surface_set_material ( p_surface_id , p_rmesh - > surface_get_material ( p_surface_id ) ) ;
// Returns true if data changed.
if ( indices . size ( ) > = num_indices_before ) {
ERR_PRINT_ONCE ( " Indices after cleaning is higher than before. " ) ;
return 1 ;
}
return num_indices_before - indices . size ( ) ;
}
// Still add the surface, as a later one may be modified.
r_dest_mesh - > add_surface_from_arrays ( p_rmesh - > surface_get_primitive_type ( p_surface_id ) , arrays ) ;
r_dest_mesh - > surface_set_material ( p_surface_id , p_rmesh - > surface_get_material ( p_surface_id ) ) ;
return 0 ;
}
bool MergingTool : : _ensure_indices_valid ( LocalVector < int > & r_indices , const PoolVector < Vector3 > & p_verts ) {
// No indices? create some.
if ( ! r_indices . size ( ) ) {
# ifdef TOOLS_ENABLED
_merge_log ( " \t \t \t \t indices are blank, creating... " ) ;
# endif
// Indices are blank!! Let's create some, assuming the mesh is using triangles.
r_indices . resize ( p_verts . size ( ) ) ;
// This is assuming each triangle vertex is unique.
for ( unsigned int n = 0 ; n < r_indices . size ( ) ; n + + ) {
r_indices [ n ] = n ;
}
}
if ( ! _check_for_valid_indices ( r_indices , p_verts , nullptr ) ) {
LocalVector < int > new_inds ;
_check_for_valid_indices ( r_indices , p_verts , & new_inds ) ;
// Copy the new indices.
r_indices = new_inds ;
return false ;
}
return true ;
}
// Check for invalid tris, or make a list of the valid triangles, depending on whether r_inds is set.
bool MergingTool : : _check_for_valid_indices ( const LocalVector < int > & p_inds , const PoolVector < Vector3 > & p_verts , LocalVector < int > * r_inds ) {
int nTris = p_inds . size ( ) ;
nTris / = 3 ;
int indCount = 0 ;
for ( int t = 0 ; t < nTris ; t + + ) {
int i0 = p_inds [ indCount + + ] ;
int i1 = p_inds [ indCount + + ] ;
int i2 = p_inds [ indCount + + ] ;
bool ok = true ;
// If the indices are the same, the triangle is invalid.
if ( i0 = = i1 ) {
ok = false ;
}
if ( i1 = = i2 ) {
ok = false ;
}
if ( i0 = = i2 ) {
ok = false ;
}
// Check positions.
if ( ok ) {
// Vertex positions.
const Vector3 & p0 = p_verts [ i0 ] ;
const Vector3 & p1 = p_verts [ i1 ] ;
const Vector3 & p2 = p_verts [ i2 ] ;
// If the area is zero, the triangle is invalid (and will crash xatlas if we use it).
if ( _triangle_is_degenerate ( p0 , p1 , p2 , 0.00001 ) ) {
# ifdef TOOLS_ENABLED
_merge_log ( " \t \t detected zero area triangle, ignoring " ) ;
# endif
ok = false ;
}
}
if ( ok ) {
// If the triangle is ok, we will output it if we are outputting.
if ( r_inds ) {
r_inds - > push_back ( i0 ) ;
r_inds - > push_back ( i1 ) ;
r_inds - > push_back ( i2 ) ;
}
} else {
// If triangle not ok, return failed check if we are not outputting.
if ( ! r_inds ) {
return false ;
}
}
}
return true ;
}
bool MergingTool : : _triangle_is_degenerate ( const Vector3 & p_a , const Vector3 & p_b , const Vector3 & p_c , real_t p_epsilon ) {
// Not interested in the actual area, but numerical stability.
Vector3 edge1 = p_b - p_a ;
Vector3 edge2 = p_c - p_a ;
// For numerical stability keep these values reasonably high.
edge1 * = 1024.0 ;
edge2 * = 1024.0 ;
Vector3 vec = edge1 . cross ( edge2 ) ;
real_t sl = vec . length_squared ( ) ;
if ( sl < = p_epsilon ) {
return true ;
}
return false ;
}
// If p_check_compatibility is set to false you MUST have performed a prior check using
// is_shadow_mergeable_with, otherwise you could get mismatching surface formats leading to graphical errors etc.
bool MergingTool : : merge_shadow_meshes ( MeshInstance & r_dest_mi , Vector < MeshInstance * > p_list , bool p_use_global_space , bool p_check_compatibility ) {
ERR_FAIL_COND_V ( p_list . size ( ) < 1 , false ) ;
// Use the first mesh instance to get common data like number of surfaces.
const MeshInstance * first = p_list [ 0 ] ;
// Mesh compatibility checking. This is relatively expensive, so if done already (e.g. in Room system)
// this step can be avoided.
LocalVector < bool > compat_list ;
if ( p_check_compatibility ) {
compat_list . resize ( p_list . size ( ) ) ;
for ( int n = 0 ; n < p_list . size ( ) ; n + + ) {
compat_list [ n ] = false ;
}
compat_list [ 0 ] = true ;
for ( uint32_t n = 1 ; n < compat_list . size ( ) ; n + + ) {
compat_list [ n ] = is_shadow_mergeable_with ( * first , * p_list [ n ] ) ;
if ( compat_list [ n ] = = false ) {
WARN_PRINT ( " MeshInstance " + p_list [ n ] - > get_name ( ) + " is incompatible for shadow merging with " + first - > get_name ( ) + " , ignoring. " ) ;
}
}
}
Ref < ArrayMesh > am ;
am . instance ( ) ;
// If we want a local space result, we need the world space transform of this MeshInstance
// available to back transform verts from world space.
Transform dest_tr_inv ;
if ( ! p_use_global_space ) {
if ( r_dest_mi . is_inside_tree ( ) ) {
dest_tr_inv = r_dest_mi . get_global_transform ( ) ;
dest_tr_inv . affine_invert ( ) ;
} else {
WARN_PRINT ( " MeshInstance must be inside tree to merge using local space, falling back to global space. " ) ;
}
}
SurfaceTool surface_tool ;
for ( int n = 0 ; n < p_list . size ( ) ; n + + ) {
// Ignore if the mesh is incompatible.
if ( p_check_compatibility & & ( ! compat_list [ n ] ) ) {
continue ;
}
MeshInstance * source_mi = p_list [ n ] ;
Ref < Mesh > rmesh = source_mi - > get_mesh ( ) ;
Transform adjustment_xform = dest_tr_inv * source_mi - > get_global_transform ( ) ;
for ( int s = 0 ; s < rmesh - > get_surface_count ( ) ; s + + ) {
surface_tool . append_from ( rmesh , s , adjustment_xform ) ;
# ifdef TOOLS_ENABLED
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
_merge_log ( " merging from \" " + source_mi - > get_name ( ) + " \" surf " + itos ( s ) + " to \" " + r_dest_mi . get_name ( ) + " \" " ) ;
MergingTool : : append_editor_description ( & r_dest_mi , " merging from " , source_mi ) ;
}
# endif
}
} // for n through source meshes
// We are only interested in position data for shadow proxy meshes, and indices if present.
surface_tool . _mask_format_flags ( Mesh : : ARRAY_FORMAT_VERTEX | Mesh : : ARRAY_FORMAT_INDEX ) ;
Array arr = surface_tool . commit_to_arrays ( ) ;
am - > add_surface_from_arrays ( Mesh : : PRIMITIVE_TRIANGLES , arr , Array ( ) , Mesh : : ARRAY_COMPRESS_DEFAULT ) ;
// Set all the surfaces on the mesh.
r_dest_mi . set_mesh ( am ) ;
_copy_geometry_instance_settings ( * first , r_dest_mi , false ) ;
r_dest_mi . set_cast_shadows_setting ( GeometryInstance : : ShadowCastingSetting : : SHADOW_CASTING_SETTING_SHADOWS_ONLY ) ;
// Don't want these set, they get set by the _copy_geometry_instance_settings call.
r_dest_mi . set_material_overlay ( Ref < Material > ( ) ) ;
r_dest_mi . set_material_override ( Ref < Material > ( ) ) ;
return true ;
}
void MergingTool : : _mesh_set_storage_mode ( Mesh * p_mesh , Mesh : : StorageMode p_mode ) {
# ifdef TOOLS_ENABLED
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
ERR_FAIL_NULL ( p_mesh ) ;
p_mesh - > set_storage_mode ( p_mode ) ;
}
# endif
}
bool MergingTool : : split_surface_to_mesh_instance ( const MeshInstance & p_source_mi , int p_surface_id , MeshInstance & r_mi ) {
SurfaceTool surface_tool ;
Ref < Mesh > rmesh = p_source_mi . get_mesh ( ) ;
if ( ! rmesh . is_valid ( ) ) {
return false ;
}
// Hard coded to local space for now.
surface_tool . append_from ( rmesh , p_surface_id , Transform ( ) ) ;
Ref < ArrayMesh > am ;
am . instance ( ) ;
_mesh_set_storage_mode ( am . ptr ( ) , Mesh : : STORAGE_MODE_CPU ) ;
Array arr = surface_tool . commit_to_arrays ( ) ;
am - > add_surface_from_arrays ( Mesh : : PRIMITIVE_TRIANGLES , arr , Array ( ) , Mesh : : ARRAY_COMPRESS_DEFAULT ) ;
r_mi . set_mesh ( am ) ;
// Set the material on the new mesh instance.
_set_rmesh_material ( r_mi , r_mi . get_mesh ( ) , 0 , p_source_mi . get_active_material ( p_surface_id ) ) ;
// Set some properties to match the source mesh.
// As they are guaranteed siblings, the transform can be identical.
_copy_geometry_instance_settings ( p_source_mi , r_mi , true ) ;
return true ;
}
# ifdef TOOLS_ENABLED
void MergingTool : : append_editor_description ( Node * p_node , String p_string , Node * p_node_named ) {
ERR_FAIL_NULL ( p_node ) ;
String existing = p_node - > get_editor_description ( ) ;
if ( existing . size ( ) > 512 ) {
// limit the max length of the description to prevent things getting ridiculous
return ;
}
String add ;
if ( existing . size ( ) ) {
add + = " \n " ;
}
add + = p_string ;
if ( p_node_named ) {
add + = " \" " + p_node_named - > get_name ( ) + " \" " ;
}
p_node - > set_editor_description ( existing + add ) ;
}
# endif
# ifdef DEV_ENABLED
void MergingTool : : debug_branch ( Node * p_node , const char * p_title , int p_depth ) {
if ( OS : : get_singleton ( ) - > is_stdout_verbose ( ) ) {
if ( p_title ) {
_merge_log ( p_title ) ;
}
if ( p_node - > is_queued_for_deletion ( ) ) {
return ;
}
String s ;
for ( int n = 0 ; n < p_depth ; n + + ) {
s + = " \t " ;
}
s + = " \" " + p_node - > get_name ( ) + " \" \t " ;
String filename = p_node - > get_filename ( ) ;
if ( filename . size ( ) ) {
s + = " [filename " + p_node - > get_filename ( ) + " ] " ;
}
s + = " owner ( " ;
if ( p_node - > get_owner ( ) ) {
s + = p_node - > get_owner ( ) - > get_name ( ) ;
} else {
s + = " NULL " ;
}
s + = " ) " ;
_merge_log ( s ) ;
for ( int n = 0 ; n < p_node - > get_child_count ( ) ; n + + ) {
debug_branch ( p_node - > get_child ( n ) , nullptr , p_depth + 1 ) ;
}
} // if verbose output
}
# endif
void MergingTool : : debug_mesh_instance ( const MeshInstance & p_mi ) {
# ifdef DEV_ENABLED
_merge_log ( " debug " + p_mi . get_name ( ) ) ;
Ref < Mesh > rmesh = p_mi . get_mesh ( ) ;
if ( ! rmesh . is_valid ( ) ) {
_merge_log ( " \t invalid mesh " ) ;
return ;
}
for ( int s = 0 ; s < rmesh - > get_surface_count ( ) ; s + + ) {
_merge_log ( " \t surf " + itos ( s ) + " inds " + itos ( rmesh - > surface_get_array_index_len ( s ) ) + " verts " + itos ( rmesh - > surface_get_array_len ( s ) ) ) ;
}
# endif
}
bool MergingTool : : join_mesh_surface ( const MeshInstance & p_source_mi , uint32_t p_source_surface_id , MeshInstance & r_dest_mi ) {
Ref < Mesh > r_sourcemesh = p_source_mi . get_mesh ( ) ;
ERR_FAIL_COND_V ( ! r_sourcemesh . is_valid ( ) , false ) ;
ERR_FAIL_COND_V ( ( int ) p_source_surface_id > = r_sourcemesh - > get_surface_count ( ) , false ) ;
// Note this can be NULL if the destination mesh instance contains no meshes yet.
// We should deal with this case.
Ref < ArrayMesh > ra_destmesh = r_dest_mi . get_mesh ( ) ;
if ( ! ra_destmesh . is_valid ( ) ) {
ra_destmesh . instance ( ) ;
_mesh_set_storage_mode ( ra_destmesh . ptr ( ) , Mesh : : STORAGE_MODE_CPU ) ;
}
// Relative xform ..
Transform relative_xform = r_dest_mi . get_global_transform ( ) . inverse ( ) * p_source_mi . get_global_transform ( ) ;
SurfaceTool surface_tool ;
surface_tool . append_from ( r_sourcemesh , p_source_surface_id , relative_xform ) ;
int new_surface_id = 0 ;
if ( ra_destmesh . is_valid ( ) ) {
new_surface_id = ra_destmesh - > get_surface_count ( ) ;
r_dest_mi . set_mesh ( surface_tool . commit ( ra_destmesh ) ) ;
} else {
r_dest_mi . set_mesh ( surface_tool . commit ( ) ) ;
}
Ref < Mesh > new_rmesh = r_dest_mi . get_mesh ( ) ;
// If no surface has been added.
ERR_FAIL_COND_V ( new_rmesh - > get_surface_count ( ) < = new_surface_id , false ) ;
// Deal with materials.
_set_rmesh_material ( r_dest_mi , new_rmesh , new_surface_id , p_source_mi . get_active_material ( p_source_surface_id ) ) ;
return true ;
}
// No compat checking, no renaming.
bool MergingTool : : join_meshes ( MeshInstance & r_dest_mi , Vector < MeshInstance * > p_list ) {
if ( p_list . size ( ) < 1 ) {
// Should not happen but just in case...
return false ;
}
// For future use of compatibility check.
LocalVector < MeshInstance * > list = p_list ;
MeshInstance * first = list [ 0 ] ;
// First copy the properties of the first meshinstance.
_copy_mesh_instance_settings ( * first , r_dest_mi , false , false ) ;
for ( unsigned int n = 0 ; n < list . size ( ) ; n + + ) {
MeshInstance * mi = list [ n ] ;
Ref < Mesh > rmesh = mi - > get_mesh ( ) ;
for ( int s = 0 ; s < rmesh - > get_surface_count ( ) ; s + + ) {
if ( MergingTool : : join_mesh_surface ( * mi , s , r_dest_mi ) ) {
# ifdef DEV_ENABLED
_merge_log ( " joining \" " + mi - > get_name ( ) + " \" to \" " + r_dest_mi . get_name ( ) + " \" " ) ;
# endif
# ifdef TOOLS_ENABLED
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
MergingTool : : append_editor_description ( & r_dest_mi , " joined to " , mi ) ;
}
# endif
} else {
# ifdef DEV_ENABLED
_merge_log ( " failed to join \" " + mi - > get_name ( ) + " \" to \" " + r_dest_mi . get_name ( ) + " \" " ) ;
# endif
}
}
}
return true ;
}
// If p_check_compatibility is set to false you MUST have performed a prior check using
// is_mergeable_with, otherwise you could get mismatching surface formats leading to graphical errors etc.
bool MergingTool : : merge_meshes ( MeshInstance & r_dest_mi , Vector < MeshInstance * > p_list , bool p_use_global_space , bool p_check_compatibility ) {
ERR_FAIL_COND_V ( p_list . size ( ) < 1 , false ) ;
// Use the first mesh instance to get common data like number of surfaces.
const MeshInstance * first = p_list [ 0 ] ;
Ref < Mesh > rmesh_first = first - > get_mesh ( ) ;
if ( ! rmesh_first . is_valid ( ) ) {
return false ;
}
int surface_count = rmesh_first - > get_surface_count ( ) ;
if ( surface_count < = 0 ) {
# ifdef TOOLS_ENABLED
_merge_log ( " merge_meshes : " + first - > get_name ( ) + " contains no surfaces, ignoring. " ) ;
# endif
return false ;
}
// Mesh compatibility checking. This is relatively expensive, so if done already (e.g. in Room system)
// this step can be avoided.
LocalVector < bool > compat_list ;
if ( p_check_compatibility ) {
compat_list . resize ( p_list . size ( ) ) ;
for ( int n = 0 ; n < p_list . size ( ) ; n + + ) {
compat_list [ n ] = false ;
}
compat_list [ 0 ] = true ;
for ( uint32_t n = 1 ; n < compat_list . size ( ) ; n + + ) {
compat_list [ n ] = is_mergeable_with ( * first , * p_list [ n ] , true ) ;
if ( compat_list [ n ] = = false ) {
WARN_PRINT ( " MeshInstance " + p_list [ n ] - > get_name ( ) + " is incompatible for merging with " + first - > get_name ( ) + " , ignoring. " ) ;
}
}
}
Ref < ArrayMesh > am ;
am . instance ( ) ;
_mesh_set_storage_mode ( am . ptr ( ) , Mesh : : STORAGE_MODE_CPU ) ;
// If we want a local space result, we need the world space transform of this MeshInstance
// available to back transform verts from world space.
Transform dest_tr_inv ;
if ( ! p_use_global_space ) {
if ( r_dest_mi . is_inside_tree ( ) ) {
dest_tr_inv = r_dest_mi . get_global_transform ( ) ;
dest_tr_inv . affine_invert ( ) ;
} else {
WARN_PRINT ( " MeshInstance must be inside tree to merge using local space, falling back to global space. " ) ;
}
}
for ( int s = 0 ; s < surface_count ; s + + ) {
SurfaceTool surface_tool ;
for ( int n = 0 ; n < p_list . size ( ) ; n + + ) {
// Ignore if the mesh is incompatible.
if ( p_check_compatibility & & ( ! compat_list [ n ] ) ) {
continue ;
}
Ref < Mesh > rmesh = p_list [ n ] - > get_mesh ( ) ;
Transform adjustment_xform = dest_tr_inv * p_list [ n ] - > get_global_transform ( ) ;
surface_tool . append_from ( rmesh , s , adjustment_xform ) ;
# ifdef TOOLS_ENABLED
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
_merge_log ( " merging from \" " + p_list [ n ] - > get_name ( ) + " \" surf " + itos ( s ) + " to \" " + r_dest_mi . get_name ( ) + " \" " ) ;
MergingTool : : append_editor_description ( & r_dest_mi , " merging from " , p_list [ n ] ) ;
}
# endif
}
Array arr = surface_tool . commit_to_arrays ( ) ;
am - > add_surface_from_arrays ( Mesh : : PRIMITIVE_TRIANGLES , arr , Array ( ) , Mesh : : ARRAY_COMPRESS_DEFAULT ) ;
} // for s
// Set all the surfaces on the mesh.
r_dest_mi . set_mesh ( am ) ;
// Set some properties to match the merged meshes.
_copy_mesh_instance_settings ( * first , r_dest_mi , false , true ) ;
return true ;
}
bool MergingTool : : split_csg_surface_to_mesh_instance ( const CSGShape & p_shape , MeshInstance & r_mi , const Ref < ArrayMesh > & p_array_mesh , CSGBrush * p_brush , int p_surface ) {
# ifdef MODULE_CSG_ENABLED
SurfaceTool surface_tool ;
Ref < ArrayMesh > am ;
am . instance ( ) ;
Ref < Mesh > rmesh = p_array_mesh ;
// We are matching the local transforms of the source and destination, as they are always
// siblings for now.
surface_tool . append_from ( rmesh , p_surface , Transform ( ) ) ;
Array arr = surface_tool . commit_to_arrays ( ) ;
am - > add_surface_from_arrays ( Mesh : : PRIMITIVE_TRIANGLES , arr , Array ( ) , Mesh : : ARRAY_COMPRESS_DEFAULT ) ;
r_mi . set_mesh ( am ) ;
// Set the material on the new mesh instance.
if ( p_surface < p_brush - > materials . size ( ) ) {
_set_rmesh_material ( r_mi , r_mi . get_mesh ( ) , 0 , p_brush - > materials [ p_surface ] ) ;
}
// As they are guaranteed siblings, the transform can be identical.
_copy_geometry_instance_settings ( p_shape , r_mi , true ) ;
return true ;
# else
return false ;
# endif
}
void MergingTool : : _copy_mesh_instance_settings ( const MeshInstance & p_source , MeshInstance & r_dest , bool p_copy_transform , bool p_copy_materials ) {
_copy_geometry_instance_settings ( p_source , r_dest , p_copy_transform ) ;
if ( p_copy_materials ) {
// Set merged materials.
Ref < Mesh > rmesh = p_source . get_mesh ( ) ;
if ( rmesh . is_valid ( ) ) {
for ( int n = 0 ; n < rmesh - > get_surface_count ( ) ; n + + ) {
_set_rmesh_material ( r_dest , r_dest . get_mesh ( ) , n , p_source . get_active_material ( n ) ) ;
}
}
}
}
void MergingTool : : _set_rmesh_material ( MeshInstance & r_mi , Ref < Mesh > r_rmesh , int p_surface_id , Ref < Material > p_material ) {
// Here we can either set the material on the rmesh, or on the mesh instance.
// Setting it directly in the rmesh seems more desired by users, but perhaps this could be
// switchable?
r_rmesh - > surface_set_material ( p_surface_id , p_material ) ;
// r_mi.set_surface_material(p_surface_id, p_material);
}
void MergingTool : : _copy_geometry_instance_settings ( const GeometryInstance & p_source , MeshInstance & r_dest , bool p_copy_transform ) {
// Set some properties to match the source mesh.
r_dest . set_material_overlay ( p_source . get_material_overlay ( ) ) ;
r_dest . set_material_override ( p_source . get_material_override ( ) ) ;
r_dest . set_cast_shadows_setting ( p_source . get_cast_shadows_setting ( ) ) ;
2024-07-15 19:15:47 +02:00
r_dest . set_flag ( GeometryInstance : : FLAG_USE_BAKED_LIGHT , p_source . get_flag ( GeometryInstance : : FLAG_USE_BAKED_LIGHT ) ) ;
2024-02-08 21:00:07 +01:00
r_dest . set_portal_mode ( p_source . get_portal_mode ( ) ) ;
r_dest . set_include_in_bound ( p_source . get_include_in_bound ( ) ) ;
r_dest . set_portal_autoplace_priority ( p_source . get_portal_autoplace_priority ( ) ) ;
r_dest . set_extra_cull_margin ( p_source . get_extra_cull_margin ( ) ) ;
// As they are guaranteed siblings, the transform can be identical.
if ( p_copy_transform ) {
r_dest . set_transform ( p_source . get_transform ( ) ) ;
}
// Preserve visibility.
// If they are siblings, they can share the visible flag, if not, we need to take into account visibility in tree.
if ( p_source . get_parent ( ) = = r_dest . get_parent ( ) ) {
r_dest . set_visible ( p_source . is_visible ( ) ) ;
} else {
r_dest . set_visible ( p_source . is_visible_in_tree ( ) ) ;
}
}
void MergingTool : : _merge_log ( String p_string , int p_priority ) {
# ifdef TOOLS_ENABLED
switch ( p_priority ) {
case 0 : {
print_verbose ( p_string ) ;
} break ;
case 2 : {
print_line ( p_string ) ;
} break ;
default : {
# ifdef DEV_ENABLED
print_line ( p_string ) ;
# else
print_verbose ( p_string ) ;
# endif
} break ;
}
# endif
}
void MergingTool : : _set_owner_logged ( Node * p_node , Node * p_owner ) {
DEV_ASSERT ( p_node ! = p_owner ) ;
# ifdef DEV_ENABLED
#if 0
// Check whether the Node::set_owner() routine will allow this .. the owner must be in the tree above
// for the call to work.
bool valid = false ;
Node * probe = p_node - > get_parent ( ) ;
while ( probe ) {
if ( probe = = p_owner ) {
valid = true ;
break ;
}
probe = probe - > get_parent ( ) ;
}
DEV_ASSERT ( valid ) ;
# endif
# endif
if ( p_node - > get_owner ( ) = = p_owner ) {
return ;
}
# ifdef DEV_ENABLED
#if 0
String string = " \t changing owner of \" " + p_node - > get_name ( ) + " \" from " ;
if ( p_node - > get_owner ( ) ) {
string + = p_node - > get_owner ( ) - > get_name ( ) ;
} else {
string + = " NULL " ;
}
string + = " to " ;
if ( p_owner ) {
string + = p_owner - > get_name ( ) ;
} else {
string + = " NULL " ;
}
_merge_log ( string ) ;
# endif
# endif
p_node - > set_owner ( p_owner ) ;
DEV_ASSERT ( p_node - > get_owner ( ) ) ;
}
bool MergingTool : : _node_has_valid_children ( Node * p_node ) {
for ( int n = 0 ; n < p_node - > get_child_count ( ) ; n + + ) {
if ( ! p_node - > get_child ( n ) - > is_queued_for_deletion ( ) ) {
return true ;
}
}
return false ;
}
void MergingTool : : _invalidate_owner_recursive ( Node * p_node , Node * p_old_owner , Node * p_new_owner ) {
if ( p_node - > get_owner ( ) = = p_old_owner ) {
_set_owner_logged ( p_node , p_new_owner ) ;
}
for ( int n = 0 ; n < p_node - > get_child_count ( ) ; n + + ) {
_invalidate_owner_recursive ( p_node - > get_child ( n ) , p_old_owner , p_new_owner ) ;
}
}
void MergingTool : : _reparent ( Node * p_branch , Node * p_new_parent , Node * p_new_owner ) {
# ifdef PANDEMONIUM_MERGING_VERBOSE
if ( p_branch - > get_parent ( ) ) {
_merge_log ( " reparenting child " + p_branch - > get_name ( ) + " from parent " + p_branch - > get_parent ( ) - > get_name ( ) + " to parent " + p_new_parent - > get_name ( ) ) ;
} else {
_merge_log ( " reparenting child " + p_branch - > get_name ( ) + " from parent NULL to parent " + p_new_parent - > get_name ( ) ) ;
}
# endif
// noop
if ( p_branch - > get_parent ( ) = = p_new_parent ) {
return ;
}
// Detach (if attached).
if ( p_branch - > get_parent ( ) ) {
p_branch - > get_parent ( ) - > remove_child ( p_branch ) ;
}
// Must be added to the scene BEFORE setting the new owner
// otherwise the set_owner() calls will fail to find the new owner.
p_new_parent - > add_child ( p_branch ) ;
_reparent_subscene_send_new_owner ( p_branch , p_new_owner ) ;
}
void MergingTool : : _reparent_subscene_send_new_owner ( Node * p_node , Node * p_new_owner ) {
bool owner_found = false ;
// Is the current owner in the subscene? If so keep it, else change.
Node * current_owner = p_node - > get_owner ( ) ;
if ( current_owner ) {
Node * probe = p_node ;
while ( probe ) {
if ( probe = = current_owner ) {
// Owner already exists in the subscene, no need to change.
owner_found = true ;
break ;
}
probe = probe - > get_parent ( ) ;
}
} // if there was a current owner
if ( ! owner_found ) {
_set_owner_logged ( p_node , p_new_owner ) ;
}
for ( int n = 0 ; n < p_node - > get_child_count ( ) ; n + + ) {
_reparent_subscene_send_new_owner ( p_node - > get_child ( n ) , p_new_owner ) ;
}
}