mirror of
https://github.com/godotengine/godot.git
synced 2024-11-21 19:42:43 +00:00
LOD: Remove "Raycast Normals" and associated "Normal Split Angle" settings
"Raycast Normals" was introduced in 4.4 dev and defaulted to "false". The limited testing results at the time suggested that raycasting generally reduces normal quality compared to native simplifier results, at the same time increasing vertex memory and import time. To play it safe, we introduced a setting that defaulted to false, with the goal of removing it later in 4.4 development cycle if no regressions are noticed. Since we already had three dev snapshots and no reports, this change removes the setting and associated code. "Normal Split Angle" was only used when raycast normals were enabled; this change removes it from the settings, but keeps it in the script binding for compatibility. Existing meshes import exactly the same after this change (unless they chose to override raycasting which would be surprising). split_normals helper was only used in this code path and is also removed for simplicity; it is unlikely that this code will be useful as is, as it can only regenerate normals without fixing tangents or updating positions.
This commit is contained in:
parent
a3080477ac
commit
494fe2fe21
@ -49,7 +49,8 @@
|
||||
<param index="2" name="bone_transform_array" type="Array" />
|
||||
<description>
|
||||
Generates all lods for this ImporterMesh.
|
||||
[param normal_merge_angle] and [param normal_split_angle] are in degrees and used in the same way as the importer settings in [code]lods[/code]. As a good default, use 25 and 60 respectively.
|
||||
[param normal_merge_angle] is in degrees and used in the same way as the importer settings in [code]lods[/code].
|
||||
[param normal_split_angle] is not used and only remains for compatibility with older versions of the API.
|
||||
The number of generated lods can be accessed using [method get_surface_lod_count], and each LOD is available in [method get_surface_lod_size] and [method get_surface_lod_indices].
|
||||
[param bone_transform_array] is an [Array] which can be either empty or contain [Transform3D]s which, for each of the mesh's bone IDs, will apply mesh skinning when generating the LOD mesh variations. This is usually used to account for discrepancies in scale between the mesh itself and its skinning data.
|
||||
</description>
|
||||
|
@ -539,7 +539,7 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
|
||||
|
||||
if (p_generate_lods) {
|
||||
// Use normal merge/split angles that match the defaults used for 3D scene importing.
|
||||
mesh->generate_lods(60.0f, 25.0f, {});
|
||||
mesh->generate_lods(60.0f, {});
|
||||
}
|
||||
|
||||
if (p_generate_shadow_mesh) {
|
||||
|
@ -2043,9 +2043,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/shadow_meshes", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lightmap_uv", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lods", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_split_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 25.0f));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_merge_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 60.0f));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "lods/raycast_normals", PROPERTY_HINT_NONE, ""), false));
|
||||
} break;
|
||||
case INTERNAL_IMPORT_CATEGORY_MATERIAL: {
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
|
||||
@ -2474,9 +2472,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
|
||||
//do mesh processing
|
||||
|
||||
bool generate_lods = p_generate_lods;
|
||||
float split_angle = 25.0f;
|
||||
float merge_angle = 60.0f;
|
||||
bool raycast_normals = false;
|
||||
bool create_shadow_meshes = p_create_shadow_meshes;
|
||||
bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS;
|
||||
String save_to_file;
|
||||
@ -2523,18 +2519,10 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh_settings.has("lods/normal_split_angle")) {
|
||||
split_angle = mesh_settings["lods/normal_split_angle"];
|
||||
}
|
||||
|
||||
if (mesh_settings.has("lods/normal_merge_angle")) {
|
||||
merge_angle = mesh_settings["lods/normal_merge_angle"];
|
||||
}
|
||||
|
||||
if (mesh_settings.has("lods/raycast_normals")) {
|
||||
raycast_normals = mesh_settings["lods/raycast_normals"];
|
||||
}
|
||||
|
||||
if (bool(mesh_settings.get("save_to_file/enabled", false))) {
|
||||
save_to_file = mesh_settings.get("save_to_file/path", String());
|
||||
if (!save_to_file.is_resource_file()) {
|
||||
@ -2583,7 +2571,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
|
||||
|
||||
if (generate_lods) {
|
||||
Array skin_pose_transform_array = _get_skinned_pose_transforms(src_mesh_node);
|
||||
src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle, skin_pose_transform_array, raycast_normals);
|
||||
src_mesh_node->get_mesh()->generate_lods(merge_angle, skin_pose_transform_array);
|
||||
}
|
||||
|
||||
if (create_shadow_meshes) {
|
||||
|
@ -33,108 +33,10 @@
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/math/convex_hull.h"
|
||||
#include "core/math/random_pcg.h"
|
||||
#include "core/math/static_raycaster.h"
|
||||
#include "scene/resources/animation_library.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
void ImporterMesh::Surface::split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) {
|
||||
_split_normals(arrays, p_indices, p_normals);
|
||||
|
||||
for (BlendShape &blend_shape : blend_shape_data) {
|
||||
_split_normals(blend_shape.arrays, p_indices, p_normals);
|
||||
}
|
||||
}
|
||||
|
||||
void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) {
|
||||
ERR_FAIL_COND(r_arrays.size() != RS::ARRAY_MAX);
|
||||
|
||||
const PackedVector3Array &vertices = r_arrays[RS::ARRAY_VERTEX];
|
||||
int current_vertex_count = vertices.size();
|
||||
int new_vertex_count = p_indices.size();
|
||||
int final_vertex_count = current_vertex_count + new_vertex_count;
|
||||
const int *indices_ptr = p_indices.ptr();
|
||||
|
||||
for (int i = 0; i < r_arrays.size(); i++) {
|
||||
if (i == RS::ARRAY_INDEX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r_arrays[i].get_type() == Variant::NIL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (r_arrays[i].get_type()) {
|
||||
case Variant::PACKED_VECTOR3_ARRAY: {
|
||||
PackedVector3Array data = r_arrays[i];
|
||||
data.resize(final_vertex_count);
|
||||
Vector3 *data_ptr = data.ptrw();
|
||||
if (i == RS::ARRAY_NORMAL) {
|
||||
const Vector3 *normals_ptr = p_normals.ptr();
|
||||
memcpy(&data_ptr[current_vertex_count], normals_ptr, sizeof(Vector3) * new_vertex_count);
|
||||
} else {
|
||||
for (int j = 0; j < new_vertex_count; j++) {
|
||||
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
|
||||
}
|
||||
}
|
||||
r_arrays[i] = data;
|
||||
} break;
|
||||
case Variant::PACKED_VECTOR2_ARRAY: {
|
||||
PackedVector2Array data = r_arrays[i];
|
||||
data.resize(final_vertex_count);
|
||||
Vector2 *data_ptr = data.ptrw();
|
||||
for (int j = 0; j < new_vertex_count; j++) {
|
||||
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
|
||||
}
|
||||
r_arrays[i] = data;
|
||||
} break;
|
||||
case Variant::PACKED_FLOAT32_ARRAY: {
|
||||
PackedFloat32Array data = r_arrays[i];
|
||||
int elements = data.size() / current_vertex_count;
|
||||
data.resize(final_vertex_count * elements);
|
||||
float *data_ptr = data.ptrw();
|
||||
for (int j = 0; j < new_vertex_count; j++) {
|
||||
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements);
|
||||
}
|
||||
r_arrays[i] = data;
|
||||
} break;
|
||||
case Variant::PACKED_INT32_ARRAY: {
|
||||
PackedInt32Array data = r_arrays[i];
|
||||
int elements = data.size() / current_vertex_count;
|
||||
data.resize(final_vertex_count * elements);
|
||||
int32_t *data_ptr = data.ptrw();
|
||||
for (int j = 0; j < new_vertex_count; j++) {
|
||||
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements);
|
||||
}
|
||||
r_arrays[i] = data;
|
||||
} break;
|
||||
case Variant::PACKED_BYTE_ARRAY: {
|
||||
PackedByteArray data = r_arrays[i];
|
||||
int elements = data.size() / current_vertex_count;
|
||||
data.resize(final_vertex_count * elements);
|
||||
uint8_t *data_ptr = data.ptrw();
|
||||
for (int j = 0; j < new_vertex_count; j++) {
|
||||
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements);
|
||||
}
|
||||
r_arrays[i] = data;
|
||||
} break;
|
||||
case Variant::PACKED_COLOR_ARRAY: {
|
||||
PackedColorArray data = r_arrays[i];
|
||||
data.resize(final_vertex_count);
|
||||
Color *data_ptr = data.ptrw();
|
||||
for (int j = 0; j < new_vertex_count; j++) {
|
||||
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
|
||||
}
|
||||
r_arrays[i] = data;
|
||||
} break;
|
||||
default: {
|
||||
ERR_FAIL_MSG("Unhandled array type.");
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String ImporterMesh::validate_blend_shape_name(const String &p_name) {
|
||||
String name = p_name;
|
||||
const char *characters = ":";
|
||||
@ -306,7 +208,7 @@ void ImporterMesh::optimize_indices_for_cache() {
|
||||
} \
|
||||
write_array[vert_idx] = transformed_vert;
|
||||
|
||||
void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array, bool p_raycast_normals) {
|
||||
void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transform_array) {
|
||||
if (!SurfaceTool::simplify_scale_func) {
|
||||
return;
|
||||
}
|
||||
@ -379,8 +281,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
|
||||
}
|
||||
|
||||
float normal_merge_threshold = Math::cos(Math::deg_to_rad(p_normal_merge_angle));
|
||||
float normal_pre_split_threshold = Math::cos(Math::deg_to_rad(MIN(180.0f, p_normal_split_angle * 2.0f)));
|
||||
float normal_split_threshold = Math::cos(Math::deg_to_rad(p_normal_split_angle));
|
||||
const Vector3 *normals_ptr = normals.ptr();
|
||||
|
||||
HashMap<Vector3, LocalVector<Pair<int, int>>> unique_vertices;
|
||||
@ -469,22 +369,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
|
||||
unsigned int index_target = 12; // Start with the smallest target, 4 triangles
|
||||
unsigned int last_index_count = 0;
|
||||
|
||||
// Only used for normal raycasting
|
||||
int split_vertex_count = vertex_count;
|
||||
LocalVector<Vector3> split_vertex_normals;
|
||||
LocalVector<int> split_vertex_indices;
|
||||
split_vertex_normals.reserve(index_count / 3);
|
||||
split_vertex_indices.reserve(index_count / 3);
|
||||
|
||||
RandomPCG pcg;
|
||||
pcg.seed(123456789); // Keep seed constant across imports
|
||||
|
||||
Ref<StaticRaycaster> raycaster = p_raycast_normals ? StaticRaycaster::create() : Ref<StaticRaycaster>();
|
||||
if (raycaster.is_valid()) {
|
||||
raycaster->add_mesh(vertices, indices, 0);
|
||||
raycaster->commit();
|
||||
}
|
||||
|
||||
const float max_mesh_error = FLT_MAX; // We don't want to limit by error, just by index target
|
||||
float mesh_error = 0.0f;
|
||||
|
||||
@ -534,173 +418,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
|
||||
}
|
||||
}
|
||||
|
||||
if (raycaster.is_valid()) {
|
||||
LocalVector<LocalVector<int>> vertex_corners;
|
||||
vertex_corners.resize(vertex_count);
|
||||
|
||||
int *ptrw = new_indices.ptrw();
|
||||
for (unsigned int j = 0; j < new_index_count; j++) {
|
||||
vertex_corners[ptrw[j]].push_back(j);
|
||||
}
|
||||
|
||||
float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15));
|
||||
const float ray_bias = 0.05;
|
||||
float ray_length = ray_bias + mesh_error * scale * 3.0f;
|
||||
|
||||
Vector<StaticRaycaster::Ray> rays;
|
||||
LocalVector<Vector2> ray_uvs;
|
||||
|
||||
int32_t *new_indices_ptr = new_indices.ptrw();
|
||||
|
||||
int current_ray_count = 0;
|
||||
for (unsigned int j = 0; j < new_index_count; j += 3) {
|
||||
const Vector3 &v0 = vertices_ptr[new_indices_ptr[j + 0]];
|
||||
const Vector3 &v1 = vertices_ptr[new_indices_ptr[j + 1]];
|
||||
const Vector3 &v2 = vertices_ptr[new_indices_ptr[j + 2]];
|
||||
Vector3 face_normal = vec3_cross(v0 - v2, v0 - v1);
|
||||
float face_area = face_normal.length(); // Actually twice the face area, since it's the same error_factor on all faces, we don't care
|
||||
if (!Math::is_finite(face_area) || face_area == 0) {
|
||||
WARN_PRINT_ONCE("Ignoring face with non-finite normal in LOD generation.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 dir = face_normal / face_area;
|
||||
int ray_count = CLAMP(5.0 * face_area * error_factor, 16, 64);
|
||||
|
||||
rays.resize(current_ray_count + ray_count);
|
||||
StaticRaycaster::Ray *rays_ptr = rays.ptrw();
|
||||
|
||||
ray_uvs.resize(current_ray_count + ray_count);
|
||||
Vector2 *ray_uvs_ptr = ray_uvs.ptr();
|
||||
|
||||
for (int k = 0; k < ray_count; k++) {
|
||||
float u = pcg.randf();
|
||||
float v = pcg.randf();
|
||||
|
||||
if (u + v >= 1.0f) {
|
||||
u = 1.0f - u;
|
||||
v = 1.0f - v;
|
||||
}
|
||||
|
||||
u = 0.9f * u + 0.05f / 3.0f; // Give barycentric coordinates some padding, we don't want to sample right on the edge
|
||||
v = 0.9f * v + 0.05f / 3.0f; // v = (v - one_third) * 0.95f + one_third;
|
||||
float w = 1.0f - u - v;
|
||||
|
||||
Vector3 org = v0 * w + v1 * u + v2 * v;
|
||||
org -= dir * ray_bias;
|
||||
rays_ptr[current_ray_count + k] = StaticRaycaster::Ray(org, dir, 0.0f, ray_length);
|
||||
rays_ptr[current_ray_count + k].id = j / 3;
|
||||
ray_uvs_ptr[current_ray_count + k] = Vector2(u, v);
|
||||
}
|
||||
|
||||
current_ray_count += ray_count;
|
||||
}
|
||||
|
||||
raycaster->intersect(rays);
|
||||
|
||||
LocalVector<Vector3> ray_normals;
|
||||
LocalVector<real_t> ray_normal_weights;
|
||||
|
||||
ray_normals.resize(new_index_count);
|
||||
ray_normal_weights.resize(new_index_count);
|
||||
|
||||
for (unsigned int j = 0; j < new_index_count; j++) {
|
||||
ray_normal_weights[j] = 0.0f;
|
||||
}
|
||||
|
||||
const StaticRaycaster::Ray *rp = rays.ptr();
|
||||
for (int j = 0; j < rays.size(); j++) {
|
||||
if (rp[j].geomID != 0) { // Ray missed
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rp[j].normal.normalized().dot(rp[j].dir) > 0.0f) { // Hit a back face.
|
||||
continue;
|
||||
}
|
||||
|
||||
const float &u = rp[j].u;
|
||||
const float &v = rp[j].v;
|
||||
const float w = 1.0f - u - v;
|
||||
|
||||
const unsigned int &hit_tri_id = rp[j].primID;
|
||||
const unsigned int &orig_tri_id = rp[j].id;
|
||||
|
||||
const Vector3 &n0 = normals_ptr[indices_ptr[hit_tri_id * 3 + 0]];
|
||||
const Vector3 &n1 = normals_ptr[indices_ptr[hit_tri_id * 3 + 1]];
|
||||
const Vector3 &n2 = normals_ptr[indices_ptr[hit_tri_id * 3 + 2]];
|
||||
Vector3 normal = n0 * w + n1 * u + n2 * v;
|
||||
|
||||
Vector2 orig_uv = ray_uvs[j];
|
||||
const real_t orig_bary[3] = { 1.0f - orig_uv.x - orig_uv.y, orig_uv.x, orig_uv.y };
|
||||
for (int k = 0; k < 3; k++) {
|
||||
int idx = orig_tri_id * 3 + k;
|
||||
real_t weight = orig_bary[k];
|
||||
ray_normals[idx] += normal * weight;
|
||||
ray_normal_weights[idx] += weight;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int j = 0; j < new_index_count; j++) {
|
||||
if (ray_normal_weights[j] < 1.0f) { // Not enough data, the new normal would be just a bad guess
|
||||
ray_normals[j] = Vector3();
|
||||
} else {
|
||||
ray_normals[j] /= ray_normal_weights[j];
|
||||
}
|
||||
}
|
||||
|
||||
LocalVector<LocalVector<int>> normal_group_indices;
|
||||
LocalVector<Vector3> normal_group_averages;
|
||||
normal_group_indices.reserve(24);
|
||||
normal_group_averages.reserve(24);
|
||||
|
||||
for (unsigned int j = 0; j < vertex_count; j++) {
|
||||
const LocalVector<int> &corners = vertex_corners[j];
|
||||
const Vector3 &vertex_normal = normals_ptr[j];
|
||||
|
||||
for (const int &corner_idx : corners) {
|
||||
const Vector3 &ray_normal = ray_normals[corner_idx];
|
||||
|
||||
if (ray_normal.length_squared() < CMP_EPSILON2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (unsigned int l = 0; l < normal_group_indices.size(); l++) {
|
||||
LocalVector<int> &group_indices = normal_group_indices[l];
|
||||
Vector3 n = normal_group_averages[l] / group_indices.size();
|
||||
if (n.dot(ray_normal) > normal_pre_split_threshold) {
|
||||
found = true;
|
||||
group_indices.push_back(corner_idx);
|
||||
normal_group_averages[l] += ray_normal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
normal_group_indices.push_back({ corner_idx });
|
||||
normal_group_averages.push_back(ray_normal);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int k = 0; k < normal_group_indices.size(); k++) {
|
||||
LocalVector<int> &group_indices = normal_group_indices[k];
|
||||
Vector3 n = normal_group_averages[k] / group_indices.size();
|
||||
|
||||
if (vertex_normal.dot(n) < normal_split_threshold) {
|
||||
split_vertex_indices.push_back(j);
|
||||
split_vertex_normals.push_back(n);
|
||||
int new_idx = split_vertex_count++;
|
||||
for (const int &index : group_indices) {
|
||||
new_indices_ptr[index] = new_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normal_group_indices.clear();
|
||||
normal_group_averages.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Surface::LOD lod;
|
||||
lod.distance = MAX(mesh_error * scale, CMP_EPSILON2);
|
||||
lod.indices = new_indices;
|
||||
@ -713,22 +430,19 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
|
||||
}
|
||||
}
|
||||
|
||||
if (raycaster.is_valid()) {
|
||||
surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals);
|
||||
}
|
||||
|
||||
surfaces.write[i].lods.sort_custom<Surface::LODComparator>();
|
||||
|
||||
for (int j = 0; j < surfaces.write[i].lods.size(); j++) {
|
||||
Surface::LOD &lod = surfaces.write[i].lods.write[j];
|
||||
unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw();
|
||||
SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), split_vertex_count);
|
||||
SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), vertex_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImporterMesh::_generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array) {
|
||||
generate_lods(p_normal_merge_angle, p_normal_split_angle, p_skin_pose_transform_array);
|
||||
// p_normal_split_angle is unused, but kept for compatibility
|
||||
generate_lods(p_normal_merge_angle, p_skin_pose_transform_array);
|
||||
}
|
||||
|
||||
bool ImporterMesh::has_mesh() const {
|
||||
|
@ -68,9 +68,6 @@ class ImporterMesh : public Resource {
|
||||
return l.distance < r.distance;
|
||||
}
|
||||
};
|
||||
|
||||
void split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals);
|
||||
static void _split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals);
|
||||
};
|
||||
Vector<Surface> surfaces;
|
||||
Vector<String> blend_shapes;
|
||||
@ -118,7 +115,7 @@ public:
|
||||
|
||||
void optimize_indices_for_cache();
|
||||
|
||||
void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array, bool p_raycast_normals = false);
|
||||
void generate_lods(float p_normal_merge_angle, Array p_skin_pose_transform_array);
|
||||
|
||||
void create_shadow_mesh();
|
||||
Ref<ImporterMesh> get_shadow_mesh() const;
|
||||
|
Loading…
Reference in New Issue
Block a user