#include "stdafx.h" #include "CastAsset.h" #include "CastNode.h" #include "File.h" #include "XXHash.h" #include "BinaryWriter.h" namespace Assets::Exporters { struct CastHeader { uint32_t Magic; // char[4] cast (0x74736163) uint32_t Version; // 0x1 uint32_t RootNodes; // Number of root nodes, which contain various sub nodes if necessary uint32_t Flags; // Reserved for flags, or padding, whichever is needed }; static_assert(sizeof(CastHeader) == 0x10, "Cast header size mismatch"); bool CastAsset::ExportAnimation(const Animation& Animation, const String& Path) { auto Writer = IO::BinaryWriter(IO::File::Create(Path)); // Magic, version 1, one root node, no flags. Writer.Write({ 0x74736163, 0x1, 0x1, 0x0 }); // This is the base of the virtual scene auto Root = CastNode(CastId::Root); auto& AnimNode = Root.Children.Emplace(CastId::Animation, Hashing::XXHash::HashString("animation")); auto& SkeletonNode = AnimNode.Children.Emplace(CastId::Skeleton, Hashing::XXHash::HashString("skeleton")); AnimNode.Properties.Emplace(CastPropertyId::Float, "fr").AddFloat(Animation.FrameRate); AnimNode.Properties.Emplace(CastPropertyId::Byte, "lo").AddByte((uint8_t)Animation.Looping); for (auto& Bone : Animation.Bones) { auto& BoneNode = SkeletonNode.Children.Emplace(CastId::Bone); BoneNode.Properties.Emplace(CastPropertyId::String, "n").SetString(Bone.Name()); BoneNode.Properties.Emplace(CastPropertyId::Integer32, "p").AddInteger32(Bone.Parent()); if (Bone.GetFlag(Assets::BoneFlags::HasLocalSpaceMatrices)) { BoneNode.Properties.Emplace(CastPropertyId::Vector3, "lp").AddVector3(Bone.LocalPosition()); BoneNode.Properties.Emplace(CastPropertyId::Vector4, "lr").AddVector4(Bone.LocalRotation()); } if (Bone.GetFlag(Assets::BoneFlags::HasGlobalSpaceMatrices)) { BoneNode.Properties.Emplace(CastPropertyId::Vector3, "wp").AddVector3(Bone.GlobalPosition()); BoneNode.Properties.Emplace(CastPropertyId::Vector4, "wr").AddVector4(Bone.GlobalRotation()); } if (Bone.GetFlag(Assets::BoneFlags::HasScale)) { BoneNode.Properties.Emplace(CastPropertyId::Vector3, "s").AddVector3(Bone.Scale()); } } for (auto& Kvp : Animation.Curves) { for (auto& Curve : Kvp.Value()) { auto& CurveNode = AnimNode.Children.Emplace(CastId::Curve, 0); CurveNode.Properties.Emplace(CastPropertyId::String, "nn").SetString(Curve.Name); constexpr const char* PropertyNameMap[] = { "ex", "rq", "rx", "ry", "rz", "tx", "ty", "tz", "sx", "sy", "sz", "vb" }; constexpr const char* ModeNameMap[] = { "absolute", "additive", "relative" }; CurveNode.Properties.Emplace(CastPropertyId::String, "kp").SetString(PropertyNameMap[(uint32_t)Curve.Property]); CurveNode.Properties.Emplace(CastPropertyId::String, "m").SetString(ModeNameMap[(uint32_t)Curve.Mode]); auto KeyframeValueProperty = CastPropertyId::Float; switch (Curve.Property) { case CurveProperty::RotateQuaternion: KeyframeValueProperty = CastPropertyId::Vector4; break; case CurveProperty::RotateX: case CurveProperty::RotateY: case CurveProperty::RotateZ: case CurveProperty::TranslateX: case CurveProperty::TranslateY: case CurveProperty::TranslateZ: case CurveProperty::ScaleX: case CurveProperty::ScaleY: case CurveProperty::ScaleZ: KeyframeValueProperty = CastPropertyId::Float; break; case CurveProperty::Visibility: KeyframeValueProperty = CastPropertyId::Byte; break; } auto KeyframeFrameProperty = CastPropertyId::Float; if (Curve.IsFrameIntegral()) { uint32_t LargestFrameIndex = 0; for (auto& KeyFrame : Curve.Keyframes) LargestFrameIndex = max(LargestFrameIndex, KeyFrame.Frame.Integer32); if (LargestFrameIndex <= 0xFF) KeyframeFrameProperty = CastPropertyId::Byte; else if (LargestFrameIndex <= 0xFFFF) KeyframeFrameProperty = CastPropertyId::Short; else KeyframeFrameProperty = CastPropertyId::Integer32; } auto& KeyFrameBuffer = CurveNode.Properties.Emplace(KeyframeFrameProperty, "kb"); auto& KeyValueBuffer = CurveNode.Properties.Emplace(KeyframeValueProperty, "kv"); for (auto& KeyFrame : Curve.Keyframes) { switch (KeyframeFrameProperty) { case CastPropertyId::Float: KeyFrameBuffer.AddFloat(KeyFrame.Frame.Float); break; case CastPropertyId::Byte: KeyFrameBuffer.AddByte((uint8_t)KeyFrame.Frame.Integer32); break; case CastPropertyId::Short: KeyFrameBuffer.AddShort((uint16_t)KeyFrame.Frame.Integer32); break; case CastPropertyId::Integer32: KeyFrameBuffer.AddInteger32(KeyFrame.Frame.Integer32); break; } switch (Curve.Property) { case CurveProperty::RotateQuaternion: KeyValueBuffer.AddVector4(KeyFrame.Value.Vector4); break; case CurveProperty::RotateX: case CurveProperty::RotateY: case CurveProperty::RotateZ: case CurveProperty::TranslateX: case CurveProperty::TranslateY: case CurveProperty::TranslateZ: case CurveProperty::ScaleX: case CurveProperty::ScaleY: case CurveProperty::ScaleZ: KeyValueBuffer.AddFloat(KeyFrame.Value.Float); break; case CurveProperty::Visibility: KeyValueBuffer.AddByte(KeyFrame.Value.Byte); break; } } } } for (auto& Notetrack : Animation.Notificiations) { auto& TrackNode = AnimNode.Children.Emplace(CastId::NotificationTrack, Hashing::XXHash::HashString(Notetrack.Key())); TrackNode.Properties.Emplace(CastPropertyId::String, "n").SetString(Notetrack.Key()); auto& KeyBuffer = TrackNode.Properties.Emplace(CastPropertyId::Integer32, "kb"); for (auto& Key : Notetrack.Value()) KeyBuffer.AddInteger32(Key); } // Finally, serialize the node to the disk Root.Write(Writer); return true; } bool CastAsset::ExportModel(const Model& Model, const String& Path) { auto Writer = IO::BinaryWriter(IO::File::Create(Path)); // Magic, version 1, one root node, no flags. Writer.Write({ 0x74736163, 0x1, 0x1, 0x0 }); // This is the base of the virtual scene auto Root = CastNode(CastId::Root); auto& ModelNode = Root.Children.Emplace(CastId::Model, Hashing::XXHash::HashString("model")); auto& SkeletonNode = ModelNode.Children.Emplace(CastId::Skeleton, Hashing::XXHash::HashString("skeleton")); auto BoneCount = Model.Bones.Count(); for (auto& Bone : Model.Bones) { auto& BoneNode = SkeletonNode.Children.Emplace(CastId::Bone); BoneNode.Properties.Emplace(CastPropertyId::String, "n").SetString(Bone.Name()); BoneNode.Properties.Emplace(CastPropertyId::Integer32, "p").AddInteger32(Bone.Parent()); if (Bone.GetFlag(Assets::BoneFlags::HasLocalSpaceMatrices)) { BoneNode.Properties.Emplace(CastPropertyId::Vector3, "lp").AddVector3(Bone.LocalPosition()); BoneNode.Properties.Emplace(CastPropertyId::Vector4, "lr").AddVector4(Bone.LocalRotation()); } if (Bone.GetFlag(Assets::BoneFlags::HasGlobalSpaceMatrices)) { BoneNode.Properties.Emplace(CastPropertyId::Vector3, "wp").AddVector3(Bone.GlobalPosition()); BoneNode.Properties.Emplace(CastPropertyId::Vector4, "wr").AddVector4(Bone.GlobalRotation()); } if (Bone.GetFlag(Assets::BoneFlags::HasScale)) { BoneNode.Properties.Emplace(CastPropertyId::Vector3, "s").AddVector3(Bone.Scale()); } } Dictionary MaterialHashMap; uint32_t MaterialIndex = 0; for (auto& Mat : Model.Materials) { auto& MatNode = ModelNode.Children.Emplace(CastId::Material, Hashing::XXHash::HashString(Mat.Name)); MatNode.Properties.Emplace(CastPropertyId::String, "n").SetString(Mat.Name); MatNode.Properties.Emplace(CastPropertyId::String, "t").SetString("pbr"); for (auto& Kvp : Mat.Slots) { auto FileHash = Hashing::XXHash::HashString(Kvp.second.first); MatNode.Children.Emplace(CastId::File, FileHash).Properties.Emplace(CastPropertyId::String, "p").SetString(Kvp.second.first); // Cast material property mapping constexpr const char* MaterialSlotNames[] = { "extra", // Invalid "albedo", "diffuse", "normal", "specular", "emissive", "gloss", "roughness", "ao", "cavity" }; MatNode.Properties.Emplace(CastPropertyId::Integer64, MaterialSlotNames[(uint32_t)Kvp.first]).AddInteger64(FileHash); } MaterialHashMap.Add(MaterialIndex++, MatNode.Hash); } uint32_t MeshIndex = 0; for (auto& Mesh : Model.Meshes) { auto& MeshNode = ModelNode.Children.Emplace(CastId::Mesh, Hashing::XXHash::HashString(String::Format("mesh%02d", MeshIndex++))); MeshNode.Properties.EmplaceBack(CastPropertyId::Vector3, "vp"); MeshNode.Properties.EmplaceBack(CastPropertyId::Vector3, "vn"); MeshNode.Properties.EmplaceBack(CastPropertyId::Integer32, "vc"); auto VertexCount = Mesh.Vertices.Count(); if (VertexCount <= 0xFF) MeshNode.Properties.EmplaceBack(CastPropertyId::Byte, "f"); else if (VertexCount <= 0xFFFF) MeshNode.Properties.EmplaceBack(CastPropertyId::Short, "f"); else MeshNode.Properties.EmplaceBack(CastPropertyId::Integer32, "f"); // Configure the uv layer count, and maximum influence MeshNode.Properties.Emplace(CastPropertyId::Byte, "ul").AddByte((uint8_t)Mesh.Vertices.UVLayerCount()); MeshNode.Properties.Emplace(CastPropertyId::Byte, "mi").AddByte((uint8_t)Mesh.Vertices.WeightCount()); if (BoneCount <= 0xFF) MeshNode.Properties.Emplace(CastPropertyId::Byte, "wb"); else if (BoneCount <= 0xFFFF) MeshNode.Properties.Emplace(CastPropertyId::Short, "wb"); else MeshNode.Properties.Emplace(CastPropertyId::Integer32, "wb"); MeshNode.Properties.Emplace(CastPropertyId::Float, "wv"); List UVLayers; for (uint8_t i = 0; i < Mesh.Vertices.UVLayerCount(); i++) MeshNode.Properties.EmplaceBack(CastPropertyId::Vector2, String::Format("u%d", i)); auto& VertexPositions = MeshNode.Properties[0]; auto& VertexNormals = MeshNode.Properties[1]; auto& VertexColors = MeshNode.Properties[2]; auto& FaceIndices = MeshNode.Properties[3]; auto& VertexWeightBones = MeshNode.Properties[6]; auto& VertexWeightValues = MeshNode.Properties[7]; for (auto& Layer : MeshNode.Properties) { if (Layer.Name != "ul" && Layer.Name.StartsWith("u")) UVLayers.Add(&Layer); } for (auto& Vertex : Mesh.Vertices) { VertexPositions.AddVector3(Vertex.Position()); VertexNormals.AddVector3(Vertex.Normal()); VertexColors.AddInteger32(*(uint32_t*)&Vertex.Color()); for (uint8_t i = 0; i < Mesh.Vertices.WeightCount(); i++) { if (BoneCount <= 0xFF) VertexWeightBones.AddByte((uint8_t)Vertex.Weights(i).Bone); else if (BoneCount <= 0xFFFF) VertexWeightBones.AddShort((uint16_t)Vertex.Weights(i).Bone); else VertexWeightBones.AddInteger32(Vertex.Weights(i).Bone); VertexWeightValues.AddFloat(Vertex.Weights(i).Value); } for (uint8_t i = 0; i < Mesh.Vertices.UVLayerCount(); i++) { UVLayers[i]->AddVector2(Vertex.UVLayers(i)); } } for (auto& Face : Mesh.Faces) { if (VertexCount <= 0xFF) { FaceIndices.AddByte((uint8_t)Face[2]); FaceIndices.AddByte((uint8_t)Face[1]); FaceIndices.AddByte((uint8_t)Face[0]); } else if (VertexCount <= 0xFFFF) { FaceIndices.AddShort((uint16_t)Face[2]); FaceIndices.AddShort((uint16_t)Face[1]); FaceIndices.AddShort((uint16_t)Face[0]); } else { FaceIndices.AddInteger32(Face[2]); FaceIndices.AddInteger32(Face[1]); FaceIndices.AddInteger32(Face[0]); } } if (Mesh.MaterialIndices.Count() > 0 && Mesh.MaterialIndices[0] > -1) { MeshNode.Properties.Emplace(CastPropertyId::Integer64, "m").AddInteger64(MaterialHashMap[Mesh.MaterialIndices[0]]); } } // Finally, serialize the node to the disk Root.Write(Writer); return true; } imstring CastAsset::ModelExtension() { return ".cast"; } imstring CastAsset::AnimationExtension() { return ".cast"; } ExporterScale CastAsset::ExportScale() { return ExporterScale::Default; } bool CastAsset::SupportsAnimations() { return true; } bool CastAsset::SupportsModels() { return true; } }