public static CVec3 Cross(CVec3 a, CVec3 b) => new CVec3 { v = new float[] { a.v[1] * b.v[2] - a.v[2] * b.v[1], a.v[2] * b.v[0] - a.v[0] * b.v[2], a.v[0] * b.v[1] - a.v[1] * b.v[0] } };
public static float Dot(CVec3 a, CVec3 b) => a.v[0] * b.v[0] + a.v[1] * b.v[1] + a.v[2] * b.v[2];
public CSkeletalMesh(USkeletalMesh mesh) { OriginalMesh = mesh; // convert bounds BoundingSphere = new FSphere { R = mesh.Bounds.sphere_radius / 2 }; BoundingBox = new FBox { Min = mesh.Bounds.origin - mesh.Bounds.box_extend, Max = mesh.Bounds.origin + mesh.Bounds.box_extend }; // MeshScale, MeshOrigin, RotOrigin are removed in UE4 //!! NOTE: MeshScale is integrated into RefSkeleton.RefBonePose[0].Scale3D. //!! Perhaps rotation/translation are integrated too! MeshOrigin = new CVec3 { v = new float[] { 0, 0, 0 } }; RotOrigin = new FRotator { pitch = 0, roll = 0, yaw = 0 }; MeshScale = new CVec3 { v = new float[] { 1, 1, 1 } }; // convert LODs Lods = new CSkelMeshLod[mesh.LODModels.Length]; for (int i = 0; i < Lods.Length; i++) { var SrcLod = mesh.LODModels[i]; if (SrcLod.Indices.Indices16.Length == 0 && SrcLod.Indices.Indices32.Length == 0) { // No indicies in this lod continue; } int NumTexCoords = SrcLod.NumTexCoords; if (NumTexCoords > 8) { throw new FileLoadException($"SkeletalMesh has too many ({NumTexCoords}) UV sets"); } CSkelMeshLod Lod = new CSkelMeshLod { NumTexCoords = NumTexCoords, HasNormals = true, HasTangents = true, }; // get vertex count and determine vertex source int VertexCount = SrcLod.VertexBufferGPUSkin.GetVertexCount(); bool bUseVerticesFromSections = false; // if (VertexCount == 0 && SrcLod.Sections.Length > 0 && SrcLod.Sections[0].SoftVertices.Count > 0) // above is used for editor assets, but there are no chunks for soft vertices Lod.AllocateVerts(VertexCount); int chunkIndex = -1; int lastChunkVertex = -1; int chunkVertexIndex = 0; ushort[] BoneMap = null; for (int j = 0; j < VertexCount; j++) { while (j >= lastChunkVertex) // this will fix any issues with empty chunks or sections { // UE4.13+ code: chunk information migrated to sections FSkelMeshSection S = SrcLod.Sections[++chunkIndex]; lastChunkVertex = (int)(S.base_vertex_index + S.num_vertices); BoneMap = S.bone_map; chunkVertexIndex = 0; } // get vertex from GPU skin FSkelMeshVertexBase V; if (!SrcLod.VertexBufferGPUSkin.bUseFullPrecisionUVs) { FGPUVert4Half V0 = SrcLod.VertexBufferGPUSkin.VertsHalf[j]; FMeshUVHalf[] SrcUV = V0.UV; V = new FSkelMeshVertexBase { Infs = V0.Infs, Normal = V0.Normal, Pos = V0.Pos }; // UV: convert half -> float Lod.Verts[j].UV = (FMeshUVFloat)SrcUV[0]; for (int TexCoordIndex = 1; TexCoordIndex < NumTexCoords; TexCoordIndex++) { Lod.ExtraUV[TexCoordIndex - 1][j] = (FMeshUVFloat)SrcUV[TexCoordIndex]; } } else { FGPUVert4Float V0 = SrcLod.VertexBufferGPUSkin.VertsFloat[j]; FMeshUVFloat[] SrcUV = V0.UV; V = new FSkelMeshVertexBase { Infs = V0.Infs, Normal = V0.Normal, Pos = V0.Pos }; // UV: convert half -> float Lod.Verts[j].UV = SrcUV[0]; for (int TexCoordIndex = 1; TexCoordIndex < NumTexCoords; TexCoordIndex++) { Lod.ExtraUV[TexCoordIndex - 1][j] = SrcUV[TexCoordIndex]; } } Lod.Verts[j].Position = V.Pos; Lod.Verts[j].UnpackNormals(V.Normal); // convert influences Lod.Verts[j].Bone = new short[4]; int k2 = 0; uint PackedWeights = 0; for (int k = 0; k < 4; k++) { int BoneIndex = V.Infs.bone_index[k]; byte BoneWeight = V.Infs.bone_weight[k]; if (BoneWeight == 0) { continue; // skip this influence (but do not stop the loop!) } PackedWeights |= (uint)(BoneWeight << (k2 * 8)); Lod.Verts[j].Bone[k2] = (short)BoneMap[BoneIndex]; k2++; } Lod.Verts[j].PackedWeights = PackedWeights; if (k2 < 4) { Lod.Verts[j].Bone[k2] = -1; // mark end of list } } // indices Lod.Indices.Initialize(SrcLod.Indices.Indices16, SrcLod.Indices.Indices32); // sections Lod.Sections = new CMeshSection[SrcLod.Sections.Length]; FSkeletalMeshLODInfo Info = mesh.LODInfo[i]; for (int j = 0; j < SrcLod.Sections.Length; j++) { FSkelMeshSection S = SrcLod.Sections[j]; CMeshSection Dst = new CMeshSection(); // remap material for LOD int MaterialIndex = S.material_index; if (MaterialIndex >= 0 && MaterialIndex < Info.LODMaterialMap.Length) { MaterialIndex = Info.LODMaterialMap[MaterialIndex]; } if (S.material_index < mesh.Materials.Length) { Dst.Material = new UUnrealMaterial();// mesh.Materials[MaterialIndex].Material; } // -> TODO: actually get the object from the pak Dst.FirstIndex = (int)S.base_index; Dst.NumFaces = (int)S.num_triangles; Lod.Sections[j] = Dst; } Lods[i] = Lod; } // copy skeleton int NumBones = mesh.RefSkeleton.ref_bone_info.Length; RefSkeleton = new CSkelMeshBone[NumBones]; for (int i = 0; i < NumBones; i++) { FMeshBoneInfo B = mesh.RefSkeleton.ref_bone_info[i]; FTransform T = mesh.RefSkeleton.ref_bone_pose[i]; CSkelMeshBone Dst = new CSkelMeshBone { Name = B.name, ParentIndex = B.parent_index, Position = T.translation, Orientation = T.rotation }; // fix skeleton; all bones but 0 if (i >= 1) { Dst.Orientation.Conjugate(); } RefSkeleton[i] = Dst; } Sockets = null; // dunno where this is set FinalizeMesh(); }
public void VectorMA(float scale, CVec3 b) { v[0] += scale * b.v[0]; v[1] += scale * b.v[1]; v[2] += scale * b.v[2]; }
public void BuildNormals() { if (HasNormals) { return; } int i, j; // Find vertices to share. // We are using very simple algorithm here: to share all vertices with the same position // independently on normals of faces which share this vertex. CVec3[] tmpNorm = new CVec3[NumVerts]; // really will use Points.Num() items, which value is smaller than NumVerts CVertexShare Share = new CVertexShare(); Share.Prepare(Verts, NumVerts, 48); for (i = 0; i < NumVerts; i++) { CPackedNormal NullVec; NullVec.Data = 0; Share.AddVertex(Verts[i].Position, NullVec); } for (i = 0; i < Indices.Length / 3; i++) { CSkelMeshVertex[] V = new CSkelMeshVertex[3]; CVec3[] N = new CVec3[3]; for (j = 0; j < 3; j++) { int idx = (int)Indices[i * 3 + j]; // index in Verts[] V[j] = Verts[idx]; N[j] = tmpNorm[Share.WedgeToVert[idx]]; // remap to shared verts } // compute edges CVec3[] D = new CVec3[] { V[1].Position - V[0].Position, V[2].Position - V[1].Position, V[0].Position - V[2].Position }; // compute face normal CVec3 norm = CVec3.Cross(D[1], D[0]); norm.Normalize(); // compute angles for (j = 0; j < 3; j++) { D[j].Normalize(); } float[] angle = new float[] { (float)Math.Acos(-CVec3.Dot(D[0], D[2])), (float)Math.Acos(-CVec3.Dot(D[0], D[1])), (float)Math.Acos(-CVec3.Dot(D[1], D[2])) }; // add normals for triangle verts for (j = 0; j < 3; j++) { N[j].VectorMA(angle[j], norm); } for (j = 0; j < 3; j++) { int idx = (int)Indices[i * 3 + j]; Verts[idx] = V[j]; tmpNorm[Share.WedgeToVert[idx]] = N[j]; } } // TODO: add "hard angle threshold" - do not share vertex between faces when angle between them // is too large. // normalize shared normals ... for (i = 0; i < Share.Points.Count; i++) { tmpNorm[i].Normalize(); } // ... then place ("unshare") normals to Verts for (i = 0; i < NumVerts; i++) { Verts[i].Normal.Pack(tmpNorm[Share.WedgeToVert[i]]); } HasNormals = true; }