Download

What happened to USkeletalMesh in UE 4.19?

I’m porting a plugin written for UE 4.16 to UE 4.19, and I’ve seen a lot of things have changed for the class [FONT=courier new]USkeletalMesh (and other related classes).

For example, in UE 4.16 I have:


[FONT=courier new]FStaticLODModel* LodModel = new FStaticLODModel();
SkeletalMesh->GetImportedResource()->LODModels.Add(LodModel);

In UE 4.19 I can’t see anymore [FONT=courier new]FStaticLODModel, so I thought it has been replaced with [FONT=courier new]FSkeletalMeshLODModel; also the method [FONT=courier new]GetImportedResource() has disappeared in UE 4.19, and the most similar method I found is [FONT=courier new]GetImportedModel().
So the previous two rows may be replaced with:


[FONT=courier new]FSkeletalMeshLODModel* LodModel = new FSkeletalMeshLODModel();
SkeletalMesh->GetImportedModel()->LODModels.Add(LodModel);

[HR][/HR]
Another thing is, in UE 4.16 I have this piece of code:


[FONT=courier new]LodModel->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t));
LodModel->NumVertices = data->vertexCount;
LodModel->NumTexCoords = 1;

for (uint32_t index = 0; index < data->indexCount; index++)
{
    LodModel->MultiSizeIndexContainer.GetIndexBuffer()->AddItem(data->indexBuffer[index]);
}

In UE 4.19 I can’t find the field [FONT=courier new]MultiSizeIndexContainer, but I find an [FONT=courier new]IndexBuffer attribute, so I replaced this snippet with:


[FONT=courier new]LodModel->NumVertices = data->vertexCount;
LodModel->NumTexCoords = 1;

for (uint32_t index = 0; index < data->indexCount; index++)
{
    LodModel->IndexBuffer.Add(data->indexBuffer[index]);
}

[HR][/HR]
The last thing (the most critical one) is this piece of code in UE 4.16:


[FONT=courier new]const uint32 vert_flags = FStaticLODModel::EVertexFlags::None | FStaticLODModel::EVertexFlags::UseFullPrecisionUVs;
LodModel->BuildVertexBuffers(vert_flags);

SkeletalMesh->Skeleton = NewObject<USkeleton>();
SkeletalMesh->Skeleton->MergeAllBonesToBoneTree(SkeletalMesh);
SkeletalMesh->PostLoad();

In UE 4.19 the method [FONT=courier new]BuildVertexBuffers has disappeared, and I’ve found nothing to replace it… so I just wrote this:


[FONT=courier new]SkeletalMesh->bUseFullPrecisionUVs = true;
SkeletalMesh->bHasVertexColors = false;

SkeletalMesh->Skeleton = NewObject<USkeleton>();
SkeletalMesh->Skeleton->MergeAllBonesToBoneTree(SkeletalMesh);
SkeletalMesh->PostLoad();

It compiles correctly, but it randomly crashes when cycling on the SkeletalMesh’s IndexBuffer, also whether I’m using the same meshes, so I think there is something I’m not replacing correctly…
May someone help me to understand what has changed in UE 4.19 and how to correctly replace those snippets of code?

Quite frustrating that none of these structural changes are documented in the release notes. We do procedural mesh stuff with Skeletal meshes, and have to deal with the same changes that you are.

@DanielW is there any documentation about these changes? Could Marcus Wassmer or anyone else involved in the changes please weight in? :slight_smile:

I’ll try and give an overview of the USkeletalMesh changes in 4.19, what we did and why.

The main idea here is that we want to move the SkeletalMesh render data into the Derived Data Cache (DDC) in a similar way to how StaticMesh works. This makes it much easier to make changes to the render buffer layout. To this end we split FSkeletalMeshResource (which was both the ‘source’ and the ‘derived’ data) into FSkeletalMeshModel (source data, only available in editor builds) and FSkeletalMeshRenderData (the actual data needed at runtime for rendering). For each LOD we now have a FSkeletalMeshLODModel (editor only data, within the FSkeletalMeshModel) and a FSkeletalMeshLODRenderData (derived render data, within the FSkeletalMeshRenderData). These are the replacement for the old, confusingly named FStaticLODModel. The derived data is transient - it is re-derived any time the source data changes.

So when you are updating your code, the main question is - “is this runtime or editor time code”. If you want to work on the source data in the editor, you need to work on FSkeletalMeshModel. If your code is supposed to run in cooked game builds, then you can only work with FSkeletalMeshRenderData. So for example, the mesh merging code all works with FSkeletalMeshRenderData because it has to work at runtime. All the editor code now operates on FSkeletalMeshModel, and triggers a rebuild of the derived data so you can see the results (you can see USkeletalMesh::PostEditChangeProperty calls InvalidateRenderData which updates the GUID on the source data and re-generates the derived data for rendering.

I hope that helps!

One thing to check would be the c++ Transition guides rama posts in the forum every engine version…
eg. c++ 4.17 Transition Guide - C++ Gameplay Programming - Unreal Engine Forums

@JamesG
​​​​​​​

This post has been very helpful. The reason of the change is clear and also the difference between editor and built behavior. We now have the plugin working in editor, but I cannot find some “equivalent” behaviour for the cooked game build.

i.e.
We have the following behaviour for the editor:


FSkeletalMeshLODModel* LodModel = new FSkeletalMeshLODModel();
...
FSkelMeshSection LodMeshSection = LodModel->Sections[0];
FSoftSkinVertex* DestVertex = LodMeshSection.SoftVertices.GetData();
for (uint32_t VertIndex = 0; VertIndex < data->vertexCount; VertIndex++, SourceVertex++, DestVertex++)
{
    DestVertex->Position = 100.0f * FVector(-SourceVertex->z, SourceVertex->x, SourceVertex->y);
}

For the cooked game we are missing something:


FSkeletalMeshLODRenderData* LodRenderData = new FSkeletalMeshLODRenderData();
...
FSkelMeshRenderSection MeshSection = LodRenderData->RenderSections[0];
// FSkelMeshRenderSection is missing a SoftVertices param; what to do?

After adding all the vertexes to the mesh, we call the following method for the editor (which replace the old [FONT=courier new]LodModel->BuildVertexBuffers()):


SkeletalMesh->PostEditChange();

but that method is available only in the editor, what to do for the cooked game build?

Many many thanks for your help!

Like @masmil1988, I had the idea of putting both editor/runtime meshes in the same function. I think the biggest drawbacks are:
Editor: MultiSizeIndexContainer replaced by IndexBuffer == doesn’t work very well (empty array)
Editor: BuildVertexBuffers no longer there

and with
Runtime: TriangleSortSettings not in LodInfo
Runtime: SoftVertices not in RenderMeshSection

Not sure for PostEditChange();
Usefulness of LodRender->BuildFromLODModel(LodModel,vert_flags) ? Why reconstruct runtime mesh with editor mesh??

Anyway; here’s the code now. //RUNTIME should be use during runtime instead of LodModel related functions. We should be able to claim victory soon!



void UOvrAvatar::LoadMesh(USkeletalMesh* SkeletalMesh, const ovrAvatarMeshAssetData* data)
{
UE_LOG(LogAvatars, Warning, TEXT("[Avatars] Loaded Mesh."));

//NEW CODE FROM 4.19
FSkeletalMeshLODModel* LodModel = new FSkeletalMeshLODModel();
SkeletalMesh->GetImportedModel()->LODModels.Add(LodModel);
FSkeletalMeshLODRenderData* LodRender = new FSkeletalMeshLODRenderData();
SkeletalMesh->GetResourceForRendering()->LODRenderData.Add(LodRender);

/* REPLACE OLD CODE FROM 4.18
FStaticLODModel* LodModel = new FStaticLODModel();
SkeletalMesh->GetImportedResource()->LODModels.Add(LodModel);
*/

new(LodModel->Sections) FSkelMeshSection();
LodModel->Sections[0].MaterialIndex = 0;
LodModel->Sections[0].BaseIndex = 0;
LodModel->Sections[0].NumTriangles = 0;

//RUNTIME//new(LodRender->RenderSections) FSkelMeshRenderSection();
//RUNTIME//LodRender->RenderSections[0].MaterialIndex = 0;
//RUNTIME//LodRender->RenderSections[0].BaseIndex = 0;
//RUNTIME//LodRender->RenderSections[0].NumTriangles = 0;

SkeletalMesh->LODInfo.Add(FSkeletalMeshLODInfo());
FSkeletalMeshLODInfo& LodInfo = SkeletalMesh->LODInfo[0];

LodInfo.ScreenSize = 0.3f;
LodInfo.LODHysteresis = 0.2f;
/* REPLACE OLD CODE FROM 4.18
LodInfo.TriangleSortSettings.Add(FTriangleSortSettings());
*/
LodInfo.LODMaterialMap.Add(0);

SkeletalMesh->Materials.Add(UMaterial::GetDefaultMaterial(MD_Surface));
SkeletalMesh->RefSkeleton.Empty(data->skinnedBindPose.jointCount);
/* REPLACE OLD CODE FROM 4.18
SkeletalMesh->RefSkeleton.Allocate(data->skinnedBindPose.jointCount);
*/

SkeletalMesh->bUseFullPrecisionUVs = true;
SkeletalMesh->bHasBeenSimplified = false;
SkeletalMesh->bHasVertexColors = false;

for (uint32_t BoneIndex = 0; BoneIndex < data->skinnedBindPose.jointCount; BoneIndex++)
{
LodModel->RequiredBones.Add(BoneIndex);
LodModel->ActiveBoneIndices.Add(BoneIndex);
LodModel->Sections[0].BoneMap.Add(BoneIndex);

//RUNTIME//LodRender->RequiredBones.Add(BoneIndex);
//RUNTIME//LodRender->ActiveBoneIndices.Add(BoneIndex);
//RUNTIME//LodRender->RenderSections[0].BoneMap.Add(BoneIndex);

FString BoneString = data->skinnedBindPose.jointNames[BoneIndex];
FName BoneName = FName(*BoneString);

FTransform Transform = FTransform::Identity;
OvrAvatarHelpers::ConvertTransform(data->skinnedBindPose.jointTransform[BoneIndex], Transform);

FReferenceSkeletonModifier Modifier = FReferenceSkeletonModifier(SkeletalMesh->RefSkeleton, nullptr);
Modifier.Add(FMeshBoneInfo(BoneName, BoneString, data->skinnedBindPose.jointParents[BoneIndex]), Transform);
}

check(data->indexCount % 3 == 0);
check(data->vertexCount > 0);


auto& MeshSection = LodModel->Sections[0];
MeshSection.BaseIndex = 0;
MeshSection.NumTriangles = data->indexCount / 3;
MeshSection.BaseVertexIndex = 0;
MeshSection.NumVertices = data->vertexCount;
MeshSection.MaxBoneInfluences = 4;
MeshSection.SoftVertices.SetNumUninitialized(data->vertexCount);

//RUNTIME//auto& RenderMeshSection = LodRender->RenderSections[0];
//RUNTIME//RenderMeshSection.BaseIndex = 0;
//RUNTIME//RenderMeshSection.NumTriangles = data->indexCount / 3;
//RUNTIME//RenderMeshSection.BaseVertexIndex = 0;
//RUNTIME//RenderMeshSection.NumVertices = data->vertexCount;
//RUNTIME//RenderMeshSection.MaxBoneInfluences = 4;

const ovrAvatarMeshVertex* SourceVertex = data->vertexBuffer;
const uint32_t NumBlendWeights = 4;

FSoftSkinVertex* DestVertex = MeshSection.SoftVertices.GetData();

FBox BoundBox = FBox();
BoundBox.Init();

for (uint32_t VertIndex = 0; VertIndex < data->vertexCount; VertIndex++, SourceVertex++, DestVertex++)
{
DestVertex->Position = 100.0f * FVector(-SourceVertex->z, SourceVertex->x, SourceVertex->y);

BoundBox += DestVertex->Position;

FVector n = FVector(-SourceVertex->nz, SourceVertex->nx, SourceVertex->ny);
FVector t = FVector(-SourceVertex->tz, SourceVertex->tx, SourceVertex->ty);
FVector bt = FVector::CrossProduct(t, n) * FMath::Sign(SourceVertex->tw);
DestVertex->TangentX = FPackedNormal(t);
DestVertex->TangentY = FPackedNormal(bt);
DestVertex->TangentZ = FPackedNormal(n);
DestVertex->UVs[0] = FVector2D(SourceVertex->u, SourceVertex->v);

for (uint32_t BlendIndex = 0; BlendIndex < MAX_TOTAL_INFLUENCES; BlendIndex++)
{
DestVertex->InfluenceWeights[BlendIndex] = BlendIndex < NumBlendWeights ? (uint8_t)(255.0f*SourceVertex->blendWeights[BlendIndex]) : 0;
DestVertex->InfluenceBones[BlendIndex] = BlendIndex < NumBlendWeights ? SourceVertex->blendIndices[BlendIndex] : 0;
}
}

/* INITIALIZE/CREATE BUFFER LIKE IN 4.18???
LodModel->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t));
*/
LodModel->NumVertices = data->vertexCount;
LodModel->NumTexCoords = 1;
//RUNTIME//LodRender->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t));
//RUNTIME//LodRender->GetNumVertices = data->vertexCount;
//RUNTIME//LodRender->GetNumTexCoords = 1;

for (uint32_t index = 0; index < data->indexCount; index++)
{
/* REPLACE OLD CODE FROM 4.18
LodModel->MultiSizeIndexContainer.GetIndexBuffer()->AddItem(data->indexBuffer[index]);
*/
LodModel->IndexBuffer.Add(data->indexBuffer[index]);
//RUNTIME//LodRender->MultiSizeIndexContainer.GetIndexBuffer()->AddItem(data->indexBuffer[index]);
}

FBoxSphereBounds Bounds(BoundBox);
Bounds = Bounds.ExpandBy(100000.0f);
SkeletalMesh->SetImportedBounds(Bounds);

/* REPLACE OLD CODE FROM 4.18
const uint32 vert_flags = FStaticLODModel::EVertexFlags::None | FStaticLODModel::EVertexFlags::UseFullPrecisionUVs;
//LodModel->BuildVertexBuffers(vert_flags);
*/
uint32 vert_flags = ESkeletalMeshVertexFlags::None | ESkeletalMeshVertexFlags::UseFullPrecisionUVs;
LodRender->BuildFromLODModel(LodModel,vert_flags);

//SkeletalMesh->PostEditChange();
SkeletalMesh->Skeleton = NewObject<USkeleton>();
SkeletalMesh->Skeleton->MergeAllBonesToBoneTree(SkeletalMesh);
SkeletalMesh->PostLoad();
}


Oh please… not again. I was really hoping that if they ever change all the skeletal mesh code again, there would be a transition help with deprecation warnings etc. But there’s absolutely NOTHING. It’s not even mentioned in the version history. Now I have around 5000 lines of code dealing with procedural skeletal mesh analysis and manipulation at runtime that are basically one big error.

Has anyone figured out yet where the section vertices have gone? I used to be able to get vertex data by accessing FStaticLODModel::Sections[x]::SoftVertices. But there are no vertices in FStaticLODRenderData::RenderSections[x].

And I can’t even test anything, because I need to fix 5000 lines of code before I can even compile my project - without any info / documentation on how to fix it. I need to create a new project with test code just to figure out what has been changed and what I can use instead of the old structures now. What a f*cking nightmare.

For example, there is StaticVertexBuffer inside FSkeletalMeshLODRenderData. Maybe that is what I have to use now, if I want to access vertices for reading and/or modifying. Or maybe that buffer is just as empty as the index-buffer. I can’t rewrite the entire code without even knowing if I’m on the right track. Wow… Thanks a lot. Might as well just dump my whole project.

@Proteus (and everyone else)

Are you sure the index buffer is still empty in no-editor builds in 4.19 [edit: I just realised you were talking about the “model”, not the “render data” - my index-array isn’t empty though]? I remember this being the case in earlier versions, which is why I had to write code to store this data from editor-builds for every mesh and restore it on loading in no-editor-builds, so I could work with it there. However, I just built a test project from the 3rd Person Template and it looks like indices are now contained at runtime in the “RenderData”. Can someone please confirm this? Maybe I just did something differently…

Also, as everyone probably knows already, the vertex-buffers are no longer kept for each section seperately and seperated into buffers for positions, weights, colors, so there are no FSoftVertices here anymore. I could be wrong, but the best way to update the model at runtime after making changes to the data is to simply call mesh->ReleaseResources() and mesh->InitResources() while suspending the render thread with FSuspendRenderThread to avoid rare crashes (because I think someone asked about this).

edit: Is it even a good time to port now or is it likely that more interface changes regarding skeletal meshes will be made in the near future? If that’s the case, I would rather skip 4.19 and wait until everything is done than adjusting all my code more than once.

Ok, it’s a pain, but I am trying to adjust most of my code to the new FSkeletalMeshRenderData. Since there are no FSoftSkinVertices here to work with anymore, I am now using

FSkeletalMeshLODRenderData::StaticVertexBuffers.PositionVertexBuffer
FSkeletalMeshLODRenderData::StaticVertexBuffers.StaticMeshVertexBuffer
FSkeletalMeshLODRenderData::StaticVertexBuffers.ColorVertexBuffer
FSkeletalMeshLODRenderData::SkinWeightVertexBuffer

for modifying skeletal meshes at runtime. I am still far from being able to compile my project without errors, and there’s already a problem that doesn’t seem solvable. All the buffers except the one for skin weights have an overloaded Init-function that takes an integer as parameter and sets the buffer size accordingly, so you can scale the vertex count up or down. For the skin weights there is no such overload. I need to dynamically add and remove RenderSections on meshes, and so I must be able to change the vertex count as well. What am I supposed to do?

The class FSkinWeightVertexBuffer has not an [FONT=courier new]Init method available for non-editor builds, but has an override of [FONT=courier new]operator=, through which you can assign a [FONT=courier new]TArray<TSkinWeightInfo<bool>> to the SkinWeightVertexBuffer’s [FONT=courier new]WeightData.

I get a linker error for FSkinWeightVertexBuffer::AllocateData(void) called from FSkinWeightVertexBuffer:: operator=

The module should be engine, so I don’t know what to do. So much trouble for nothing again with this update…

I wrote the following snippet for assigning the SkingWeightVertexBuffer weights:


    TArray<TSkinWeightInfo<true>> InWeights;
    InWeights.AddUninitialized(data->vertexCount);

    for (uint32_t VertIndex = 0; VertIndex < data->vertexCount; VertIndex++, SourceVertex++)
    {
        FModelVertex ModelVertex;
        ModelVertex.Position = 100.0f * FVector(-SourceVertex->z, SourceVertex->x, SourceVertex->y);
        BoundBox += ModelVertex.Position;

        FVector n = FVector(-SourceVertex->nz, SourceVertex->nx, SourceVertex->ny);
        FVector t = FVector(-SourceVertex->tz, SourceVertex->tx, SourceVertex->ty);
        FVector bt = FVector::CrossProduct(t, n) * FMath::Sign(SourceVertex->tw);
        ModelVertex.TangentX = FPackedNormal(t);
        ModelVertex.TangentZ = FPackedNormal(n);
        ModelVertex.TexCoord = FVector2D(SourceVertex->u, SourceVertex->v);

        LodRenderData->StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertIndex) = ModelVertex.Position;
        LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertIndex, ModelVertex.TangentX, ModelVertex.GetTangentY(), ModelVertex.TangentZ);
        LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertIndex, 0, ModelVertex.TexCoord);

        for (uint32_t BlendIndex = 0; BlendIndex < MAX_TOTAL_INFLUENCES; BlendIndex++)
        {
            InWeights[VertIndex].InfluenceWeights[BlendIndex] = BlendIndex < NumBlendWeights ? (uint8_t)(255.0f*SourceVertex->blendWeights[BlendIndex]) : 0;
            InWeights[VertIndex].InfluenceBones[BlendIndex] = BlendIndex < NumBlendWeights ? SourceVertex->blendIndices[BlendIndex] : 0;
        }
    }
    LodRenderData->SkinWeightVertexBuffer.SetHasExtraBoneInfluences(true);
    LodRenderData->SkinWeightVertexBuffer = InWeights;

I still miss something. I still have a crash in the non-editor build, on [FONT=courier new]SkeletalMesh->PostLoad() … A help, or a transition guide would be appreciated.

The code so far (only for runtime - I guess that FSkeletalMeshLODRenderData is the way to go now).
linker error for FSkinWeightVertexBuffer::AllocateData(void) called from FSkinWeightVertexBuffer:: operator= still there.
We are making progress.



void UOvrAvatar::LoadMesh(USkeletalMesh* SkeletalMesh, const ovrAvatarMeshAssetData* data)
{
UE_LOG(LogAvatars, Warning, TEXT("[Avatars] Loaded Mesh."));

/*EDITOR FSkeletalMeshLODModel* LodModel = new FSkeletalMeshLODModel();
SkeletalMesh->GetImportedModel()->LODModels.Add(LodModel);*/
FSkeletalMeshLODRenderData* LodRender = new FSkeletalMeshLODRenderData();
SkeletalMesh->GetResourceForRendering()->LODRenderData.Add(LodRender);

/* REPLACE OLD CODE FROM 4.18
FStaticLODModel* LodModel = new FStaticLODModel();
SkeletalMesh->GetImportedResource()->LODModels.Add(LodModel);
*/

/*EDITOR new(LodModel->Sections) FSkelMeshSection();
LodModel->Sections[0].MaterialIndex = 0;
LodModel->Sections[0].BaseIndex = 0;
LodModel->Sections[0].NumTriangles = 0;*/

new(LodRender->RenderSections) FSkelMeshRenderSection();
LodRender->RenderSections[0].MaterialIndex = 0;
LodRender->RenderSections[0].BaseIndex = 0;
LodRender->RenderSections[0].NumTriangles = 0;

SkeletalMesh->LODInfo.Add(FSkeletalMeshLODInfo());
FSkeletalMeshLODInfo& LodInfo = SkeletalMesh->LODInfo[0];

LodInfo.ScreenSize = 0.3f;
LodInfo.LODHysteresis = 0.2f;
/* REMOVED IN 4.19
LodInfo.TriangleSortSettings.Add(FTriangleSortSettings());
*/
LodInfo.LODMaterialMap.Add(0);

SkeletalMesh->Materials.Add(UMaterial::GetDefaultMaterial(MD_Surface));
SkeletalMesh->RefSkeleton.Empty(data->skinnedBindPose.jointCount);
/* REPLACE OLD CODE FROM 4.18
SkeletalMesh->RefSkeleton.Allocate(data->skinnedBindPose.jointCount);
*/

SkeletalMesh->bUseFullPrecisionUVs = true;
SkeletalMesh->bHasBeenSimplified = false;
SkeletalMesh->bHasVertexColors = false;

for (uint32_t BoneIndex = 0; BoneIndex < data->skinnedBindPose.jointCount; BoneIndex++)
{
/*EDITOR LodModel->RequiredBones.Add(BoneIndex);
LodModel->ActiveBoneIndices.Add(BoneIndex);
LodModel->Sections[0].BoneMap.Add(BoneIndex);*/

LodRender->RequiredBones.Add(BoneIndex);
LodRender->ActiveBoneIndices.Add(BoneIndex);
LodRender->RenderSections[0].BoneMap.Add(BoneIndex);

FString BoneString = data->skinnedBindPose.jointNames[BoneIndex];
FName BoneName = FName(*BoneString);

FTransform Transform = FTransform::Identity;
OvrAvatarHelpers::ConvertTransform(data->skinnedBindPose.jointTransform[BoneIndex], Transform);

FReferenceSkeletonModifier Modifier = FReferenceSkeletonModifier(SkeletalMesh->RefSkeleton, nullptr);
Modifier.Add(FMeshBoneInfo(BoneName, BoneString, data->skinnedBindPose.jointParents[BoneIndex]), Transform);
}

check(data->indexCount % 3 == 0);
check(data->vertexCount > 0);


/* EDITOR auto& MeshSection = LodModel->Sections[0];
MeshSection.BaseIndex = 0;
MeshSection.NumTriangles = data->indexCount / 3;
MeshSection.BaseVertexIndex = 0;
MeshSection.NumVertices = data->vertexCount;
MeshSection.MaxBoneInfluences = 4;

MeshSection.SoftVertices.SetNumUninitialized(data->vertexCount);*/

auto& RenderMeshSection = LodRender->RenderSections[0];
RenderMeshSection.BaseIndex = 0;
RenderMeshSection.NumTriangles = data->indexCount / 3;
RenderMeshSection.BaseVertexIndex = 0;
RenderMeshSection.NumVertices = data->vertexCount;
RenderMeshSection.MaxBoneInfluences = 4;
//REMOVED IN 4.19//RenderMeshSection.SoftVertices.SetNumUninitialized(data->vertexCount);

const ovrAvatarMeshVertex* SourceVertex = data->vertexBuffer;
const uint32_t NumBlendWeights = 4;

//EDITOR FSoftSkinVertex* DestVertex = MeshSection.SoftVertices.GetData();

FBox BoundBox = FBox();
BoundBox.Init();

/*EDITOR for (uint32_t VertIndex = 0; VertIndex < data->vertexCount; VertIndex++, SourceVertex++, DestVertex++)
{
DestVertex->Position = 100.0f * FVector(-SourceVertex->z, SourceVertex->x, SourceVertex->y);

BoundBox += DestVertex->Position;

FVector n = FVector(-SourceVertex->nz, SourceVertex->nx, SourceVertex->ny);
FVector t = FVector(-SourceVertex->tz, SourceVertex->tx, SourceVertex->ty);
FVector bt = FVector::CrossProduct(t, n) * FMath::Sign(SourceVertex->tw);
DestVertex->TangentX = FPackedNormal(t);
DestVertex->TangentY = FPackedNormal(bt);
DestVertex->TangentZ = FPackedNormal(n);
DestVertex->UVs[0] = FVector2D(SourceVertex->u, SourceVertex->v);

for (uint32_t BlendIndex = 0; BlendIndex < MAX_TOTAL_INFLUENCES; BlendIndex++)
{
DestVertex->InfluenceWeights[BlendIndex] = BlendIndex < NumBlendWeights ? (uint8_t)(255.0f*SourceVertex->blendWeights[BlendIndex]) : 0;
DestVertex->InfluenceBones[BlendIndex] = BlendIndex < NumBlendWeights ? SourceVertex->blendIndices[BlendIndex] : 0;
}
}*/

TArray<TSkinWeightInfo<true>> InWeights;
InWeights.AddUninitialized(data->vertexCount);

for (uint32_t VertIndex = 0; VertIndex < data->vertexCount; VertIndex++, SourceVertex++)
{
FModelVertex ModelVertex;
ModelVertex.Position = 100.0f * FVector(-SourceVertex->z, SourceVertex->x, SourceVertex->y);
BoundBox += ModelVertex.Position;

FVector n = FVector(-SourceVertex->nz, SourceVertex->nx, SourceVertex->ny);
FVector t = FVector(-SourceVertex->tz, SourceVertex->tx, SourceVertex->ty);
FVector bt = FVector::CrossProduct(t, n) * FMath::Sign(SourceVertex->tw);
ModelVertex.TangentX = FPackedNormal(t);
ModelVertex.TangentZ = FPackedNormal(n);
ModelVertex.TexCoord = FVector2D(SourceVertex->u, SourceVertex->v);

LodRender->StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertIndex) = ModelVertex.Position;
LodRender->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertIndex, ModelVertex.TangentX, ModelVertex.GetTangentY(), ModelVertex.TangentZ);
LodRender->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertIndex, 0, ModelVertex.TexCoord);

for (uint32_t BlendIndex = 0; BlendIndex < MAX_TOTAL_INFLUENCES; BlendIndex++)
{
InWeights[VertIndex].InfluenceWeights[BlendIndex] = BlendIndex < NumBlendWeights ? (uint8_t)(255.0f*SourceVertex->blendWeights[BlendIndex]) : 0;
InWeights[VertIndex].InfluenceBones[BlendIndex] = BlendIndex < NumBlendWeights ? SourceVertex->blendIndices[BlendIndex] : 0;
}
}
LodRender->SkinWeightVertexBuffer.SetHasExtraBoneInfluences(true);
LodRender->SkinWeightVertexBuffer = InWeights;

/* REMOVED IN 4.19
LodModel->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t));
*/
/*EDITOR LodModel->NumVertices = data->vertexCount;
LodModel->NumTexCoords = 1;*/

LodRender->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t));
LodRender->RenderSections[0].NumVertices = data->vertexCount;
//REMOVED IN 4.19 LodModel->NumTexCoords = 1;

for (uint32_t index = 0; index < data->indexCount; index++)
{

//EDITOR LodModel->IndexBuffer.Add(data->indexBuffer[index]);
LodRender->MultiSizeIndexContainer.GetIndexBuffer()->AddItem(data->indexBuffer[index]);
}

FBoxSphereBounds Bounds(BoundBox);
Bounds = Bounds.ExpandBy(100000.0f);
SkeletalMesh->SetImportedBounds(Bounds);

/* REPLACE OLD CODE FROM 4.18
const uint32 vert_flags = FStaticLODModel::EVertexFlags::None | FStaticLODModel::EVertexFlags::UseFullPrecisionUVs;
//LodModel->BuildVertexBuffers(vert_flags);
*/
uint32 vert_flags = ESkeletalMeshVertexFlags::None | ESkeletalMeshVertexFlags::UseFullPrecisionUVs;
//NOT USED SINCE WE ARE ONLY RUNTIME?? LodRender->BuildFromLODModel(LodModel,vert_flags);
//INITRESSOURCES?? LodRender->InitResources(false, )
//RELEASE RESSOURCES ?? LodRender->ReleaseResources();


//POSTEDITCHANGES?? SkeletalMesh->PostEditChange();
SkeletalMesh->Skeleton = NewObject<USkeleton>();
SkeletalMesh->Skeleton->MergeAllBonesToBoneTree(SkeletalMesh);
SkeletalMesh->PostLoad();
}


I tried everything, but I can’t fix the linker error for FSkinWeightVertexBuffer::AllocateData(void) when using the = operator. I don’t know if this has something to do with the templated nature of the operator-function. Neither AllocateData() nor FSkinWeightVertexBuffer itself are templated, though. I even created a new project and copied my code over without any success. In my 4.19 test project the error has now appeared, too. This is so stupid… a dev statement would be nice before I go insane over some error that can’t be fixed.

How are you NOT getting a linker error for this line?

LodRenderData->SkinWeightVertexBuffer = InWeights;

Did you include anything special or added some module?

Nothing, just make sure that the boolean param in


TArray<TSkinWeightInfo<BOOLEAN>> InWeights;

is the same you pass to


LodRenderData->SkinWeightVertexBuffer.SetHasExtraBoneInfluences(BOOLEAN);

Actually the target is the call to [FONT=courier new]SkeletalMesh->InvalidateRenderData(), but it is a private method, so it has to be called indirectly. [FONT=courier new]PostEditChange is the only way I found to call it…

I have. It doesn’t make a difference.

I don’t know what to say. This is the actual status of my code, working in editor. You can also see the clear separation between editor and non-editor code:


#if WITH_EDITOR
    FSkeletalMeshLODModel* LodModel = new FSkeletalMeshLODModel();
    SkeletalMesh->GetImportedModel()->LODModels.Add(LodModel);
#else
    FSkeletalMeshLODRenderData* LodRenderData = new FSkeletalMeshLODRenderData();
    SkeletalMesh->AllocateResourceForRendering();
    SkeletalMesh->GetResourceForRendering()->LODRenderData.Add(LodRenderData);
#endif

#if WITH_EDITOR
    new(LodModel->Sections) FSkelMeshSection();
    LodModel->Sections[0].MaterialIndex = 0;
    LodModel->Sections[0].BaseIndex = 0;
    LodModel->Sections[0].NumTriangles = 0;
#else
    new(LodRenderData->RenderSections) FSkelMeshRenderSection();
    LodRenderData->RenderSections[0].MaterialIndex = 0;
    LodRenderData->RenderSections[0].BaseIndex = 0;
    LodRenderData->RenderSections[0].NumTriangles = 0;
#endif

    SkeletalMesh->LODInfo.Add(FSkeletalMeshLODInfo());
    FSkeletalMeshLODInfo& LodInfo = SkeletalMesh->LODInfo[0];

    LodInfo.ScreenSize = 0.3f;
    LodInfo.LODHysteresis = 0.2f;

    //LodInfo.TriangleSortSettings.Add(FTriangleSortSettings());
    LodInfo.LODMaterialMap.Add(0);

    SkeletalMesh->Materials.Add(UMaterial::GetDefaultMaterial(MD_Surface));
    //SkeletalMesh->RefSkeleton.Allocate(data->skinnedBindPose.jointCount);

    SkeletalMesh->bUseFullPrecisionUVs = true;
    SkeletalMesh->bHasBeenSimplified = false;
    SkeletalMesh->bHasVertexColors = false;

    for (int BoneIndex = 0; BoneIndex < (int)data->skinnedBindPose.jointCount; BoneIndex++)
    {
#if WITH_EDITOR
        LodModel->RequiredBones.Add(BoneIndex);
        LodModel->ActiveBoneIndices.Add(BoneIndex);
        LodModel->Sections[0].BoneMap.Add(BoneIndex);
#else
        LodRenderData->RequiredBones.Add(BoneIndex);
        LodRenderData->ActiveBoneIndices.Add(BoneIndex);
        LodRenderData->RenderSections[0].BoneMap.Add(BoneIndex);
#endif

        FString BoneString = data->skinnedBindPose.jointNames[BoneIndex];
        FName BoneName = FName(*BoneString);

        FTransform Transform = FTransform::Identity;
        OvrAvatarHelpers::ConvertTransform(data->skinnedBindPose.jointTransform[BoneIndex], Transform);

        FReferenceSkeletonModifier Modifier = FReferenceSkeletonModifier(SkeletalMesh->RefSkeleton, nullptr);
        Modifier.Add(FMeshBoneInfo(BoneName, BoneString, data->skinnedBindPose.jointParents[BoneIndex]), Transform);
    }

    check(data->indexCount % 3 == 0);
    check(data->vertexCount > 0);

#if WITH_EDITOR
    auto& MeshSection = LodModel->Sections[0];
    MeshSection.BaseIndex = 0;
    MeshSection.NumTriangles = data->indexCount / 3;
    MeshSection.BaseVertexIndex = 0;
    MeshSection.NumVertices = data->vertexCount;
    MeshSection.MaxBoneInfluences = 4;
#else
    auto& RenderMeshSection = LodRenderData->RenderSections[0];
    RenderMeshSection.BaseIndex = 0;
    RenderMeshSection.NumTriangles = data->indexCount / 3;
    RenderMeshSection.BaseVertexIndex = 0;
    RenderMeshSection.NumVertices = data->vertexCount;
    RenderMeshSection.MaxBoneInfluences = 4;
#endif

    const ovrAvatarMeshVertex* SourceVertex = data->vertexBuffer;
    const uint32_t NumBlendWeights = 4;

    FBox BoundBox = FBox();
    BoundBox.Init();

#if WITH_EDITOR
    MeshSection.SoftVertices.SetNumUninitialized(data->vertexCount);
    FSoftSkinVertex* DestVertex = MeshSection.SoftVertices.GetData();

    for (uint32_t VertIndex = 0; VertIndex < data->vertexCount; VertIndex++, SourceVertex++, DestVertex++)
    {
        DestVertex->Position = 100.0f * FVector(-SourceVertex->z, SourceVertex->x, SourceVertex->y);

        BoundBox += DestVertex->Position;

        FVector n = FVector(-SourceVertex->nz, SourceVertex->nx, SourceVertex->ny);
        FVector t = FVector(-SourceVertex->tz, SourceVertex->tx, SourceVertex->ty);
        FVector bt = FVector::CrossProduct(t, n) * FMath::Sign(SourceVertex->tw);
        DestVertex->TangentX = FPackedNormal(t);
        DestVertex->TangentY = FPackedNormal(bt);
        DestVertex->TangentZ = FPackedNormal(n);
        DestVertex->UVs[0] = FVector2D(SourceVertex->u, SourceVertex->v);

        for (uint32_t BlendIndex = 0; BlendIndex < MAX_TOTAL_INFLUENCES; BlendIndex++)
        {
            DestVertex->InfluenceWeights[BlendIndex] = BlendIndex < NumBlendWeights ? (uint8_t)(255.0f*SourceVertex->blendWeights[BlendIndex]) : 0;
            DestVertex->InfluenceBones[BlendIndex] = BlendIndex < NumBlendWeights ? SourceVertex->blendIndices[BlendIndex] : 0;
        }
    }
#else
    LodRenderData->StaticVertexBuffers.PositionVertexBuffer.Init(data->vertexCount);
    LodRenderData->StaticVertexBuffers.ColorVertexBuffer.Init(data->vertexCount);
    LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.Init(data->vertexCount, 1);

    TArray<TSkinWeightInfo<true>> InWeights;
    InWeights.AddUninitialized(data->vertexCount);

    for (uint32_t VertIndex = 0; VertIndex < data->vertexCount; VertIndex++, SourceVertex++)
    {
        FModelVertex ModelVertex;
        ModelVertex.Position = 100.0f * FVector(-SourceVertex->z, SourceVertex->x, SourceVertex->y);
        BoundBox += ModelVertex.Position;

        FVector n = FVector(-SourceVertex->nz, SourceVertex->nx, SourceVertex->ny);
        FVector t = FVector(-SourceVertex->tz, SourceVertex->tx, SourceVertex->ty);
        FVector bt = FVector::CrossProduct(t, n) * FMath::Sign(SourceVertex->tw);
        ModelVertex.TangentX = FPackedNormal(t);
        ModelVertex.TangentZ = FPackedNormal(n);
        ModelVertex.TexCoord = FVector2D(SourceVertex->u, SourceVertex->v);

        LodRenderData->StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertIndex) = ModelVertex.Position;
        LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertIndex, ModelVertex.TangentX, ModelVertex.GetTangentY(), ModelVertex.TangentZ);
        LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertIndex, 0, ModelVertex.TexCoord);

        for (uint32_t BlendIndex = 0; BlendIndex < MAX_TOTAL_INFLUENCES; BlendIndex++)
        {
            InWeights[VertIndex].InfluenceWeights[BlendIndex] = BlendIndex < NumBlendWeights ? (uint8_t)(255.0f*SourceVertex->blendWeights[BlendIndex]) : 0;
            InWeights[VertIndex].InfluenceBones[BlendIndex] = BlendIndex < NumBlendWeights ? SourceVertex->blendIndices[BlendIndex] : 0;
        }
    }
//    LodRenderData->SkinWeightVertexBuffer.SetNeedsCPUAccess(true);
    LodRenderData->SkinWeightVertexBuffer.SetHasExtraBoneInfluences(true);
    LodRenderData->SkinWeightVertexBuffer = InWeights;
    LodRenderData->StaticVertexBuffers.ColorVertexBuffer.InitFromSingleColor(FColor::Blue, data->vertexCount);
#endif

#if WITH_EDITOR
    LodModel->NumVertices = data->vertexCount;
    LodModel->NumTexCoords = 1;
#else
    LodRenderData->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t));
#endif

    for (uint32_t index = 0; index < data->indexCount; index++)
    {
#if WITH_EDITOR
        LodModel->IndexBuffer.Add(data->indexBuffer[index]);
#else
        LodRenderData->MultiSizeIndexContainer.GetIndexBuffer()->AddItem(data->indexBuffer[index]);
#endif
    }

    FBoxSphereBounds Bounds(BoundBox);
    Bounds = Bounds.ExpandBy(100000.0f);
    SkeletalMesh->SetImportedBounds(Bounds);

    //const uint32 vert_flags = ESkeletalMeshVertexFlags::None | ESkeletalMeshVertexFlags::UseFullPrecisionUVs;
    //LodModel->BuildVertexBuffers(vert_flags);
#if WITH_EDITOR
    SkeletalMesh->PostEditChange();
#else
    // TODO: something is missing here
#endif

    SkeletalMesh->Skeleton = NewObject<USkeleton>();
    SkeletalMesh->Skeleton->MergeAllBonesToBoneTree(SkeletalMesh);
    SkeletalMesh->PostLoad();

In the non-editor build, the application crashes on [FONT=courier new]SkeletalMesh->PostLoad(), I think because of the missing line corresponding to [FONT=courier new]SkeletalMesh->PostEditChange() (for in-editor code)