static void ExportMeshLod(ExportContext& Context, const CBaseMeshLod& Lod, const CMeshVertex* Verts, FArchive& Ar, FArchive& Ar2) { guard(ExportMeshLod); // Opening brace Ar.Printf("{\n"); // Asset Ar.Printf( " \"asset\" : {\n" " \"generator\" : \"UE Viewer (umodel) build %s\",\n" " \"version\" : \"2.0\"\n" " },\n", STR(GIT_REVISION)); // Scene Ar.Printf( " \"scene\" : 0,\n" " \"scenes\" : [\n" " {\n" " \"nodes\" : [ 0 ]\n" " }\n" " ],\n" ); // Nodes if (!Context.IsSkeletal()) { Ar.Printf( " \"nodes\" : [\n" " {\n" " \"name\" : \"%s\",\n" " \"mesh\" : 0\n" " }\n" " ],\n", Context.MeshName ); } else { ExportSkinData(Context, static_cast<const CSkelMeshLod&>(Lod), Ar); } // Materials Ar.Printf(" \"materials\" : [\n"); for (int i = 0; i < Lod.Sections.Num(); i++) { ExportMaterial(Lod.Sections[i].Material, Ar, i, i == Lod.Sections.Num() - 1); } Ar.Printf(" ],\n"); // Meshes Ar.Printf( " \"meshes\" : [\n" " {\n" " \"primitives\" : [\n" ); for (int i = 0; i < Lod.Sections.Num(); i++) { ExportSection(Context, Lod, Verts, i, Ar); } Ar.Printf( " ],\n" " \"name\" : \"%s\"\n" " }\n" " ],\n", Context.MeshName ); // Write animations if (Context.IsSkeletal() && Context.SkelMesh->Anim) { ExportAnimations(Context, Ar); } // Write buffers int bufferLength = 0; for (int i = 0; i < Context.Data.Num(); i++) { bufferLength += Context.Data[i].DataSize; } Ar.Printf( " \"buffers\" : [\n" " {\n" " \"uri\" : \"%s.bin\",\n" " \"byteLength\" : %d\n" " }\n" " ],\n", Context.MeshName, bufferLength ); // Write bufferViews Ar.Printf( " \"bufferViews\" : [\n" ); int bufferOffset = 0; for (int i = 0; i < Context.Data.Num(); i++) { const BufferData& B = Context.Data[i]; Ar.Printf( " {\n" " \"buffer\" : 0,\n" " \"byteOffset\" : %d,\n" " \"byteLength\" : %d\n" " }%s\n", bufferOffset, B.DataSize, i == (Context.Data.Num()-1) ? "" : "," ); bufferOffset += B.DataSize; } Ar.Printf( " ],\n" ); // Write accessors Ar.Printf( " \"accessors\" : [\n" ); for (int i = 0; i < Context.Data.Num(); i++) { const BufferData& B = Context.Data[i]; Ar.Printf( " {\n" " \"bufferView\" : %d,\n", i ); if (B.bNormalized) { Ar.Printf(" \"normalized\" : true,\n"); } if (B.BoundsMin.Len()) { Ar.Printf( " \"min\" : %s,\n" " \"max\" : %s,\n", *B.BoundsMin, *B.BoundsMax ); } Ar.Printf( " \"componentType\" : %d,\n" " \"count\" : %d,\n" " \"type\" : \"%s\"\n" " }%s\n", B.ComponentType, B.Count, B.Type, i == (Context.Data.Num()-1) ? "" : "," ); } Ar.Printf( " ]\n" ); // Write binary data guard(WriteBIN); for (int i = 0; i < Context.Data.Num(); i++) { const BufferData& B = Context.Data[i]; #if MAX_DEBUG assert(B.FillCount == B.Count); #endif Ar2.Serialize(B.Data, B.DataSize); } unguard; // Closing brace Ar.Printf("}\n"); unguard; }
static void ExportSection(ExportContext& Context, const CBaseMeshLod& Lod, const CMeshVertex* Verts, int SectonIndex, FArchive& Ar) { guard(ExportSection); int VertexSize = Context.IsSkeletal() ? sizeof(CSkelMeshVertex) : sizeof(CStaticMeshVertex); const CMeshSection& S = Lod.Sections[SectonIndex]; bool bLast = (SectonIndex == Lod.Sections.Num()-1); // Remap section indices to local indices CIndexBuffer::IndexAccessor_t GetIndex = Lod.Indices.GetAccessor(); TArray<int> indexRemap; // old vertex index -> new vertex index indexRemap.Init(-1, Lod.NumVerts); int numLocalVerts = 0; int numLocalIndices = S.NumFaces * 3; for (int idx = 0; idx < numLocalIndices; idx++) { int vertIndex = GetIndex(S.FirstIndex + idx); if (indexRemap[vertIndex] == -1) { indexRemap[vertIndex] = numLocalVerts++; } } // Prepare buffers int IndexBufIndex = Context.Data.AddZeroed(); int PositionBufIndex = Context.Data.AddZeroed(); int NormalBufIndex = Context.Data.AddZeroed(); int TangentBufIndex = Context.Data.AddZeroed(); int BonesBufIndex = -1; int WeightsBufIndex = -1; if (Context.IsSkeletal()) { BonesBufIndex = Context.Data.AddZeroed(); WeightsBufIndex = Context.Data.AddZeroed(); } int UVBufIndex[MAX_MESH_UV_SETS]; for (int i = 0; i < Lod.NumTexCoords; i++) { UVBufIndex[i] = Context.Data.AddZeroed(); } BufferData& IndexBuf = Context.Data[IndexBufIndex]; BufferData& PositionBuf = Context.Data[PositionBufIndex]; BufferData& NormalBuf = Context.Data[NormalBufIndex]; BufferData& TangentBuf = Context.Data[TangentBufIndex]; BufferData* UVBuf[MAX_MESH_UV_SETS]; BufferData* BonesBuf = NULL; BufferData* WeightsBuf = NULL; PositionBuf.Setup(numLocalVerts, "VEC3", BufferData::FLOAT, sizeof(CVec3)); NormalBuf.Setup(numLocalVerts, "VEC3", BufferData::FLOAT, sizeof(CVec3)); TangentBuf.Setup(numLocalVerts, "VEC4", BufferData::FLOAT, sizeof(CVec4)); for (int i = 0; i < Lod.NumTexCoords; i++) { UVBuf[i] = &Context.Data[UVBufIndex[i]]; UVBuf[i]->Setup(numLocalVerts, "VEC2", BufferData::FLOAT, sizeof(CMeshUVFloat)); } if (Context.IsSkeletal()) { BonesBuf = &Context.Data[BonesBufIndex]; WeightsBuf = &Context.Data[WeightsBufIndex]; BonesBuf->Setup(numLocalVerts, "VEC4", BufferData::UNSIGNED_SHORT, sizeof(uint16)*4); WeightsBuf->Setup(numLocalVerts, "VEC4", BufferData::UNSIGNED_BYTE, sizeof(uint32), /*InNormalized=*/ true); } // Prepare and build indices TArray<int> localIndices; localIndices.AddUninitialized(numLocalIndices); int* pIndex = localIndices.GetData(); for (int i = 0; i < numLocalIndices; i++) { *pIndex++ = GetIndex(S.FirstIndex + i); } if (numLocalVerts <= 65536) { IndexBuf.Setup(numLocalIndices, "SCALAR", BufferData::UNSIGNED_SHORT, sizeof(uint16)); for (int idx = 0; idx < numLocalIndices; idx++) { IndexBuf.Put<uint16>(indexRemap[localIndices[idx]]); } } else { IndexBuf.Setup(numLocalIndices, "SCALAR", BufferData::UNSIGNED_INT, sizeof(uint32)); for (int idx = 0; idx < numLocalIndices; idx++) { IndexBuf.Put<uint32>(indexRemap[localIndices[idx]]); } } // Build reverse index map for fast lookup of vertex by its new index. // It maps new vertex index to old vertex index. TArray<int> revIndexMap; revIndexMap.AddUninitialized(numLocalVerts); for (int i = 0; i < indexRemap.Num(); i++) { int newIndex = indexRemap[i]; if (newIndex != -1) { revIndexMap[newIndex] = i; } } // Build vertices for (int i = 0; i < numLocalVerts; i++) { int vertIndex = revIndexMap[i]; const CMeshVertex& V = VERT(vertIndex); CVec3 Position = V.Position; CVec4 Normal, Tangent; Unpack(Normal, V.Normal); Unpack(Tangent, V.Tangent); // Unreal (and we are) using normal.w for computing binormal. glTF // uses tangent.w for that. Make this value exactly 1.0 of -1.0 to make glTF // validator happy. #if 0 // There's some problem: V.Normal.W == 0x80 -> -1.008 instead of -1.0 if (Normal.w > 1.001 || Normal.w < -1.001) { appError("%X -> %g\n", V.Normal.Data, Normal.w); } #endif Tangent.w = (Normal.w < 0) ? -1 : 1; TransformPosition(Position); TransformDirection(Normal); TransformDirection(Tangent); Normal.Normalize(); Tangent.Normalize(); // Fill buffers PositionBuf.Put(Position); NormalBuf.Put(Normal.xyz); TangentBuf.Put(Tangent); UVBuf[0]->Put(V.UV); } // Compute bounds for PositionBuf CVec3 Mins, Maxs; ComputeBounds((CVec3*)PositionBuf.Data, numLocalVerts, sizeof(CVec3), Mins, Maxs); char buf[256]; appSprintf(ARRAY_ARG(buf), "[ %g, %g, %g ]", VECTOR_ARG(Mins)); PositionBuf.BoundsMin = buf; appSprintf(ARRAY_ARG(buf), "[ %g, %g, %g ]", VECTOR_ARG(Maxs)); PositionBuf.BoundsMax = buf; if (Context.IsSkeletal()) { for (int i = 0; i < numLocalVerts; i++) { int vertIndex = revIndexMap[i]; const CMeshVertex& V0 = VERT(vertIndex); const CSkelMeshVertex& V = static_cast<const CSkelMeshVertex&>(V0); int16 Bones[NUM_INFLUENCES]; static_assert(NUM_INFLUENCES == 4, "Code designed for 4 influences"); static_assert(sizeof(Bones) == sizeof(V.Bone), "Unexpected V.Bones size"); memcpy(Bones, V.Bone, sizeof(Bones)); for (int j = 0; j < NUM_INFLUENCES; j++) { // We have INDEX_NONE as list terminator, should replace with something else for glTF if (Bones[j] == INDEX_NONE) { Bones[j] = 0; } } BonesBuf->Put(*(uint64*)&Bones); WeightsBuf->Put(V.PackedWeights); } } // Secondary UVs for (int uvIndex = 1; uvIndex < Lod.NumTexCoords; uvIndex++) { BufferData* pBuf = UVBuf[uvIndex]; const CMeshUVFloat* srcUV = Lod.ExtraUV[uvIndex-1]; for (int i = 0; i < numLocalVerts; i++) { int vertIndex = revIndexMap[i]; pBuf->Put(srcUV[vertIndex]); } } // Write primitive information to json Ar.Printf( " {\n" " \"attributes\" : {\n" " \"POSITION\" : %d,\n" " \"NORMAL\" : %d,\n" " \"TANGENT\" : %d,\n", PositionBufIndex, NormalBufIndex, TangentBufIndex ); if (Context.IsSkeletal()) { Ar.Printf( " \"JOINTS_0\" : %d,\n" " \"WEIGHTS_0\" : %d,\n", BonesBufIndex, WeightsBufIndex ); } for (int i = 0; i < Lod.NumTexCoords; i++) { Ar.Printf( " \"TEXCOORD_%d\" : %d%s\n", i, UVBufIndex[i], i < (Lod.NumTexCoords-1) ? "," : "" ); } Ar.Printf( " },\n" " \"indices\" : %d,\n" " \"material\" : %d\n" " }%s\n", IndexBufIndex, SectonIndex, SectonIndex < (Lod.Sections.Num()-1) ? "," : "" ); unguard; }