Skip to content

Commit

Permalink
Skeletal Animation asset support (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaremieromer authored Dec 21, 2023
1 parent 5055b9c commit 04e1aa9
Show file tree
Hide file tree
Showing 31 changed files with 533 additions and 64 deletions.
55 changes: 46 additions & 9 deletions docs/AssetFormats.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This document describes the data layouts for NcEngine assets and asset file type
- [HullCollider](#hullcollider-blob-format)
- [Mesh](#mesh-blob-format)
- [Shader](#shader-blob-format)
- [SkeletalAnimation](#skeletalanimation-blob-format)
- [Texture](#texture-blob-format)

## Nc Asset
Expand Down Expand Up @@ -94,21 +95,57 @@ CubeMap faces in pixel data array are ordered: front, back, up, down, right, lef
### Mesh Blob Format
> Magic Number: 'MESH'
| Name | Type | Size |
|--------------|---------------------|-------------------|
| extents | Vector3 | 12 |
| max extent | float | 4 |
| vertex count | u64 | 8 |
| index count | u64 | 8 |
| vertex list | MeshVertex[] | vertex count * 88 |
| indices | int[] | index count * 4 |
| bones data | optional<BonesData> | 56 |
| Name | Type | Size | Note
|----------------------|--------------------------------------|-------------------|-------------
| extents | Vector3 | 12 |
| max extent | float | 4 |
| vertex count | u64 | 8 |
| index count | u64 | 8 |
| vertex list | MeshVertex[] | vertex count * 88 |
| indices | int[] | index count * 4 |
| bones data has value | bool | 1 |
| BonesData | BonesData | | [BonesData](#bones-data-blob-format)

### Bones Data Blob Format

| Name | Type | Size | Note
|------------------------------|-----------------------------------------------------|---------------------------------------------------------|-------------
| vertexSpaceToBoneSpace count | u64 | 8 |
| boneSpaceToParentSpace count | u64 | 8 |
| boneMapping | (u64 + string + u32) * vertexSpaceToBoneSpace count | (12 + sizeof(boneName)) * vertexSpaceToBoneSpace count |
| vertexSpaceToBoneSpace | VertexSpaceToBoneSpace[] | (72 + sizeof(boneName)) * vertexSpaceToBoneSpace count |
| boneSpaceToParentSpace | BoneSpaceToParentSpace[] | (136 + sizeof(boneName)) * vertexSpaceToBoneSpace count |

### Shader Blob Format
> Magic Number: 'SHAD'
TODO

### SkeletalAnimation Blob Format
> Magic Number: 'SKEL'
| Name | Type | Size | Note
|----------------------------|------------------|---------------------------|------
| name size | u64 | 8 |
| name | string | name.size() |
| durationInTicks | u32 | 4 |
| ticksPerSecond | float | 4 |
| framesPerBone count | u64 | 8 |
| framesPerBone list | FramesPerBone[] | | [FramesPerBone](#FramesPerBone-blob-format)

### FramesPerBone Blob Format

| Name | Type | Size | Note
|----------------------|------------------|---------------------------|------
| name size | u64 | 8 |
| name | string | name.size() |
| position frames size | u64 | 8 |
| position frames | PositionFrames[] | position frames size * 20 |
| rotation frames size | u64 | 8 |
| rotation frames | RotationFrames[] | rotation frames size * 24 |
| scale frames size | u64 | 8 |
| scale frames | ScaleFrames[] | scale frames size * 20 |

### Texture Blob Format
> Magic Number: 'TEXT'
Expand Down
37 changes: 37 additions & 0 deletions docs/SourceFileRequirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ This makes `Transform` operations within NcEngine less surprising. Additionally,
NcEngine will treat the origin as the object's center of mass for physics
calculations.

If the mesh is intended for animation, it needs to have the following properties:

- An armature with bones
- All bone weights for each vertex must be normalized to sum 1.0
- No more than four bone influences per vertices

These properties can be set in the modeling software. To set them in Blender, for example:

Normalizing bone weights:
1. Select the mesh
2. Change mode to Weight Paint
3. Select all vertices
4. Choose Weights -> Normalize All

Limiting bone influences to four:
1. Select the mesh
2. Change mode to Weight Paint
3. Select all vertices
4. Choose Weights -> Limit Total
5. Set the limit to 4 in the popup menu

Geometry used for `hull-collider` generation should be convex.

## Image Conversion
Expand Down Expand Up @@ -64,3 +85,19 @@ Vertical cross layout (3:4):
[-Y]
[-Z]
```

## Skeletal Animation Conversion
> Supported file types: .fbx
`skeletal-animation` assets can be converted from .fbx files with animation data.
When exporting the .fbx from the modeling software, the following settings must be observed. (Using Blender as an example):
- Rotate the mesh -90 degrees on the X-axis
- Set "Apply Scalings" to "FBX Units Scale"
- Check the "Apply Unit" box
- Check the "Use Space Transform" box
- Set "Forward" to "-Z Forward"
- Set "Up" to "Y Up"

![Alt text](image.png)

There is also support for external animations such as Mixamo.
Binary file added docs/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions include/ncasset/AssetType.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum class AssetType
HullCollider,
Mesh,
Shader,
SkeletalAnimation,
Texture
};
} // namespace nc::asset
37 changes: 37 additions & 0 deletions include/ncasset/Assets.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <array>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

namespace nc::asset
Expand Down Expand Up @@ -33,6 +34,7 @@ struct BoneSpaceToParentSpace

struct BonesData
{
std::unordered_map<std::string, uint32_t> boneMapping;
std::vector<VertexSpaceToBoneSpace> vertexSpaceToBoneSpace;
std::vector<BoneSpaceToParentSpace> boneSpaceToParentSpace;
};
Expand Down Expand Up @@ -81,6 +83,41 @@ struct Shader
{
};

struct PositionFrame
{
float timeInTicks;
Vector3 position;
};

struct RotationFrame
{
float timeInTicks;
Quaternion rotation;
};

struct ScaleFrame
{
float timeInTicks;
Vector3 scale;
};

struct SkeletalAnimationFrames
{
// Vectors are not guaranteed to be the same length.
// There could be no rotation data for a frame, for example.
std::vector<PositionFrame> positionFrames;
std::vector<RotationFrame> rotationFrames;
std::vector<ScaleFrame> scaleFrames;
};

struct SkeletalAnimation
{
std::string name;
uint32_t durationInTicks;
float ticksPerSecond;
std::unordered_map<std::string, SkeletalAnimationFrames> framesPerBone;
};

struct Texture
{
static constexpr uint32_t numChannels = 4u;
Expand Down
2 changes: 2 additions & 0 deletions include/ncasset/AssetsFwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace nc::asset
{
struct AudioClip;
struct BonesData;
struct ConcaveCollider;
struct CubeMap;
struct HullCollider;
struct Mesh;
struct MeshVertex;
struct SkeletalAnimation;
struct Texture;
} // namespace nc::asset
6 changes: 6 additions & 0 deletions include/ncasset/Import.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ auto ImportMesh(const std::filesystem::path& ncaPath) -> Mesh;
/** @brief Read a Mesh asset from a binary stream. */
auto ImportMesh(std::istream& data) -> Mesh;

/** @brief Read a SkeletalAnimation asset from an .nca file. */
auto ImportSkeletalAnimation(const std::filesystem::path& ncaPath) -> SkeletalAnimation;

/** @brief Read a SkeletalAnimation asset from a binary stream. */
auto ImportSkeletalAnimation(std::istream& data) -> SkeletalAnimation;

/** @brief Read a Texture asset from an .nca file. */
auto ImportTexture(const std::filesystem::path& ncaPath) -> Texture;

Expand Down
1 change: 1 addition & 0 deletions include/ncasset/NcaHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct MagicNumber
static constexpr auto hullCollider = std::string_view{"HULL"};
static constexpr auto mesh = std::string_view{"MESH"};
static constexpr auto shader = std::string_view{"SHAD"};
static constexpr auto skeletalAnimation = std::string_view{"SKEL"};
static constexpr auto texture = std::string_view{"TEXT"};
};

Expand Down
5 changes: 5 additions & 0 deletions source/ncasset/Deserialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ auto DeserializeMesh(std::istream& stream) -> DeserializedResult<Mesh>
return DeserializeImpl<Mesh>(stream, MagicNumber::mesh);
}

auto DeserializeSkeletalAnimation(std::istream& stream) -> DeserializedResult<SkeletalAnimation>
{
return DeserializeImpl<SkeletalAnimation>(stream, MagicNumber::skeletalAnimation);
}

auto DeserializeTexture(std::istream& stream) -> DeserializedResult<Texture>
{
return DeserializeImpl<Texture>(stream, MagicNumber::texture);
Expand Down
3 changes: 3 additions & 0 deletions source/ncasset/Deserialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ auto DeserializeHullCollider(std::istream& stream) -> DeserializedResult<HullCol
/** @brief Construct a Mesh from data in a binary stream. */
auto DeserializeMesh(std::istream& stream) -> DeserializedResult<Mesh>;

/** @brief Construct a SkeletalAnimation from data in a binary stream. */
auto DeserializeSkeletalAnimation(std::istream& stream) -> DeserializedResult<SkeletalAnimation>;

/** @brief Construct a Texture from data in a binary stream. */
auto DeserializeTexture(std::istream& stream) -> DeserializedResult<Texture>;
} // nc::asset
12 changes: 12 additions & 0 deletions source/ncasset/Import.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ auto ImportMesh(const std::filesystem::path& ncaPath) -> Mesh
return ImportMesh(file);
}

auto ImportSkeletalAnimation(std::istream& data) -> SkeletalAnimation
{
auto [header, asset] = DeserializeSkeletalAnimation(data);
return asset;
}

auto ImportSkeletalAnimation(const std::filesystem::path& ncaPath) -> SkeletalAnimation
{
auto file = ::OpenNca(ncaPath);
return ImportSkeletalAnimation(file);
}

auto ImportTexture(std::istream& data) -> Texture
{
auto [header, asset] = DeserializeTexture(data);
Expand Down
1 change: 1 addition & 0 deletions source/ncconvert/builder/BuildInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ auto BuildTargetMap() -> std::unordered_map<nc::asset::AssetType, std::vector<nc
out.emplace(nc::asset::AssetType::ConcaveCollider, std::vector<nc::convert::Target>{});
out.emplace(nc::asset::AssetType::HullCollider, std::vector<nc::convert::Target>{});
out.emplace(nc::asset::AssetType::Mesh, std::vector<nc::convert::Target>{});
out.emplace(nc::asset::AssetType::SkeletalAnimation, std::vector<nc::convert::Target>{});
out.emplace(nc::asset::AssetType::Texture, std::vector<nc::convert::Target>{});
return out;
}
Expand Down
3 changes: 2 additions & 1 deletion source/ncconvert/builder/BuildOrchestrator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@

namespace
{
constexpr auto assetTypes = std::array<nc::asset::AssetType, 6>{
constexpr auto assetTypes = std::array<nc::asset::AssetType, 7>{
nc::asset::AssetType::AudioClip,
nc::asset::AssetType::CubeMap,
nc::asset::AssetType::ConcaveCollider,
nc::asset::AssetType::HullCollider,
nc::asset::AssetType::Mesh,
nc::asset::AssetType::SkeletalAnimation,
nc::asset::AssetType::Texture
};
}
Expand Down
6 changes: 6 additions & 0 deletions source/ncconvert/builder/Builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ auto Builder::Build(asset::AssetType type, const Target& target) -> bool
{
throw NcError("Not implemented");
}
case asset::AssetType::SkeletalAnimation:
{
const auto asset = m_geometryConverter->ImportSkeletalAnimation(target.sourcePath, target.subResourceName);
convert::Serialize(outFile, asset, assetId);
return true;
}
case asset::AssetType::Texture:
{
const auto asset = m_textureConverter->ImportTexture(target.sourcePath);
Expand Down
27 changes: 22 additions & 5 deletions source/ncconvert/builder/Inspect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,19 @@ R"(Data

constexpr auto meshTemplate =
R"(Data
extents {}, {}, {}
max extent {}
vertex count {}
index count {})";
extents {}, {}, {}
max extent {}
vertex count {}
index count {}
bones data vertex to bone count {}
bones data bone to parent count {})";

constexpr auto skeletalAnimationTemplate =
R"(Data
name {}
duration in ticks {}
ticks per seconds {}
frames per bone {})";

constexpr auto textureTemplate =
R"(Data
Expand Down Expand Up @@ -86,14 +95,22 @@ void Inspect(const std::filesystem::path& ncaPath)
case asset::AssetType::Mesh:
{
const auto asset = asset::ImportMesh(ncaPath);
LOG(meshTemplate, asset.extents.x, asset.extents.y, asset.extents.z, asset.maxExtent, asset.vertices.size(), asset.indices.size());
auto vertexSpaceSize = asset.bonesData.has_value()? asset.bonesData.value().vertexSpaceToBoneSpace.size() : 0;
auto boneSpaceSize = asset.bonesData.has_value()? asset.bonesData.value().boneSpaceToParentSpace.size() : 0;
LOG(meshTemplate, asset.extents.x, asset.extents.y, asset.extents.z, asset.maxExtent, asset.vertices.size(), asset.indices.size(), vertexSpaceSize, boneSpaceSize);
break;
}
case asset::AssetType::Shader:
{
LOG("Shader not supported");
break;
}
case asset::AssetType::SkeletalAnimation:
{
const auto asset = asset::ImportSkeletalAnimation(ncaPath);
LOG(skeletalAnimationTemplate, asset.name, asset.durationInTicks, asset.ticksPerSecond, asset.framesPerBone.size());
break;
}
case asset::AssetType::Texture:
{
const auto asset = asset::ImportTexture(ncaPath);
Expand Down
4 changes: 2 additions & 2 deletions source/ncconvert/builder/Manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace
{
const auto jsonAssetArrayTags = std::array<std::string, 6> {
"audio-clip", "concave-collider", "cube-map", "hull-collider", "mesh", "texture"
const auto jsonAssetArrayTags = std::array<std::string, 7> {
"audio-clip", "concave-collider", "cube-map", "hull-collider", "mesh", "skeletal-animation", "texture"
};

struct GlobalManifestOptions
Expand Down
5 changes: 5 additions & 0 deletions source/ncconvert/builder/Serialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ void Serialize(std::ostream& stream, const asset::Mesh& data, size_t assetId)
SerializeImpl(stream, data, asset::MagicNumber::mesh, assetId);
}

void Serialize(std::ostream& stream, const asset::SkeletalAnimation& data, size_t assetId)
{
SerializeImpl(stream, data, asset::MagicNumber::skeletalAnimation, assetId);
}

void Serialize(std::ostream& stream, const asset::Texture& data, size_t assetId)
{
SerializeImpl(stream, data, asset::MagicNumber::texture, assetId);
Expand Down
3 changes: 3 additions & 0 deletions source/ncconvert/builder/Serialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ void Serialize(std::ostream& stream, const asset::HullCollider& data, size_t ass
/** @brief Write a Mesh to a binary stream. */
void Serialize(std::ostream& stream, const asset::Mesh& data, size_t assetId);

/** @brief Write a SkeletalAnimation to a binary stream. */
void Serialize(std::ostream& stream, const asset::SkeletalAnimation& data, size_t assetId);

/** @brief Write a Texture to a binary stream. */
void Serialize(std::ostream& stream, const asset::Texture& data, size_t assetId);
} // nc::convert
Loading

0 comments on commit 04e1aa9

Please sign in to comment.