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

	PyObject *py_skeleton;
	if (!PyArg_ParseTuple(args, "O:skeletal_mesh_set_skeleton", &py_skeleton))
		return nullptr;

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

	USkeleton *skeleton = ue_py_check_type<USkeleton>(py_skeleton);
	if (!skeleton)
		return PyErr_Format(PyExc_Exception, "argument is not a USkeleton.");

	mesh->ReleaseResources();
	mesh->ReleaseResourcesFence.Wait();

	mesh->Skeleton = skeleton;

	mesh->RefSkeleton = skeleton->GetReferenceSkeleton();

	mesh->RefBasesInvMatrix.Empty();
	mesh->CalculateInvRefMatrices();

#if WITH_EDITOR
	mesh->PostEditChange();
#endif
	mesh->InitResources();
	mesh->MarkPackageDirty();

	Py_RETURN_NONE;
}
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;
}
void FLODUtilities::RemoveLOD(FSkeletalMeshUpdateContext& UpdateContext, int32 DesiredLOD )
{
	USkeletalMesh* SkeletalMesh = UpdateContext.SkeletalMesh;
	FSkeletalMeshResource* SkelMeshResource = SkeletalMesh->GetImportedResource();

	if( SkelMeshResource->LODModels.Num() == 1 )
	{
		FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "NoLODToRemove", "No LODs to remove!") );
		return;
	}

	// Now display combo to choose which LOD to remove.
	TArray<FString> LODStrings;
	LODStrings.AddZeroed( SkelMeshResource->LODModels.Num()-1 );
	for(int32 i=0; i<SkelMeshResource->LODModels.Num()-1; i++)
	{
		LODStrings[i] = FString::Printf( TEXT("%d"), i+1 );
	}

	check( SkeletalMesh->LODInfo.Num() == SkelMeshResource->LODModels.Num() );

	// If its a valid LOD, kill it.
	if( DesiredLOD > 0 && DesiredLOD < SkelMeshResource->LODModels.Num() )
	{
		//We'll be modifying the skel mesh data so reregister

		//TODO - do we need to reregister something else instead?
		FMultiComponentReregisterContext ReregisterContext(UpdateContext.AssociatedComponents);

		// Release rendering resources before deleting LOD
		SkelMeshResource->ReleaseResources();

		// Block until this is done
		FlushRenderingCommands();

		SkelMeshResource->LODModels.RemoveAt(DesiredLOD);
		SkeletalMesh->LODInfo.RemoveAt(DesiredLOD);
		SkeletalMesh->InitResources();

		RefreshLODChange(SkeletalMesh);

		// Set the forced LOD to Auto.
		for(auto Iter = UpdateContext.AssociatedComponents.CreateIterator(); Iter; ++Iter)
		{
			USkinnedMeshComponent* SkinnedComponent = Cast<USkinnedMeshComponent>(*Iter);
			if(SkinnedComponent)
			{
				SkinnedComponent->ForcedLodModel = 0;
			}
		}
		
		//Notify calling system of change
		UpdateContext.OnLODChanged.ExecuteIfBound();

		// Mark things for saving.
		SkeletalMesh->MarkPackageDirty();
	}
}
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;
}
PyObject *py_ue_skeletal_mesh_set_required_bones(ue_PyUObject *self, PyObject * args)
{
	ue_py_check(self);

	PyObject *py_map;
	int lod_index = 0;
	if (!PyArg_ParseTuple(args, "O|i:skeletal_mesh_set_required_bones", &py_map, &lod_index))
		return nullptr;

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

#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() - 1);

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

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

	TArray<FBoneIndexType> required_bones;

	while (PyObject *py_item = PyIter_Next(py_iter))
	{
		if (!PyNumber_Check(py_item))
		{
			Py_DECREF(py_iter);
			return PyErr_Format(PyExc_Exception, "argument is not an iterable of numbers");
		}
		PyObject *py_num = PyNumber_Long(py_item);
		uint16 index = PyLong_AsUnsignedLong(py_num);
		Py_DECREF(py_num);
		required_bones.Add(index);
	}
	Py_DECREF(py_iter);

	// temporarily disable all USkinnedMeshComponent's
	TComponentReregisterContext<USkinnedMeshComponent> ReregisterContext;

	mesh->ReleaseResources();
	mesh->ReleaseResourcesFence.Wait();

	model.RequiredBones = required_bones;
	model.RequiredBones.Sort();

	mesh->RefBasesInvMatrix.Empty();
	mesh->CalculateInvRefMatrices();

#if WITH_EDITOR
	mesh->PostEditChange();
#endif
	mesh->InitResources();
	mesh->MarkPackageDirty();

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

	PyObject *py_ss_vertex;
	int lod_index = 0;
	int section_index = 0;
	if (!PyArg_ParseTuple(args, "O|ii:skeletal_mesh_set_soft_vertices", &py_ss_vertex, &lod_index, &section_index))
		return nullptr;

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

#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() - 1);

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

	if (section_index < 0 || section_index >= model.Sections.Num())
		return PyErr_Format(PyExc_Exception, "invalid Section index, must be between 0 and %d", model.Sections.Num() - 1);

	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;

	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");
		}
		soft_vertices.Add(ss_vertex->ss_vertex);
	}
	Py_DECREF(py_iter);

	// temporarily disable all USkinnedMeshComponent's
	TComponentReregisterContext<USkinnedMeshComponent> ReregisterContext;

	mesh->ReleaseResources();
	mesh->ReleaseResourcesFence.Wait();

	model.Sections[section_index].SoftVertices = soft_vertices;

	model.Sections[section_index].NumVertices = soft_vertices.Num();
	model.Sections[section_index].CalcMaxBoneInfluences();

	mesh->RefBasesInvMatrix.Empty();
	mesh->CalculateInvRefMatrices();

#if WITH_EDITOR
	mesh->PostEditChange();
#endif

	mesh->InitResources();
	mesh->MarkPackageDirty();

	Py_RETURN_NONE;

}