# 🧭 Node Transforms（ノード変換）

FBX のノード変換は、****一連の変換のチェーン****として構成されています。

もし特別な理由がなければ、FBX 固有の変換モデルを直接扱うよりも、  
****ufbx が提供する変換表現****を使用することを強く推奨します。  
<span style="white-space: pre-wrap;">詳細は → </span>[Elements / Nodes / Transforms](/elements/nodes/#transforms)<span style="white-space: pre-wrap;"> を参照してください。</span>

---

## 🔩 基本構造（Transform Properties）

FBX 内でのノード変換は、いくつかの****プロパティ****で構成されています。

<table id="bkmrk-%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E5%90%8D%E6%84%8F%E5%91%B3%22lcl-transla"><colgroup><col></col><col></col></colgroup><tbody><tr><th>プロパティ名

</th><th>意味

</th></tr><tr><td>`<span class="editor-theme-code">"Lcl Translation"</span>`

</td><td>親ノードに対する平行移動

</td></tr><tr><td>`<span class="editor-theme-code">"Lcl Scaling"</span>`

</td><td>親ノードに対する非一様スケール

</td></tr><tr><td>`<span class="editor-theme-code">"Lcl Rotation"</span>`

</td><td>親ノードに対する回転（オイラー角）

</td></tr><tr><td>`<span class="editor-theme-code">"RotationOrder"</span>`

</td><td>`<span class="editor-theme-code">"Lcl Rotation"</span>`

<span style="white-space: pre-wrap;"> のオイラー回転順序（</span>

`<span class="editor-theme-code">ufbx_rotation_order</span>`

）

</td></tr></tbody></table>

---

### 🎯 ピボット・オフセット関連

以下のプロパティによって、回転やスケールの中心・ずれが定義されます。

<table id="bkmrk-%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E5%90%8D%E5%86%85%E5%AE%B9%22scalingpivo"><colgroup><col></col><col></col></colgroup><tbody><tr><th>プロパティ名

</th><th>内容

</th></tr><tr><td>`<span class="editor-theme-code">"ScalingPivot"</span>`

</td><td>スケーリングの中心点

</td></tr><tr><td>`<span class="editor-theme-code">"ScalingOffset"</span>`

</td><td>スケール後のオフセット移動

</td></tr><tr><td>`<span class="editor-theme-code">"RotationPivot"</span>`

</td><td>回転の中心点

</td></tr><tr><td>`<span class="editor-theme-code">"RotationOffset"</span>`

</td><td>回転後のオフセット移動

</td></tr></tbody></table>

<span style="white-space: pre-wrap;">さらに </span>`<span class="editor-theme-code">"Lcl Rotation"</span>`<span style="white-space: pre-wrap;"> 以外に、</span>****2種類の補助回転****が存在します：

<table id="bkmrk-%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E5%90%8D%E5%86%85%E5%AE%B9%22prerotation"><colgroup><col></col><col></col></colgroup><tbody><tr><th>プロパティ名

</th><th>内容

</th></tr><tr><td>`<span class="editor-theme-code">"PreRotation"</span>`

</td><td>`<span class="editor-theme-code">"Lcl Rotation"</span>`

<span style="white-space: pre-wrap;"> の前に適用される回転（常に XYZ 順）</span>

</td></tr><tr><td>`<span class="editor-theme-code">"PostRotation"</span>`

</td><td>`<span class="editor-theme-code">"Lcl Rotation"</span>`

<span style="white-space: pre-wrap;"> の後に適用される</span>

****逆回転****

（常に XYZ 順）

</td></tr></tbody></table>

---

## ⚙️ 変換チェーンの計算例

以下は、****ufbx の内部補正（adjust transform）を考慮しない****  
基本的なノード変換の計算例です。

```cpp
// 手動で `ufbx_node.node_to_parent` を計算
// ※ ufbx固有の調整変換（adjust transform）は考慮しない
Matrix4 get_transform(ufbx_node *node)
{
    ufbx_props *props = &node->props;

    // 主要プロパティを取得（必要に応じてカーブから評価）
    int64_t rotation_order  = ufbx_find_int(props, "RotationOrder", 0);
    Vector3 lcl_translation = ufbx_find_vec3(props, "Lcl Translation", ufbx_zero_vec3);
    Vector3 lcl_scaling     = ufbx_find_vec3(props, "Lcl Scaling", ufbx_zero_vec3);
    Vector3 lcl_rotation    = ufbx_find_vec3(props, "Lcl Rotation", ufbx_zero_vec3);
    Vector3 rotation_pivot  = ufbx_find_vec3(props, "RotationPivot", ufbx_zero_vec3);
    Vector3 scaling_pivot   = ufbx_find_vec3(props, "ScalingPivot", ufbx_zero_vec3);
    Vector3 rotation_offset = ufbx_find_vec3(props, "RotationOffset", ufbx_zero_vec3);
    Vector3 scaling_offset  = ufbx_find_vec3(props, "ScalingOffset", ufbx_zero_vec3);
    Vector3 pre_rotation    = ufbx_find_vec3(props, "PreRotation", ufbx_zero_vec3);
    Vector3 post_rotation   = ufbx_find_vec3(props, "PostRotation", ufbx_zero_vec3);

    // オイラー角 → クォータニオン変換
    Quaternion lcl_quat = Quaternion_euler(lcl_rotation, (EulerOrder)rotation_order);
    Quaternion pre_quat = Quaternion_euler(pre_rotation, EulerOrder_XYZ);
    Quaternion post_quat = Quaternion_euler(post_rotation, EulerOrder_XYZ);

    Matrix4 m = Matrix4_identity;

    // スケール適用（ピボット・オフセット含む）
    m = Matrix4_translate(-scaling_pivot) * m;
    m = Matrix4_scale_nonuniform(lcl_scaling) * m;
    m = Matrix4_translate(scaling_pivot) * m;
    m = Matrix4_translate(scaling_offset) * m;

    // 回転適用（PostRotation は反転）
    m = Matrix4_translate(-rotation_pivot) * m;
    m = Matrix4_rotate(Quaternion_inverse(post_quat)) * m;
    m = Matrix4_rotate(lcl_quat) * m;
    m = Matrix4_rotate(pre_quat) * m;
    m = Matrix4_translate(rotation_pivot) * m;
    m = Matrix4_translate(rotation_offset) * m;

    // 最後に平行移動
    m = Matrix4_translate(lcl_translation) * m;

    return m;
}
```

---

## 🧮 Adjust Transforms（調整変換）

FBX の座標系やピボットの扱いに応じて、  
ufbx は内部でいくつかの「調整変換（adjust transform）」を自動的に付与します。  
これらを考慮しないと、見た目上の座標がずれることがあります。

### 主な adjust transform の対応表

<table id="bkmrk-%E6%A9%9F%E8%83%BD%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89ufbx_spac"><colgroup><col></col><col></col></colgroup><tbody><tr><th>機能

</th><th>対応するフィールド

</th></tr><tr><td>`<span class="editor-theme-code">UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS</span>`

</td><td>`<span class="editor-theme-code">adjust_pre_rotation</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">adjust_pre_scale</span>`

</td></tr><tr><td>`<span class="editor-theme-code">UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY</span>`

</td><td>`<span class="editor-theme-code">adjust_pre_rotation</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">adjust_translation_scale</span>`

</td></tr><tr><td>`<span class="editor-theme-code">UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT</span>`

</td><td>`<span class="editor-theme-code">adjust_pre_translation</span>`

</td></tr><tr><td>`<span class="editor-theme-code">UFBX_INHERIT_MODE_HANDLING_COMPENSATE</span>`

</td><td>`<span class="editor-theme-code">adjust_post_scale</span>`

</td></tr><tr><td>`<span class="editor-theme-code">ufbx_load_opts.target_camera_axes</span>`

</td><td>`<span class="editor-theme-code">adjust_post_rotation</span>`

</td></tr><tr><td>`<span class="editor-theme-code">ufbx_load_opts.target_light_axes</span>`

</td><td>`<span class="editor-theme-code">adjust_post_rotation</span>`

</td></tr></tbody></table>

`<span class="editor-theme-code">ufbx_node.has_adjust_transform</span>`<span style="white-space: pre-wrap;"> が </span>`<span class="editor-theme-code">true</span>`<span style="white-space: pre-wrap;"> の場合は、</span>  
ノードが非単位（≠ identity）の調整変換を持っています。  
ただし、常に適用しても安全です。

---

### 💻 Adjust 変換対応版の完全計算例

```cpp
// ufbx固有の adjust transform を考慮した `ufbx_node.node_to_parent` の計算
Matrix4 get_transform(ufbx_node *node)
{
    ufbx_props *props = &node->props;

    // プロパティの取得
    int64_t rotation_order  = ufbx_find_int(props, "RotationOrder", 0);
    Vector3 lcl_translation = ufbx_find_vec3(props, "Lcl Translation", ufbx_zero_vec3);
    Vector3 lcl_scaling     = ufbx_find_vec3(props, "Lcl Scaling", ufbx_zero_vec3);
    Vector3 lcl_rotation    = ufbx_find_vec3(props, "Lcl Rotation", ufbx_zero_vec3);
    Vector3 rotation_pivot  = ufbx_find_vec3(props, "RotationPivot", ufbx_zero_vec3);
    Vector3 scaling_pivot   = ufbx_find_vec3(props, "ScalingPivot", ufbx_zero_vec3);
    Vector3 rotation_offset = ufbx_find_vec3(props, "RotationOffset", ufbx_zero_vec3);
    Vector3 scaling_offset  = ufbx_find_vec3(props, "ScalingOffset", ufbx_zero_vec3);
    Vector3 pre_rotation    = ufbx_find_vec3(props, "PreRotation", ufbx_zero_vec3);
    Vector3 post_rotation   = ufbx_find_vec3(props, "PostRotation", ufbx_zero_vec3);

    // ufbx 特有の調整パラメータ
    Vector3 adjust_pre_translation = node->adjust_pre_translation;
    Quaternion adjust_pre_rotation = node->adjust_pre_rotation;
    Quaternion adjust_post_rotation = node->adjust_post_rotation;
    float adjust_pre_scale = (float)node->adjust_pre_scale;
    float adjust_post_scale = (float)node->adjust_post_scale;
    float adjust_translation_scale = (float)node->adjust_translation_scale;

    // クォータニオン化
    Quaternion lcl_quat = Quaternion_euler(lcl_rotation, (EulerOrder)rotation_order);
    Quaternion pre_quat = Quaternion_euler(pre_rotation, EulerOrder_XYZ);
    Quaternion post_quat = Quaternion_euler(post_rotation, EulerOrder_XYZ);

    Matrix4 m = Matrix4_identity;

    // 後処理（post-adjust）
    m = Matrix4_rotate(adjust_post_rotation) * m;
    m = Matrix4_scale(adjust_post_scale) * m;

    // スケール
    m = Matrix4_translate(-scaling_pivot) * m;
    m = Matrix4_scale_nonuniform(lcl_scaling) * m;
    m = Matrix4_translate(scaling_pivot) * m;
    m = Matrix4_translate(scaling_offset) * m;

    // 回転
    m = Matrix4_translate(-rotation_pivot) * m;
    m = Matrix4_rotate(Quaternion_inverse(post_quat)) * m;
    m = Matrix4_rotate(lcl_quat) * m;
    m = Matrix4_rotate(pre_quat) * m;
    m = Matrix4_translate(rotation_pivot) * m;
    m = Matrix4_translate(rotation_offset) * m;

    // 平行移動
    m = Matrix4_translate(lcl_translation) * m;

    // 前処理（pre-adjust）
    m = Matrix4_translate(adjust_pre_translation) * m;
    m = Matrix4_rotate(adjust_pre_rotation) * m;
    m = Matrix4_scale(adjust_pre_scale) * m;

    // translation のみスケーリング
    m.m03 *= adjust_translation_scale;
    m.m13 *= adjust_translation_scale;
    m.m23 *= adjust_translation_scale;

    return m;
}
```

📘 実際の実装例：  
[`<span class="editor-theme-code">ufbxi_get_transform()</span>`<span style="white-space: pre-wrap;"> on GitHub</span>](https://github.com/ufbx/ufbx/blob/6dd7771ee215c7489a211b7aa259f1056ce14354/ufbx.c#L21497-L21558)

---

## 🧱 Geometric Transforms（ジオメトリ変換）

ジオメトリ変換（**geometry transforms**）は、  
ノードの****子ノードには影響せず、ノード自身の内容（例：メッシュ）だけを変換****します。

<span style="white-space: pre-wrap;">詳細は → </span>[Elements / Nodes / Geometry Transforms](/elements/nodes/#geometry-transforms)

<table id="bkmrk-%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E5%90%8D%E5%86%85%E5%AE%B9%22geometrictr"><colgroup><col></col><col></col></colgroup><tbody><tr><th>プロパティ名

</th><th>内容

</th></tr><tr><td>`<span class="editor-theme-code">"GeometricTranslation"</span>`

</td><td>ノード内コンテンツの平行移動

</td></tr><tr><td>`<span class="editor-theme-code">"GeometricScaling"</span>`

</td><td>ノード内コンテンツのスケール

</td></tr><tr><td>`<span class="editor-theme-code">"GeometricRotation"</span>`

</td><td>ノード内コンテンツの回転（常に XYZ 順）

</td></tr></tbody></table>

---

### 💻 Geometry Transform の計算例

```cpp
// 手動で `ufbx_node.geometry_to_node` を計算
Matrix4 get_geometry_transform(ufbx_node *node)
{
    ufbx_props *props = &node->props;

    Vector3 geo_translation = ufbx_find_vec3(props, "GeometricTranslation", ufbx_zero_vec3);
    Vector3 geo_scaling     = ufbx_find_vec3(props, "GeometricScaling", ufbx_zero_vec3);
    Vector3 geo_rotation    = ufbx_find_vec3(props, "GeometricRotation", ufbx_zero_vec3);

    Quaternion geo_quat = Quaternion_euler(geo_rotation, EulerOrder_XYZ);

    Matrix4 m = Matrix4_identity;
    m = Matrix4_scale_nonuniform(geo_scaling) * m;
    m = Matrix4_rotate(geo_quat) * m;
    m = Matrix4_translate(geo_translation) * m;

    return m;
}
```

---

<span style="white-space: pre-wrap;">📘 </span>****備考****  
このドキュメントは MIT / Public Domain ライセンスのもとで公開された  
[ufbx (c) 2020 Samuli Raivio](https://ufbx.github.io)<span style="white-space: pre-wrap;"> の内容を翻訳・整形しています。</span>