mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
cppkore uses string/wstring as StringBase while we use std::string/std::wstring as string/wstring. Changed all types in cppkore to String/WString instead.
403 lines
12 KiB
C++
403 lines
12 KiB
C++
#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<CastHeader>({ 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<CastHeader>({ 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<uint32_t, uint64_t> 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<CastProperty*> 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;
|
|
}
|
|
}
|