Speed up FBX export of normals/tangents/bitangents with numpy #104449

Merged
Bastien Montagne merged 1 commits from Mysteryem/blender-addons:fbx_numpy_normals_tangents_bitangents_pr into main 2023-03-06 15:05:26 +01:00
2 changed files with 25 additions and 24 deletions

View File

@ -49,7 +49,7 @@ from .fbx_utils import (
units_blender_to_fbx_factor, units_convertor, units_convertor_iter,
matrix4_to_array, similar_values, similar_values_iter, astype_view_signedness, fast_first_axis_unique,
# Mesh transform helpers.
vcos_transformed_gen, nors_transformed_gen, vcos_transformed, nors_transformed,
vcos_transformed_gen, vcos_transformed, nors_transformed,
# UUID from key.
get_fbx_uuid_from_key,
# Key generators.
@ -1033,28 +1033,35 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
# but this does not seem well supported by apps currently...
me.calc_normals_split()
t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
ln_bl_dtype = np.single
ln_fbx_dtype = np.float64
t_ln = np.empty(len(me.loops) * 3, dtype=ln_bl_dtype)
me.loops.foreach_get("normal", t_ln)
t_ln = nors_transformed_gen(t_ln, geom_mat_no)
t_ln = nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype)
if 0:
t_ln = tuple(t_ln) # No choice... :/
lnidx_fbx_dtype = np.int32
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
elem_data_single_string(lay_nor, b"Name", b"")
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
ln2idx = tuple(set(t_ln))
elem_data_single_float64_array(lay_nor, b"Normals", chain(*ln2idx))
# Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_ln.
# Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique
# helper function.
t_ln, t_lnidx = fast_first_axis_unique(t_ln.reshape(-1, 3), return_inverse=True)
# Convert to the type for fbx
t_lnidx = astype_view_signedness(t_lnidx, lnidx_fbx_dtype)
elem_data_single_float64_array(lay_nor, b"Normals", t_ln)
# Normal weights, no idea what it is.
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(ln2idx)
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(t_ln)
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
ln2idx = {nor: idx for idx, nor in enumerate(ln2idx)}
elem_data_single_int32_array(lay_nor, b"NormalsIndex", (ln2idx[n] for n in t_ln))
elem_data_single_int32_array(lay_nor, b"NormalsIndex", t_lnidx)
del ln2idx
del t_lnidx
# del t_lnw
else:
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
@ -1062,7 +1069,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
elem_data_single_string(lay_nor, b"Name", b"")
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
elem_data_single_float64_array(lay_nor, b"Normals", chain(*t_ln))
elem_data_single_float64_array(lay_nor, b"Normals", t_ln)
# Normal weights, no idea what it is.
# t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
@ -1073,9 +1080,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
tspacenumber = len(me.uv_layers)
if tspacenumber:
# We can only compute tspace on tessellated meshes, need to check that here...
t_lt = [None] * len(me.polygons)
lt_bl_dtype = np.uintc
t_lt = np.empty(len(me.polygons), dtype=lt_bl_dtype)
me.polygons.foreach_get("loop_total", t_lt)
if any((lt > 4 for lt in t_lt)):
if (t_lt > 4).any():
del t_lt
scene_data.settings.report(
{'WARNING'},
@ -1084,7 +1092,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
else:
del t_lt
num_loops = len(me.loops)
t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * num_loops * 3
t_ln = np.empty(num_loops * 3, dtype=ln_bl_dtype)
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
# Annoying, `me.calc_tangent` errors in case there is no geometry...
@ -1102,7 +1110,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
elem_data_single_float64_array(lay_nor, b"Binormals",
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype))
# Binormal weights, no idea what it is.
# elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
@ -1115,7 +1123,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
elem_data_single_float64_array(lay_nor, b"Tangents",
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype))
# Tangent weights, no idea what it is.
# elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)

View File

@ -265,13 +265,6 @@ def vcos_transformed_gen(raw_cos, m=None):
gen = zip(*(iter(raw_cos),) * 3)
return gen if m is None else (m @ Vector(v) for v in gen)
def nors_transformed_gen(raw_nors, m=None):
# Great, now normals are also expected 4D!
# XXX Back to 3D normals for now!
# gen = zip(*(iter(raw_nors),) * 3 + (_infinite_gen(1.0),))
gen = zip(*(iter(raw_nors),) * 3)
return gen if m is None else (m @ Vector(v) for v in gen)
def _mat4_vec3_array_multiply(mat4, vec3_array, dtype=None, return_4d=False):
"""Multiply a 4d matrix by each 3d vector in an array and return as an array of either 3d or 4d vectors.