PyObject *py_ue_skeletal_mesh_register_morph_target(ue_PyUObject *self, PyObject * args)
{
	ue_py_check(self);

	PyObject *py_morph;

	if (!PyArg_ParseTuple(args, "O:skeletal_mesh_register_morph_target", &py_morph))
	{
		return nullptr;
	}

	USkeletalMesh *mesh = ue_py_check_type<USkeletalMesh>(self);
	if (!mesh)
		return PyErr_Format(PyExc_Exception, "uobject is not a SkeletalMesh");

	UMorphTarget *morph = ue_py_check_type<UMorphTarget>(py_morph);
	if (!morph)
		return PyErr_Format(PyExc_Exception, "argument is not a MorphTarget");

#if ENGINE_MINOR_VERSION > 16
	if (!morph->HasValidData())
		return PyErr_Format(PyExc_Exception, "the MorphTarget has no valid data");
#endif

	mesh->PreEditChange(nullptr);

	mesh->RegisterMorphTarget(morph);

	mesh->PostEditChange();

	mesh->MarkPackageDirty();

	Py_RETURN_NONE;
}
PyObject *py_ue_skeletal_mesh_build_lod(ue_PyUObject *self, PyObject * args, PyObject * kwargs)
{
	ue_py_check(self);

	PyObject *py_ss_vertex;
	int lod_index = 0;

	PyObject *py_compute_normals = nullptr;
	PyObject *py_compute_tangents = nullptr;
	PyObject *py_use_mikk = nullptr;

	static char *kw_names[] = { (char *)"soft_vertices", (char *)"lod", (char *)"compute_normals", (char *)"compute_tangents", (char *)"use_mikk", nullptr };

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|iOOO:skeletal_mesh_build_lod", kw_names, &py_ss_vertex, &lod_index, &py_compute_normals, &py_compute_tangents, &py_use_mikk))
	{
		return nullptr;
	}

	USkeletalMesh *mesh = ue_py_check_type<USkeletalMesh>(self);
	if (!mesh)
		return PyErr_Format(PyExc_Exception, "uobject is not a SkeletalMesh");

#if ENGINE_MINOR_VERSION < 19
	FSkeletalMeshResource *resource = mesh->GetImportedResource();
#else
	FSkeletalMeshModel *resource = mesh->GetImportedModel();
#endif

	if (lod_index < 0 || lod_index > resource->LODModels.Num())
		return PyErr_Format(PyExc_Exception, "invalid LOD index, must be between 0 and %d", resource->LODModels.Num());

	mesh->PreEditChange(nullptr);

	if (lod_index == resource->LODModels.Num())
	{
#if ENGINE_MINOR_VERSION < 19
		resource->LODModels.Add(new FStaticLODModel());
#else
		resource->LODModels.Add(new FSkeletalMeshLODModel());
#endif
		mesh->LODInfo.AddZeroed();
	}
	else
	{
		// reinitialized already existent LOD
#if ENGINE_MINOR_VERSION < 19
		new(&resource->LODModels[lod_index]) FStaticLODModel();
#else
		new(&resource->LODModels[lod_index]) FSkeletalMeshLODModel();
#endif
	}

#if ENGINE_MINOR_VERSION < 19
	FStaticLODModel& LODModel = resource->LODModels[lod_index];
#else
	FSkeletalMeshLODModel& LODModel = resource->LODModels[lod_index];
#endif

	mesh->LODInfo[lod_index].LODHysteresis = 0.02;

	FSkeletalMeshOptimizationSettings settings;
	mesh->LODInfo[lod_index].ReductionSettings = settings;

	LODModel.NumTexCoords = 1;

	IMeshUtilities & MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");

	PyObject *py_iter = PyObject_GetIter(py_ss_vertex);
	if (!py_iter)
	{
		return PyErr_Format(PyExc_Exception, "argument is not an iterable of FSoftSkinVertex");
	}

	TArray<FSoftSkinVertex> soft_vertices;

	TArray<FVector> points;
	TArray<FMeshWedge> wedges;
	TArray<FMeshFace> faces;
	TArray<FVertInfluence> influences;
	TArray<int32> points_to_map;

	TArray<FVector> tangentsX;
	TArray<FVector> tangentsY;
	TArray<FVector> tangentsZ;
	TArray<uint16> material_indices;
	TArray<uint32> smoothing_groups;

	while (PyObject *py_item = PyIter_Next(py_iter))
	{
		ue_PyFSoftSkinVertex *ss_vertex = py_ue_is_fsoft_skin_vertex(py_item);
		if (!ss_vertex)
		{
			Py_DECREF(py_iter);
			return PyErr_Format(PyExc_Exception, "argument is not an iterable of FSoftSkinVertex");
		}
		int32 vertex_index = points.Add(ss_vertex->ss_vertex.Position);

		points_to_map.Add(vertex_index);

		FMeshWedge wedge;
		wedge.iVertex = vertex_index;
		wedge.Color = ss_vertex->ss_vertex.Color;
		for (int32 i = 0; i < MAX_TEXCOORDS; i++)
		{
			wedge.UVs[i] = ss_vertex->ss_vertex.UVs[i];
		}
		int32 wedge_index = wedges.Add(wedge);

		for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; i++)
		{
			FVertInfluence influence;
			influence.VertIndex = wedge_index;
			influence.BoneIndex = ss_vertex->ss_vertex.InfluenceBones[i];
			influence.Weight = ss_vertex->ss_vertex.InfluenceWeights[i] / 255.f;
			influences.Add(influence);
		}

		tangentsX.Add(ss_vertex->ss_vertex.TangentX);
		tangentsY.Add(ss_vertex->ss_vertex.TangentY);
		tangentsZ.Add(ss_vertex->ss_vertex.TangentZ);

		material_indices.Add(ss_vertex->material_index);
		smoothing_groups.Add(ss_vertex->smoothing_group);
	}

	Py_DECREF(py_iter);

	if (wedges.Num() % 3 != 0)
		return PyErr_Format(PyExc_Exception, "invalid number of FSoftSkinVertex, must be a multiple of 3");

	for (int32 i = 0; i < wedges.Num(); i += 3)
	{
		FMeshFace face;
		face.iWedge[0] = i;
		face.iWedge[1] = i + 1;
		face.iWedge[2] = i + 2;

		face.MeshMaterialIndex = material_indices[i];
		face.SmoothingGroups = smoothing_groups[i];

		face.TangentX[0] = tangentsX[i];
		face.TangentX[1] = tangentsX[i + 1];
		face.TangentX[2] = tangentsX[i + 2];

		face.TangentY[0] = tangentsY[i];
		face.TangentY[1] = tangentsY[i + 1];
		face.TangentY[2] = tangentsY[i + 2];

		face.TangentZ[0] = tangentsZ[i];
		face.TangentZ[1] = tangentsZ[i + 1];
		face.TangentZ[2] = tangentsZ[i + 2];

		faces.Add(face);
	}

#if ENGINE_MINOR_VERSION < 19
	FStaticLODModel & lod_model = resource->LODModels[lod_index];
#else
	FSkeletalMeshLODModel & lod_model = resource->LODModels[lod_index];
#endif

	IMeshUtilities::MeshBuildOptions build_settings;
	build_settings.bUseMikkTSpace = (py_use_mikk && PyObject_IsTrue(py_use_mikk));
	build_settings.bComputeNormals = (py_compute_normals && PyObject_IsTrue(py_compute_normals));
	build_settings.bComputeTangents = (py_compute_tangents && PyObject_IsTrue(py_compute_tangents));
	build_settings.bRemoveDegenerateTriangles = true;

	bool success = MeshUtilities.BuildSkeletalMesh(lod_model, mesh->RefSkeleton, influences, wedges, faces, points, points_to_map, build_settings);

	if (!success)
	{
		return PyErr_Format(PyExc_Exception, "unable to create new Skeletal LOD");
	}

#if ENGINE_MINOR_VERSION < 19
	for (int32 i = 0; i < lod_model.Sections.Num(); i++)
	{
		mesh->LODInfo[lod_index].TriangleSortSettings.AddZeroed();
	}
#endif

	mesh->CalculateRequiredBones(LODModel, mesh->RefSkeleton, nullptr);
	mesh->CalculateInvRefMatrices();

	mesh->Skeleton->RecreateBoneTree(mesh);
	mesh->Skeleton->SetPreviewMesh(mesh);

	mesh->Skeleton->PostEditChange();
	mesh->Skeleton->MarkPackageDirty();

	mesh->PostEditChange();
	mesh->MarkPackageDirty();

	Py_RETURN_NONE;
}