﻿/******************************************************************************
    DccShape の使用方法

    Date: 2018/02/14

    Copyright (C)Nintendo All rights reserved.
******************************************************************************/

// 改訂履歴

/******************************************************************************
    概要
******************************************************************************/

//-----------------------------------------------------------------------------
// DccShape
DccShape は DCC ツールから取得した頂点属性とプリミティブ情報を
fmd ファイルに出力するためのライブラリです。

//-----------------------------------------------------------------------------
// 頂点属性の種類
頂点属性には次のものがあります。
・頂点座標
・法線
・接線（最大 4 セットまで）
・従法線（最大 4 セットまで）
・頂点カラー（最大 8 セットまで）
・テクスチャ座標（最大 8 セットまで）
・ボーンインデックス
・スキンウェイト

//-----------------------------------------------------------------------------
// 頂点属性とプリミティブ情報
DCC プラグインは、取得した頂点属性とプリミティブ情報を
「値の配列」と「配列へのインデックス」の形式にして、
RShape オブジェクトに設定します。

例えば、三角形が 2 つあって、
三角形 0 の頂点座標が (0, 0, 0), (-1, 0, 0), (0, 1, 0)、
三角形 1 の頂点座標が (1, 0, 0), ( 0, 0, 0), (0, 1, 0) なら
「頂点座標の値の配列」は長さ 4 の RVec3Array で、
{
    {  0, 0, 0 }, // 0
    { -1, 0, 0 }, // 1
    {  0, 1, 0 }, // 2
    {  1, 0, 0 }, // 3
}
2 つの三角形の「頂点座標のインデックス」は
{ 0, 1, 2, 3, 0, 2 }
となります（反時計回りが表になる順番です）。

　　２
　／│＼
１─０─３

法線を出力する場合、
三角形 0 の法線が (0.0, 0.0, 1.0), (-0.6, 0.0, 0.8), (-0.6, 0.0, 0.8)、
三角形 1 の法線が (0.6, 0.0, 0.8), ( 0.0, 0.0, 1.0), ( 0.6, 0.0, 0.8) なら
「法線の値の配列」は長さ 5 の RVec3Array で、
{
    {  0.0, 0.0, 1.0 }, // 0
    { -0.6, 0.0, 0.8 }, // 1
    { -0.6, 0.0, 0.8 }, // 2
    {  0.6, 0.0, 0.8 }, // 3
    {  0.6, 0.0, 0.8 }, // 4
}
2 つの三角形の「法線のインデックス」は
{ 0, 1, 2, 3, 0, 4 }
となります（反時計回りが表になる順番です）。

　　２４
　／││＼
１─００─３

法線、接線、従法線、頂点カラー、テクスチャ座標の「値の配列」は、
このように同じ頂点を共有するプリミティブで値が同じなら
1 つにまとめておきます。
上記の例では「法線の値の配列」の 0 番目が 2 つの三角形から参照されています。
（異なる頂点でも値が同じならまとめても構いませんが、
  頂点数が増えると重複チェックの負荷が大きくなってしまいます）。

たとえば、平面の場合、
「頂点座標の値の配列」の長さと「法線の値の配列」の長さは等しいですが、
立方体では、「頂点座標の値の配列」の長さが 8、
「法線の値の配列」の長さが 24 (4 頂点 x 6 フェース) となります。

//-----------------------------------------------------------------------------
// シェイプアニメーションの頂点属性の値の配列
シェイプアニメーションで変化する頂点属性については、
すべてのキーシェイプで「値の配列」の長さが同じになるようにします。

まず、すべてのキーシェイプの各頂点について、
法線、接線、従法線、頂点カラーが、
頂点を共有するすべてのフェースで同じか、
頂点を共有するフェースによって異なるかを調査します。

ある頂点について、いずれかのキーシェイプで
頂点を共有するフェースによって頂点属性が異なる場合、
その頂点については頂点属性の値をまとめません。
頂点を共有するフェース数だけ頂点属性の値を「値の配列」に追加します。

ある頂点について、すべてのキーシェイプで
頂点を共有するすべてのフェースの頂点属性が同じであれば、
その頂点については頂点属性の値を 1 つだけ「値の配列」に追加します。

//-----------------------------------------------------------------------------
// 「値の配列」の型
「値の配列」の型は次のようにします。
・頂点座標              RVec3Array
・法線                  RVec3Array
・接線                  RVec4Array
・従法線                RVec4Array
・頂点カラー            RVec4Array
・テクスチャ座標        RVec2Array
・ボーンインデックスとスキンウェイト    RVtxMtxArray

接線と従法線の W 成分には、UV のワインド順序が時計回り（前向き）なら 1、
反時計回り（後ろ向き）なら -1 を出力します。

//-----------------------------------------------------------------------------
// 頂点座標・法線・接線・従法線の座標系
頂点座標・法線・接線・従法線の座標系はスキニングの状態によって異なります。

・スキニングなし（リジッドボディ）  → シェイプが属するボーンにおけるローカル座標
・リジッドスキニング                → 影響するボーンにおけるローカル座標
・スムーススキニング                → バインドポーズでのモデル座標

DCC ツールでスキニングが設定されていれば、
スキンウェイトがすべて 100%かつ
ボーンインデックスがすべての頂点で同じでも
リジッドボディではなくリジッドスキニングとして出力します。

//-----------------------------------------------------------------------------
// 頂点行列
ボーンインデックスとスキンウェイトは頂点行列（RVtxMtx 型）として
まとめて扱います。
スキンウェイトは [0, 100] の整数にします。
三角形 0 のボーンインデックスが ( 0,  1), ( 0,  1), ( 0,  1,  2)、
三角形 0 のスキンウェイトが     (20, 80), (50, 50), (20, 30, 50)、
三角形 1 のボーンインデックスが (  3), ( 3,  4), ( 0,  1)、
三角形 1 のスキンウェイトが     (100), (50, 50), (50, 50) なら、
「頂点行列の値の配列」は長さ 5 の RVtxMtxArray で、
{
    { { 0, 20 }, { 1, 80 } },
    { { 0, 50 }, { 1, 50 } },
    { { 0, 20 }, { 1, 30 }, { 2, 50 } },
    { { 3, 100 } },
    { { 3, 50 }, { 4, 50 } },
}
2 つの三角形の「頂点行列のインデックス」は
{ 0, 1, 2, 3, 4, 1 }
となります。

頂点行列はスキニングを使用しない場合でも用意する必要があります。
スキニングを使用しない場合、「頂点行列の値の配列」は
長さ 1 の RVtxMtxArray で、「1 つのボーンにウェイト 100」となります。

/******************************************************************************
    【手順 1】 ヘッダを include
******************************************************************************/

#include "DccShape.h"
using namespace nn::gfx::tool::dcc;

/******************************************************************************
    【手順 2】 RShape オブジェクトを生成
******************************************************************************/

// 標準シェーダの行列パレットサイズを取得します。
const int mtxPalCount = RShape::GetStandardMtxPalCount(false, 0);

// RShape オブジェクトを生成します。
RShape rshape(skinningMode, mtxPalCount);

/******************************************************************************
    【手順 3】 DCC ツールから取得した頂点属性とプリミティブ情報を設定
******************************************************************************/

//-----------------------------------------------------------------------------
// 頂点属性の使用フラグを設定します。
rshape.m_VtxAttrFlag[RPrimVtx::POS0] = true; // 常に true
rshape.m_VtxAttrFlag[RPrimVtx::NRM0] = (法線を出力するなら true);
for (int iTanSet = 0; iTanSet < (出力する接線セット数); ++iTanSet)
{
    // 接線は TAN0 から連続して使用します。
    rshape.m_VtxAttrFlag[RPrimVtx::TAN0 + iTanSet] = true;
}
for (int iBinSet = 0; iBinSet < (出力する従法線セット数); ++iBinSet)
{
    // 従法線は BIN0 から連続して使用します。
    rshape.m_VtxAttrFlag[RPrimVtx::BIN0 + iBinSet] = true;
}
for (int iCol = 0; iCol < RPrimVtx::VTX_COL_MAX; ++iCol)
{
    // 頂点カラーは color0 と color2 のみ出力して color1 は出力しないといった
    // ケースに対応します。
    if (iCol 番目の頂点カラーを出力)
    {
        rshape.m_VtxAttrFlag[RPrimVtx::COL0 + iCol] = true;
    }
}
for (int iTex = 0; iTex < (出力するテクスチャ座標数); ++iTex)
{
    // テクスチャ座標は TEX0 から連続して使用します。
    rshape.m_VtxAttrFlag[RPrimVtx::TEX0 + iTex] = true;
}
rshape.m_VtxAttrFlag[RPrimVtx::IDX0] = (skinningMode != RShape::NO_SKINNING);
rshape.m_VtxAttrFlag[RPrimVtx::Wgt0] = (skinningMode == RShape::SMOOTH);

//-----------------------------------------------------------------------------
// フェース（ポリゴン）についてループします。
for (int iFace = 0; iFace < (フェース数); ++iFace)
{
    const int vtxCount = (フェースの頂点数);

    // RPrimitive オブジェクトを生成します。
    RPrimitive rprim(vtxCount);

    // 各頂点属性の「配列へのインデックス」を設定します。
    for (int iVtx = 0; iVtx < vtxCount; ++iVtx)
    {
        RPrimVtx vtx;
        // 以下すべて iVtx 番目の頂点の「配列へのインデックス」を設定します。
        vtx.m_Mtx = (頂点行列のインデックス);
        vtx[RPrimVtx::POS0] = (頂点座標のインデックス);
        vtx[RPrimVtx::NRM0] = (rshape.m_VtxAttrFlag[RPrimVtx::NRM0]) ?
            (法線のインデックス) : -1;
        for (int iTanSet = 0; iTanSet < (出力する接線セット数); ++iTanSet)
        {
            vtx[RPrimVtx::TAN0 + iTanSet] =
                (iTanSet 番目の接線のインデックス);
        }
        for (int iBinSet = 0; iBinSet < (出力する従法線セット数); ++iBinSet)
        {
            vtx[RPrimVtx::BIN0 + iBinSet] =
                (iBinSet 番目の従法線のインデックス);
        }
        for (int iCol = 0; iCol < RPrimVtx::VTX_COL_MAX; ++iCol)
        {
            if (iCol 番目の頂点カラーを出力)
            {
                vtx[RPrimVtx::COL0 + iCol] =
                    (iCol 番目の頂点カラーのインデックス);
            }
        }
        for (int iTex = 0; iTex < (出力するテクスチャ座標数); ++iTex)
        {
            vtx[RPrimVtx::TEX0 + iTex] =
                (iTex 番目のテクスチャ座標のインデックス);
        }
        rprim.SetPrimVtx(iVtx, vtx);
    }

    // RShape オブジェクトにプリミティブを追加します。
    if (プリミティブがポイント)
    {
        rshape.AppendPoints(rprim, (頂点行列の値の配列));
    }
    else if (プリミティブがライン)
    {
        rshape.AppendLines(rprim, (頂点行列の値の配列));
    }
    else // プリミティブがポリゴン
    {
        const bool forceTriangulate = true; // 現在は常に三角形分割して出力します。
        const int triPattern = RGetAngleTriangulationPattern(フェースの頂点座標配列);
            // ↑三角形の角ができるだけ鋭角にならないように三角形分割します。
        if (!rshape.AppendPolygon(rprim, (頂点行列の値の配列), forceTriangulate, triPattern))
        {
            // 現在は失敗することはありません。
            // ShowError はエラー表示関数です。
            ShowError("Polygon bone size is over: %s", (シェイプ名));
            return (失敗);
        }
    }
}

/******************************************************************************
    【手順 4】 出力用データを設定して最適化
******************************************************************************/

rshape.SetAndOptimize(
    (頂点行列の値の配列),
    (ボーンに対する行列パレットインデックスの配列)
);

ボーンに対する行列パレットインデックスの配列は、
スムーススキニングの場合はスムーススキニング用のインデックス配列、
リジッドスキニングの場合はリジッドスキニング用のインデックス配列、
リジッドボディの場合はすべて -1 の配列を指定します。

/******************************************************************************
    【手順 5】 出力
******************************************************************************/

//-----------------------------------------------------------------------------
// ROutShapeInfo オブジェクトを生成します。
ROutShapeInfo outInfo;
outInfo.m_Index    = (<shape> の index に出力する値);
outInfo.m_Name     = (<shape> の name に出力する名前);
outInfo.m_MatName  = (<shape_info> の mat_name に出力する値);
outInfo.m_BoneName = (<shape_info> の bone_name に出力する値);

//-----------------------------------------------------------------------------
// シェイプの頂点属性出力情報を設定します。
// 出力しない頂点属性の情報は設定しなくても構いません。
outInfo.m_VtxInfos[RPrimVtx::POS0] = ROutShapeVtxInfo( // POS の array は RVec3Array のみ可
    3, RPrimVtx::VALUE_FLOAT, &(頂点座標の値の配列));
outInfo.m_VtxInfos[RPrimVtx::NRM0] = ROutShapeVtxInfo( // NRM の array は RVec3Array のみ可
    3, RPrimVtx::VALUE_FLOAT, &(法線の値の配列));

for (int iTanSet = 0; iTanSet < (出力する接線セット数); ++iTanSet)
{
    outInfo.m_VtxInfos[RPrimVtx::TAN0 + iTanSet] = ROutShapeVtxInfo( // TAN の array は RVec4Array のみ可
        4, RPrimVtx::VALUE_FLOAT, &(接線 iTanSet の値の配列));   // TAN は NRM と同じ valueType
}
for (int iBinSet = 0; iBinSet < (出力する従法線セット数); ++iBinSet)
{
    outInfo.m_VtxInfos[RPrimVtx::BIN0 + iBinSet] = ROutShapeVtxInfo( // Bin の array は RVec4Array のみ可
        4, RPrimVtx::VALUE_FLOAT, &(従法線 iBinSet の値の配列)); // BIN は NRM と同じ valueType
}

for (int iCol = 0; iCol < RPrimVtx::VTX_COL_MAX; ++iCol) // COL の array は RVec4Array のみ可
{
    if (iCol 番目の頂点カラーを出力)
    {
        outInfo.m_VtxInfos[RPrimVtx::COL0 + iCol] = ROutShapeVtxInfo(
            (頂点カラー iCol の成分数), // 1, 2, 3, 4
            (頂点カラー iCol の値の型), // RPrimVtx::VALUE_INT, VALUE_UINT, VALUE_FLOAT
            &(頂点カラー iCol の値の配列));
    }
}

for (int iTex = 0; iTex < (出力するテクスチャ座標数); ++iTex) // TEX の array は RVec2Array のみ可
{
    outInfo.m_VtxInfos[RPrimVtx::TEX0 + iTex] = ROutShapeVtxInfo(
        2, RPrimVtx::VALUE_FLOAT, &(テクスチャ座標 iTex の値の配列));
}

outInfo.m_VtxInfos[RPrimVtx::IDX0] = ROutShapeVtxInfo( // IDX0 は valueType のみ影響します。
    4, RPrimVtx::VALUE_UINT, NULL);
outInfo.m_VtxInfos[RPrimVtx::WGT0] = ROutShapeVtxInfo( // WGT0 は valueType のみ影響します。
    4, RPrimVtx::VALUE_FLOAT, NULL);

//-----------------------------------------------------------------------------
// シェイプアニメーションを使用する場合の頂点属性出力情報を設定します。
if (ベースシェイプ以外のキーシェイプを出力)
{
    // キーシェイプの名前の配列を設定します。
    // 配列の先頭はベースシェイプにします。
    for (int iKey = 0; iKey < (キーシェイプ数); ++iKey)
    {
        outInfo.m_KeyNames.push_back(キーシェイプ iKey の名前);
    }

    // シェイプアニメーションで変化する頂点属性について、
    // ベースシェイプ以外のキーシェイプの値の配列のポインタを配列に追加します。
    // シェイプアニメーションで変化しない頂点属性については追加しません。
    //
    // 以下のコード例では、(キーシェイプ数) がベースシェイプも含むので、
    // for 文の iKey が 1 から開始していることに注意してください。
    if (頂点座標が変化)
    {
        for (int iKey = 1; iKey < (キーシェイプ数); ++iKey)
        {
            outInfo.m_VtxInfos[RPrimVtx::POS0].m_pArrays.push_back(
                &(キーシェイプ iKey の頂点座標の値の配列));
        }
    }

    if (法線が変化)
    {
        for (int iKey = 1; iKey < (キーシェイプ数); ++iKey)
        {
            outInfo.m_VtxInfos[RPrimVtx::NRM0].m_pArrays.push_back(
                &(キーシェイプ iKey の法線の値の配列));
        }
    }

    if (接線が変化)
    {
        // 接線セットが 1 つでも変化するなら、全接線セットのターゲットの接線データを出力します。
        for (int iTanSet = 0; iTanSet < (出力する接線セット数); ++iTanSet)
        {
            for (int iKey = 1; iKey < (キーシェイプ数); ++iKey)
            {
                outInfo.m_VtxInfos[RPrimVtx::TAN0 + iTanSet].m_pArrays.push_back(
                    &(キーシェイプ iKey の接線 iTanSet の値の配列));
            }
        }
    }
    if (従法線が変化)
    {
        // 従法線セットが 1 つでも変化するなら、全従法線セットのターゲットの従法線データを出力します。
        for (int iBinSet = 0; iBinSet < (出力する従法線セット数); ++iBinSet)
        {
            for (int iKey = 1; iKey < (キーシェイプ数); ++iKey)
            {
                outInfo.m_VtxInfos[RPrimVtx::BIN0 + iBinSet].m_pArrays.push_back(
                    &(キーシェイプ iKey の従法線 iBinSet の値の配列));
            }
        }
    }

    for (int iCol = 0; iCol < RPrimVtx::VTX_COL_MAX; ++iCol)
    {
        if (頂点カラー iCol が変化)
        {
            for (int iKey = 1; iKey < (キーシェイプ数); ++iKey)
            {
                outInfo.m_VtxInfos[RPrimVtx::COL0 + iCol].m_pArrays.push_back(
                    &(キーシェイプ iKey の頂点カラー iCol の値の配列));
            }
        }
    }
}

//-----------------------------------------------------------------------------
// <shape> 要素を出力し、頂点データ配列とデータ列配列に値を追加します。
// RInitOutStreamFormat で出力ストリーム os の書式を設定しておきます。
rshape.Out(os, (頂点データ配列), (データ列配列), (インデントに必要なタブ数), outInfo);

//-----------------------------------------------------------------------------
// ここからは fmd ファイル全体の出力について説明します。

RDataStreamArray dataStreams; // fmd ファイル全体のデータ列配列です。

// <renderset_array>、<material_array>、<skeleton> を先に出力します。

// <vertex_array> の後に <shape_array> を出力するため
// RShape::Out は次のように使用します。
std::ostringstream shapeOs; // <shape> の出力ストリームです。
RInitOutStreamFormat(shapeOs);

RVertexArray vertices;
for (int iShape = 0; iShape < (シェイプ数); ++iShape)
{
    rshape.Out(shapeOs, vertices, dataStreams, 1, outInfo);
}

ROutArrayElement(os, 0, vertices, "vertex_array");

os << "<shape_array length=\"" << (シェイプ数) << "\">" << R_ENDL;
os << shapeOs.str();
os << "</shape_array>" << R_ENDL;

// <original_material_array> を出力します。

// 最後にデータ列配列を出力します。
if (アスキー形式)
{
    ROutDataStreams(os, dataStreams);
}

os << "</model>" << R_ENDL;
os << "</nw4f_3dif>" << R_ENDL;

if (バイナリ形式)
{
    ROutBinaryDataStreams(os, dataStreams, true);
}

/******************************************************************************
    改訂履歴
******************************************************************************/

■ 2018/02/14
R_VTX_NRM_TOLERANCE の値を 0.001 に変更（以前は 0.01）

■ 2014/03/17
RShape::AppendPolygon の引数に triPattern を追加
RGetAngleTriangulationPattern を追加

■ 2014/01/31
<shape_info> に polygon_reduction_mode を出力

■ 2013/05/31
ROutShapeInfo に m_pUvAttribNames を追加。

■ 2013/05/08
ROutShapeInfo に m_pUvHintIdxs を追加。

■ 2013/05/07
AdjustTangents の補整で、法線・接線・従法線がかならず直交するように修正。

■ 2012/09/10
ROutShapeInfo の m_RenderPriority 削除。

■ 2012/07/12
複数の接線と従法線の出力に対応。

■ 2012/06/28
RShape::Out の引数に result を追加。

■ 2012/06/22
補整した接線・従法線の長さが 0 なら (0, 1, 0) に置き換えるように修正

■ 2012/06/20
接線・従法線・法線が互いに直交するように、接線と従法線を補整して出力するように修正。

■ 2012/06/01
法線と接線から従法線を計算して出力するように変更。

■ 2012/04/13
マルチ頂点カラーのシェイプアニメーションで、<vtx_attrib> の hint が重複する場合が
あった不具合を修正。

■ 2011/11/10
enum をすべて大文字に変更。
シェイプアニメーションに対応。

■ 2011/10/31
ROutShapeInfo に m_MatName、m_BoneName、m_RenderPriority 追加。m_BoneIndex 削除。

■ 2011/09/22
接線と従法線の「値の配列」の型を RVec4Array に変更。

■ 2011/08/29
マルチ頂点カラーに対応。
「出力」の ROutShapeVtxInfo 設定変更。

■ 2011/05/23
「概要」の「頂点属性とプリミティブ情報」を修正。

■ 2011/05/12
初版。

