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_lods_num(ue_PyUObject *self, PyObject * args)
{
	ue_py_check(self);

	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

	return PyLong_FromLong(resource->LODModels.Num());
}
PyObject *py_ue_skeletal_mesh_get_raw_indices(ue_PyUObject *self, PyObject * args)
{

	ue_py_check(self);

	int lod_index = 0;

	if (!PyArg_ParseTuple(args, "|i:skeletal_mesh_get_raw_indices", &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_list = PyList_New(0);

	int32 *raw_indices = (int32 *)model.RawPointIndices.Lock(LOCK_READ_ONLY);
	int32 *indices = (int32 *)FMemory_Alloca(model.RawPointIndices.GetBulkDataSize());
	FMemory::Memcpy(indices, raw_indices, model.RawPointIndices.GetBulkDataSize());
	model.RawPointIndices.Unlock();

	for (int32 index = 0; index < model.RawPointIndices.GetBulkDataSize() / sizeof(int32); index++)
	{
		PyList_Append(py_list, PyLong_FromLong(indices[index]));
	}

	return py_list;
}
PyObject *py_ue_skeletal_mesh_get_soft_vertices(ue_PyUObject *self, PyObject * args)
{

	ue_py_check(self);

	int lod_index = 0;
	int section_index = 0;
	if (!PyArg_ParseTuple(args, "|ii:skeletal_mesh_get_soft_vertices", &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_list = PyList_New(0);

	for (int32 i = 0; i < model.Sections[section_index].SoftVertices.Num(); i++)
	{
		PyList_Append(py_list, py_ue_new_fsoft_skin_vertex(model.Sections[section_index].SoftVertices[i]));
	}

	return py_list;
}
PyObject *py_ue_skeletal_mesh_to_import_vertex_map(ue_PyUObject *self, PyObject * args)
{
	ue_py_check(self);

	int lod_index = 0;

	if (!PyArg_ParseTuple(args, "|i:skeletal_mesh_to_import_vertex_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());

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

	PyObject *py_list = PyList_New(0);

	for (int32 value : LODModel.MeshToImportVertexMap)
	{
		PyList_Append(py_list, PyLong_FromLong(value));
	}

	return py_list;
}
PyObject *py_ue_skeletal_mesh_sections_num(ue_PyUObject *self, PyObject * args)
{
	ue_py_check(self);

	int lod_index = 0;
	if (!PyArg_ParseTuple(args, "|i:skeletal_mesh_sections_num", &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);

	return PyLong_FromLong(resource->LODModels[lod_index].Sections.Num());
}
PyObject *py_ue_skeletal_mesh_get_required_bones(ue_PyUObject *self, PyObject * args)
{
	ue_py_check(self);

	int lod_index = 0;
	if (!PyArg_ParseTuple(args, "|i:skeletal_mesh_get_required_bones", &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_list = PyList_New(0);

	for (uint16 index : model.RequiredBones)
	{
		PyList_Append(py_list, PyLong_FromUnsignedLong(index));
	}

	return py_list;
}
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_get_lod(ue_PyUObject *self, PyObject * args)
{

	ue_py_check(self);

	int lod_index = 0;

	if (!PyArg_ParseTuple(args, "|i:skeletal_mesh_get_lod", &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_list = PyList_New(0);

	TArray<uint32> indices;
#if ENGINE_MINOR_VERSION > 18
	indices = model.IndexBuffer;
#else
	model.MultiSizeIndexContainer.GetIndexBuffer(indices);
#endif

	for (int32 index = 0; index < indices.Num(); index++)
	{
		int32 section_index;
		int32 vertex_index;
#if ENGINE_MINOR_VERSION > 18
		model.GetSectionFromVertexIndex(indices[index], section_index, vertex_index);
#else
		bool has_extra_influences;
		model.GetSectionFromVertexIndex(indices[index], section_index, vertex_index, has_extra_influences);
#endif
		FSoftSkinVertex ssv = model.Sections[section_index].SoftVertices[vertex_index];
		// fix bone id
		for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; i++)
		{
			if (ssv.InfluenceBones[i] < model.Sections[section_index].BoneMap.Num())
			{
				ssv.InfluenceBones[i] = model.Sections[section_index].BoneMap[ssv.InfluenceBones[i]];
			}
			else
			{
				UE_LOG(LogPython, Warning, TEXT("unable to retrieve bone mapping for index %d, forcing to 0"), ssv.InfluenceBones[i]);
				ssv.InfluenceBones[i] = 0;
			}
		}
		ue_PyFSoftSkinVertex *py_ss_vertex = (ue_PyFSoftSkinVertex *)py_ue_new_fsoft_skin_vertex(ssv);
		py_ss_vertex->material_index = section_index;
		PyList_Append(py_list, (PyObject *)py_ss_vertex);
	}

	return py_list;
}
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;

}
UObject* USpriterImporterFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn)
{
	Flags |= RF_Transactional;

	FEditorDelegates::OnAssetPreImport.Broadcast(this, InClass, InParent, InName, Type);

 	FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
 
 	bool bLoadedSuccessfully = true;
 
 	const FString CurrentFilename = UFactory::GetCurrentFilename();
 	FString CurrentSourcePath;
 	FString FilenameNoExtension;
 	FString UnusedExtension;
 	FPaths::Split(CurrentFilename, CurrentSourcePath, FilenameNoExtension, UnusedExtension);
 
 	const FString LongPackagePath = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetPathName());
 
 	const FString NameForErrors(InName.ToString());
 	const FString FileContent(BufferEnd - Buffer, Buffer);
 	TSharedPtr<FJsonObject> DescriptorObject = ParseJSON(FileContent, NameForErrors);

	UPaperSpriterImportData* Result = nullptr;
 
	// Parse the file 
	FSpriterSCON DataModel;
	if (DescriptorObject.IsValid())
	{
		DataModel.ParseFromJSON(DescriptorObject, NameForErrors, /*bSilent=*/ false, /*bPreParseOnly=*/ false);
	}

	// Create the new 'hub' asset and convert the data model over
	if (DataModel.IsValid())
	{
		const bool bSilent = false;

		Result = NewObject<UPaperSpriterImportData>(InParent, InName, Flags);
		Result->Modify();

		//@TODO: Do some things here maybe?
		Result->ImportedData = DataModel;


		// Import the assets in the folders
		for (const FSpriterFolder& Folder : DataModel.Folders)
		{
			for (const FSpriterFile& File : Folder.Files)
			{
				const FString RelativeFilename = File.Name.Replace(TEXT("\\"), TEXT("/"), ESearchCase::CaseSensitive);
				const FString SourceSpriterFilePath = FPaths::Combine(*CurrentSourcePath, *RelativeFilename);

				FString RelativeDestPath;
				FString JustFilename;
				FString JustExtension;
				FPaths::Split(RelativeFilename, /*out*/ RelativeDestPath, /*out*/ JustFilename, /*out*/ JustExtension);

				if (File.FileType == ESpriterFileType::Sprite)
				{
					const FString TargetTexturePath = LongPackagePath / TEXT("Textures") / RelativeDestPath;
					const FString TargetSpritePath = LongPackagePath / TEXT("Sprites") / RelativeDestPath;

					// Import the texture
					UTexture2D* ImportedTexture = ImportTexture(SourceSpriterFilePath, TargetTexturePath);

					if (ImportTexture == nullptr)
					{
						SPRITER_IMPORT_ERROR(TEXT("Failed to import texture '%s' while importing '%s'"), *SourceSpriterFilePath, *CurrentFilename);
					}

					// Create a sprite from it
					UPaperSprite* ImportedSprite = CastChecked<UPaperSprite>(CreateNewAsset(UPaperSprite::StaticClass(), TargetSpritePath, JustFilename, Flags));

					const ESpritePivotMode::Type PivotMode = ConvertNormalizedPivotPointToPivotMode(File.PivotX, File.PivotY);
					const double PivotInPixelsX = File.Width * File.PivotX;
					const double PivotInPixelsY = File.Height * File.PivotY;

					ImportedSprite->SetPivotMode(PivotMode, FVector2D((float)PivotInPixelsX, (float)PivotInPixelsY));

					FSpriteAssetInitParameters SpriteInitParams;
					SpriteInitParams.SetTextureAndFill(ImportedTexture);
					GetDefault<UPaperImporterSettings>()->ApplySettingsForSpriteInit(SpriteInitParams);
					SpriteInitParams.SetPixelsPerUnrealUnit(1.0f);
					ImportedSprite->InitializeSprite(SpriteInitParams);
				}
				else if (File.FileType == ESpriterFileType::Sound)
				{
					// Import the sound
					const FString TargetAssetPath = LongPackagePath / RelativeDestPath;
					UObject* ImportedSound = ImportAsset(SourceSpriterFilePath, TargetAssetPath);
				}
				else if (File.FileType != ESpriterFileType::INVALID)
				{
					ensureMsgf(false, TEXT("Importer was not updated when a new entry was added to ESpriterFileType"));
				}
					// 		TMap<FString, class UTexture2D*> ImportedTextures;
					// 		TMap<FString, class UPaperSprite> ImportedSprites;

			}
		}

		for (const FSpriterEntity& Entity : DataModel.Entities)
		{
			// Extract the common/shared skeleton
			FBoneHierarchyBuilder HierarchyBuilder;
			HierarchyBuilder.ProcessHierarchy(Entity);

			// Create the skeletal mesh
			const FString TargetMeshName = Entity.Name + TEXT("_SkelMesh");
			const FString TargetMeshPath = LongPackagePath;
			USkeletalMesh* SkeletalMesh = CastChecked<USkeletalMesh>(CreateNewAsset(USkeletalMesh::StaticClass(), TargetMeshPath, TargetMeshName, Flags));

			// Create the skeleton
			const FString TargetSkeletonName = Entity.Name + TEXT("_Skeleton");
			const FString TargetSkeletonPath = LongPackagePath;
			USkeleton* EntitySkeleton = CastChecked<USkeleton>(CreateNewAsset(USkeleton::StaticClass(), TargetSkeletonPath, TargetSkeletonName, Flags));

			// Initialize the mesh asset
			FSkeletalMeshResource* ImportedResource = SkeletalMesh->GetImportedResource();
			check(ImportedResource->LODModels.Num() == 0);
			ImportedResource->LODModels.Empty();
			FStaticLODModel& LODModel = *new (ImportedResource->LODModels) FStaticLODModel();

			SkeletalMesh->LODInfo.Empty();
			SkeletalMesh->LODInfo.AddZeroed();
			SkeletalMesh->LODInfo[0].LODHysteresis = 0.02f;
			FSkeletalMeshOptimizationSettings Settings;
			// set default reduction settings values
			SkeletalMesh->LODInfo[0].ReductionSettings = Settings;

			// Create initial bounding box based on expanded version of reference pose for meshes without physics assets. Can be overridden by artist.
// 			FBox BoundingBox(SkelMeshImportDataPtr->Points.GetData(), SkelMeshImportDataPtr->Points.Num());
// 			FBox Temp = BoundingBox;
// 			FVector MidMesh = 0.5f*(Temp.Min + Temp.Max);
// 			BoundingBox.Min = Temp.Min + 1.0f*(Temp.Min - MidMesh);
// 			BoundingBox.Max = Temp.Max + 1.0f*(Temp.Max - MidMesh);
// 			// Tuck up the bottom as this rarely extends lower than a reference pose's (e.g. having its feet on the floor).
// 			// Maya has Y in the vertical, other packages have Z.
// 			//BEN const int32 CoordToTuck = bAssumeMayaCoordinates ? 1 : 2;
// 			//BEN BoundingBox.Min[CoordToTuck]	= Temp.Min[CoordToTuck] + 0.1f*(Temp.Min[CoordToTuck] - MidMesh[CoordToTuck]);
// 			BoundingBox.Min[2] = Temp.Min[2] + 0.1f*(Temp.Min[2] - MidMesh[2]);
// 			SkeletalMesh->Bounds = FBoxSphereBounds(BoundingBox);

			// Store whether or not this mesh has vertex colors
// 			SkeletalMesh->bHasVertexColors = SkelMeshImportDataPtr->bHasVertexColors;

			// Pass the number of texture coordinate sets to the LODModel.  Ensure there is at least one UV coord
			LODModel.NumTexCoords = 1;// FMath::Max<uint32>(1, SkelMeshImportDataPtr->NumTexCoords);


			// Create the reference skeleton and update LOD0
			FReferenceSkeleton& RefSkeleton = SkeletalMesh->RefSkeleton;
			HierarchyBuilder.CopyToRefSkeleton(RefSkeleton);
			SkeletalMesh->CalculateRequiredBones(LODModel, RefSkeleton, /*BonesToRemove=*/ nullptr);
			SkeletalMesh->CalculateInvRefMatrices();

			// Initialize the skeleton asset
			EntitySkeleton->MergeAllBonesToBoneTree(SkeletalMesh);

			// Point the mesh and skeleton at each other
			SkeletalMesh->Skeleton = EntitySkeleton;
			EntitySkeleton->SetPreviewMesh(SkeletalMesh);

			// Create the animations
			for (const FSpriterAnimation& Animation : Entity.Animations)
			{
				//@TODO: That thing I said...

				const FString TargetAnimationName = Animation.Name;
				const FString TargetAnimationPath = LongPackagePath / TEXT("Animations");
				UAnimSequence* AnimationAsset = CastChecked<UAnimSequence>(CreateNewAsset(UAnimSequence::StaticClass(), TargetAnimationPath, TargetAnimationName, Flags));

				AnimationAsset->SetSkeleton(EntitySkeleton);

				// if you have one pose(thus 0.f duration), it still contains animation, so we'll need to consider that as MINIMUM_ANIMATION_LENGTH time length
				const float DurationInSeconds = Animation.LengthInMS * 0.001f;
				AnimationAsset->SequenceLength = FMath::Max<float>(DurationInSeconds, MINIMUM_ANIMATION_LENGTH);

				const bool bSourceDataExists = (AnimationAsset->SourceRawAnimationData.Num() > 0);
				TArray<struct FRawAnimSequenceTrack>& RawAnimationData = bSourceDataExists ? AnimationAsset->SourceRawAnimationData : AnimationAsset->RawAnimationData;




				int32 TotalNumKeys = 0;
				for (const FSpriterTimeline& Timeline : Animation.Timelines)
				{
					if (Timeline.ObjectType != ESpriterObjectType::Bone)
					{
						continue;
					}

					const FName BoneName = Entity.Objects[Timeline.ObjectIndex].ObjectName;

					const int32 RefBoneIndex = EntitySkeleton->GetReferenceSkeleton().FindBoneIndex(BoneName);
					check(RefBoneIndex != INDEX_NONE);

					FRawAnimSequenceTrack RawTrack;
					RawTrack.PosKeys.Empty();
					RawTrack.RotKeys.Empty();
					RawTrack.ScaleKeys.Empty();

					int32 NumKeysForTrack = 0;

					//@TODO: Quick and dirty resampling code that needs to be replaced (totally ignores curve type, edge cases, etc...)
					const float ResampleFPS = 30.0f;
					int32 DesiredNumKeys = FMath::CeilToInt(ResampleFPS * DurationInSeconds);
					const float TimePerKey = 1.0f / ResampleFPS;
					
					float CurrentSampleTime = 0.0f;
					for (int32 FrameIndex = 0; FrameIndex < DesiredNumKeys; ++FrameIndex)
					{
						int32 LowerKeyIndex = 0;
						for (; LowerKeyIndex < Timeline.Keys.Num(); ++LowerKeyIndex)
						{
							if (Timeline.Keys[LowerKeyIndex].TimeInMS * 0.001f > CurrentSampleTime)
							{
								--LowerKeyIndex;
								break;
							}
						}
						if (LowerKeyIndex >= Timeline.Keys.Num())
						{
							LowerKeyIndex = Timeline.Keys.Num() - 1;
						}

						int32 UpperKeyIndex = LowerKeyIndex + 1;
						float UpperKeyTime = 0.0f;
						if (UpperKeyIndex >= Timeline.Keys.Num())
						{
							UpperKeyTime = DurationInSeconds;
							if (Animation.bIsLooping)
							{
								UpperKeyIndex = 0;
							}
							else
							{
								UpperKeyIndex = Timeline.Keys.Num() - 1;
							}
						}
						else
						{
							UpperKeyTime = Timeline.Keys[UpperKeyIndex].TimeInMS * 0.001f;
						}

						const FSpriterFatTimelineKey& TimelineKey0 = Timeline.Keys[LowerKeyIndex];
						const FSpriterFatTimelineKey& TimelineKey1 = Timeline.Keys[UpperKeyIndex];
						const float LowerKeyTime = TimelineKey0.TimeInMS * 0.001f;

						const FTransform LocalTransform0 = TimelineKey0.Info.ConvertToTransform();
						const FTransform LocalTransform1 = TimelineKey1.Info.ConvertToTransform();

						FTransform LocalTransform = LocalTransform0;
						if (LowerKeyIndex != UpperKeyIndex)
						{
							const float Alpha = (CurrentSampleTime - LowerKeyTime) / (UpperKeyTime - LowerKeyTime);

							LocalTransform.Blend(LocalTransform0, LocalTransform1, Alpha);
						}

						RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D());
						RawTrack.PosKeys.Add(LocalTransform.GetTranslation());
						RawTrack.RotKeys.Add(LocalTransform.GetRotation());
						++NumKeysForTrack;

						CurrentSampleTime += TimePerKey;
					}
// 
// 					for (const FSpriterFatTimelineKey& TimelineKey : Timeline.Keys)
// 					{
// 						//@TODO: Ignoring TimeInMS
// 						const FTransform LocalTransform = TimelineKey.Info.ConvertToTransform();
// 
// 						RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D());
// 						RawTrack.PosKeys.Add(LocalTransform.GetTranslation());
// 						RawTrack.RotKeys.Add(LocalTransform.GetRotation());
// 
// 						++NumKeysForTrack;
// 					}
// 



					RawAnimationData.Add(RawTrack);
					AnimationAsset->AnimationTrackNames.Add(BoneName);

					// add mapping to skeleton bone track
					AnimationAsset->TrackToSkeletonMapTable.Add(FTrackToSkeletonMap(RefBoneIndex));

					TotalNumKeys = FMath::Max(TotalNumKeys, NumKeysForTrack);
				}
				AnimationAsset->NumFrames = TotalNumKeys;

				AnimationAsset->MarkRawDataAsModified();

				// compress animation
				{
					GWarn->BeginSlowTask(LOCTEXT("BeginCompressAnimation", "Compress Animation"), true);
					GWarn->StatusForceUpdate(1, 1, LOCTEXT("CompressAnimation", "Compressing Animation"));
					// if source data exists, you should bake it to Raw to apply
					if (bSourceDataExists)
					{
						AnimationAsset->BakeTrackCurvesToRawAnimation();
					}
					else
					{
						// otherwise just compress
						AnimationAsset->PostProcessSequence();
					}

					// run debug mode
					GWarn->EndSlowTask();
				}


// 					NewAnimation = FFbxImporter->ImportAnimations(Skeleton, Outer, SortedLinks, AnimName, TemplateImportData, FBXMeshNodeArray);
// 
// 					if (NewAnimation)
// 					{
// 						// since to know full path, reimport will need to do same
// 						UFbxAnimSequenceImportData* ImportData = UFbxAnimSequenceImportData::GetImportDataForAnimSequence(NewAnimation, TemplateImportData);
// 						ImportData->SourceFilePath = FReimportManager::SanitizeImportFilename(UFactory::CurrentFilename, NewAnimation);
// 						ImportData->SourceFileTimestamp = IFileManager::Get().GetTimeStamp(*UFactory::CurrentFilename).ToString();
// 					}


			}
		}

		Result->PostEditChange();
	}
 	else
 	{
 		// Failed to parse the JSON
 		bLoadedSuccessfully = false;
 	}

	if (Result != nullptr)
	{
		//@TODO: Need to do this
		// Store the current file path and timestamp for re-import purposes
// 		UAssetImportData* ImportData = UTileMapAssetImportData::GetImportDataForTileMap(Result);
// 		ImportData->SourceFilePath = FReimportManager::SanitizeImportFilename(CurrentFilename, Result);
// 		ImportData->SourceFileTimestamp = IFileManager::Get().GetTimeStamp(*CurrentFilename).ToString();
	}

	FEditorDelegates::OnAssetPostImport.Broadcast(this, Result);

	return Result;
}