// このコードではコメントがdoxygenスタイルになっていますが、作者がUVTexIntegraの作成時にdoxygenを使っていただけですので、とくに気にしないで下さい。
using System;
using System.ComponentModel;
using UVTexIntegra.Scripting;

//// UVTexIntegraで焼き込み処理をカスタマイズするには、
//// UVTexIntegra.Scripting.ScriptMainを継承したクラスをpublicに公開するdllを作ってUVTexIntegraに読み込ませます。
//// 
//// <dllをコンパイルするのに参照設定が必要なDLL>
//// - System.dll
//// - System.Drawing.dll
//// - OpenTK.dll
//// - UVTexIntegra.Scripting.dll
////
//// 多分これくらいです(実行時コンパイルする時は mscorlib.dll も要るはず)。
////
//// 一応スクリプトと銘打っているので実行時コンパイルも対応していくつもりなんですが、
//// VC#の入力補完が便利すぎてVC#インストールしてdll作っちゃうほうがラクじゃないのかな…と思っています。

//! @brief 鋭角ベイクスクリプト
//! @details モデルが尖っているほど黒くなるテクスチャ焼き込みスクリプトのサンプルです。
//! 頂点を共有する全ての面の法線の平均を頂点の法線とした時、頂点の法線と面の法線の角度差が大きいほど頂点色が黒くなります。
[Serializable, UVTexIntegra.Scripting.SerializeSave(true)]
public class EdgeBake :
    UVTexIntegra.Scripting.ScriptMain,          // ScriptMainを継承してください。
    System.Runtime.Serialization.ISerializable  // パラメータを保存する時は必要
{
    //// このクラスで使う独自型の定義 ////

    //! @brief 補間モードの定義です。
    public enum InteropMode
    {
        //! @brief 直線補間を行います。デフォルトの値です。
        Linear,
        //! @brief 三角関数曲線を使って、ゆるやかなS字を描いて補完します。
        Angle
    };

    //! @brief スムージングモードの定義です。
    public enum SmoothMode
    {
        //! @brief スムージングを行いません
        None,
        //! @brief 最も明るい色を採用します
        Max,
        //! @brief 最も暗い色を採用します
        Min,
        //! @brief 平均（単純な相加平均）を採用します
        Average
    }

    //// スクリプト名 ////
    //// Nameプロパティをオーバーライドしてスクリプトの名前をカスタマイズします。オーバーライドしない時は、クラス名が返されます。
    public override string Name
    {
        get
        {
            return "鋭角ベイク";
        }
    }

    //// 独自のプロパティ定義 ////
    //// 読み書き可能なプロパティをpublicに公開すると、UVTexIntegraのプロパティグリッドから実行時に値を調整できるようになります。
    //// 例えば、焼き込みプラグインでの焼き込みの強さ調整や、頂点色にビットマスクをかけるプラグインでのビットマスク値を設定できると便利です。
    //// （もちろん公開するenumや独自のクラス型などもpublicに公開する必要があります）

    //! @brief 補間モードを取得,設定します。
    [DefaultValue(InteropMode.Linear)]
    [Description("ホワイトアウト/ブラックアウトの補間モードを指定します。")]
    public InteropMode Interop { get; set; }

    //! @brief ブラックアウト角を取得,設定します。
    //! @details 頂点の法線と面の法線の角度差がこの角度より大きい時、焼きこむ頂点色がブラックアウトします。0～180度以内で、かつホワイトアウト角より大きい値を設定してください。
    //! もしホワイトアウト角より小さい値を設定するとホワイトアウト角が小さくなります。
    [DefaultValue(180.0f)]
    [Description("ブラックアウト角を設定します。頂点の法線と面の法線の角度差がこの角度より大きい時、焼きこむ頂点色がブラックアウトします。0～180度以内で、かつホワイトアウト角より大きい値を設定してください。")]
    public float BlackoutAngle
    {
        get { return m_blackoutAngle; }
        set
        {
            value = Math.Min(Math.Max(value, 0.0f), 180.0f);
            m_blackoutAngle = value;
            m_whiteoutAngle = System.Math.Min(value, m_whiteoutAngle);
            OnResetParams();
        }
    }

    //! @brief スムージングモードを取得,設定します。
    //! @details 同じUV座標を共有する時のスムージングモードを取得,設定します。
    [DefaultValue(SmoothMode.Average)]
    [Description("同じUV座標を共有する時のスムージングモードを指定します。None-無効/Max-明るい最大値/Min-暗い最小値/Average-単純な相加平均")]
    public SmoothMode Smooth { get; set; }

    //! @brief ホワイトアウト角を取得,設定します。
    //! @details 頂点の法線と面の法線の角度差がこの角度より小さい時、焼きこむ頂点色がホワイトアウトします。0～180度以内で、かつブラックアウト角より小さい値を設定してください。
    //! もしブラックアウト角より大きい値を設定するとブラックアウト角が大きくなります。
    [DefaultValue(0.0f)]
    [Description("ホワイトアウト角を設定します。頂点の法線と面の法線の角度差がこの角度より小さい時、焼きこむ頂点色がホワイトアウトします。0～180度以内で、かつブラックアウト角より小さい値を設定してください。")]
    public float WhiteoutAngle
    {
        get { return m_whiteoutAngle; }
        set
        {
            value = Math.Min(Math.Max(value, 0.0f), 180.0f);
            m_whiteoutAngle = value;
            m_blackoutAngle = System.Math.Max(value, m_blackoutAngle);
            OnResetParams();
        }
    }

    //! @brief デフォルトコンストラクタ
    public EdgeBake()
    {
        Interop = InteropMode.Linear;
        m_blackoutAngle = 180.0f;
        m_whiteoutAngle = 0.0f;
        Smooth = SmoothMode.Average;
        OnResetParams();
    }

    //// プロパティ状態の読込・書出のカスタマイズ ////
    //// 以下の手順を踏めばプロパティの状態を保存するように出来ます。
    //// 1. クラスが System.Runtime.Serialization.ISerializable を実装する。
    ////    >> void GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext) メソッドを公開する
    //// 2. (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext) を引数にするコンストラクタを公開する
    ////
    //// ちなみに、SerializationInfoクラスが、要素名とプロパティの値オブジェクトを格納するようになっています。

    //! @brief デシリアライズ処理のカスタマイズ
    public EdgeBake(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext stream)
        : this()
    {
        try
        {
            Interop = (InteropMode)(info.GetValue("Interop", typeof(InteropMode)));
        }
        catch (System.Exception)
        {
        }

        try
        {
            m_blackoutAngle = info.GetSingle("bkout");
        }
        catch (System.Exception)
        {
        }
        
        try
        {
            m_whiteoutAngle = info.GetSingle("whout");
        }
        catch (System.Exception)
        {
        }

        try
        {
            Smooth = (SmoothMode)(info.GetValue("Smooth", typeof(SmoothMode)));
        }
        catch (System.Exception) { }

        OnResetParams();
    }

    //! @brief シリアライズ処理のカスタマイズ
    public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext stream)
    {
        info.AddValue("Interop", Interop);
        info.AddValue("bkout", m_blackoutAngle);
        info.AddValue("whout", m_whiteoutAngle);
        info.AddValue("Smooth", Smooth);
    }

    //// UV操作のカスタマイズ ////
    //// void OnUVOperation(ScriptMain.Face[] faces) をオーバーライドすると出力する面情報を加工できます。

    //! @brief UV操作のカスタマイズ
    public override void OnUVOperation(ScriptMain.Face[] faces)
    {
        // 面法線と頂点法線の角度差を計算して色に変換
        foreach (ScriptMain.Face face in faces)
        {
            for (int index = 0; index < face.Points.Length; index++)
            {
                // 面法線と頂点法線で内積。内積が角度差をcosで表すので、それを0～1にマップします。
                double dot_f = OpenTK.Vector3.Dot(face.Normal, face.Points[index].Normal) * 0.5 + 0.5;
                
                // 事前計算済みのパラメータを使ってブラックアウトとホワイトアウトを調整します。
                dot_f = System.Math.Max(System.Math.Min(dot_f / m_div + m_ofs, 1.0), 0.0);

                // InteropMode.Angleが指定される時は、0-1の直線補間からゆるやかなS字（コサインカーブ）に置換し、再び0-1に再マップ。
                if (Interop == InteropMode.Angle) dot_f = System.Math.Cos((1.0 + dot_f) * System.Math.PI) * 0.5 + 0.5;
                
                // RGBに変換
                int dot = System.Math.Max(System.Math.Min((int)(255.0 * dot_f), 255), 0);
                
                // １つの頂点ごとに計算結果を反映
                face.Points[index].Color = System.Drawing.Color.FromArgb(0xFF, dot, dot, dot);
            }
        }

        // 基本的にはUV座標情報の書き換えフックはここまでです。
        // ただ頂点色ベイクという処理の都合上スムージングした方が良いのでスムージング処理も書いておきます。
        
        // スムージング処理
        if (Smooth != SmoothMode.None)
        {
            // ScriptMain.Face.ShareRelation.GetRelationsSnap()メソッドを使うと、
            // その時点で、ほとんど同じ座標（座標の差がきわめて小さい）のUV頂点を並べたデータを返します。
            // (*1) 作った時点でのスナップショットなので、UV座標を書き換えると関連性は正しくない状態になります。
            //      UV座標を書き換えるときはそれが終わってからGetRelationsSnapするか、その都度作り直さないと整合性がなくなります。
            // (*2) ここでは同じ座標とみなす許容誤差にfloat.Epsilon * 4.0fをとりあえず設定しています(この数値にとくに論拠はありません)。
            // (*3) UV頂点はひとつのShareRelationにしか属さないので、重複登録されたりはしません。
            System.Collections.ObjectModel.ReadOnlyCollection<ScriptMain.Face.ShareRelation> shareSnap =
                ScriptMain.Face.ShareRelation.GetRelationsSnap(faces, float.Epsilon * 4.0f);
            
            switch (Smooth)
            {
                // 相加平均モード。いわゆる普通の平均値
                case SmoothMode.Average:

                    // まとめられたUV情報(ScriptMain.Face.ShareRelation)を総当り
                    foreach (ScriptMain.Face.ShareRelation rel in shareSnap)
                    {
                        if (1 < rel.Owners.Count)
                        {
                            int sum = 0;
                            // このShareRelationでUVを共有する全てのオーナーのR要素だけ全部合計
                            foreach (Face.ShareRelation.IReference reference in rel.Owners) sum += reference.Point.Color.R;
                            // 要素数で割って平均値。一応オーバーフローしないようにしてるけどいらないかも
                            sum = System.Math.Min((int)(sum / rel.Owners.Count), 0xFF);
                            // このShareRelationでUVを共有する全てのオーナーのカラーを平均値で更新
                            System.Drawing.Color color = System.Drawing.Color.FromArgb(0xFF, sum, sum, sum);
                            foreach (Face.ShareRelation.IReference reference in rel.Owners) reference.Point.Color = color;
                        }
                    }

                    // おしまい。
                    break;
                
                // 最大値モード
                case SmoothMode.Max:

                    // まとめられたUV情報(ScriptMain.Face.ShareRelation)を総当り
                    foreach (ScriptMain.Face.ShareRelation rel in shareSnap)
                    {
                        if (1 < rel.Owners.Count)
                        {
                            int max = 0;
                            // このShareRelationでUVを共有する全てのオーナーのR要素の最大値をゲット
                            foreach (Face.ShareRelation.IReference reference in rel.Owners) max = System.Math.Max(reference.Point.Color.R, max);
                            // このShareRelationでUVを共有する全てのオーナーのカラーを最大値で更新
                            System.Drawing.Color color = System.Drawing.Color.FromArgb(0xFF, max, max, max);
                            foreach (Face.ShareRelation.IReference reference in rel.Owners) reference.Point.Color = color;
                        }
                    }

                    // おしまい。
                    break;

                // 最小値モード
                case SmoothMode.Min:

                    // まとめられたUV情報(ScriptMain.Face.ShareRelation)を総当り
                    foreach (ScriptMain.Face.ShareRelation rel in shareSnap)
                    {
                        if (1 < rel.Owners.Count)
                        {
                            int min = 0xFF;
                            // このShareRelationでUVを共有する全てのオーナーのR要素の最小値をゲット
                            foreach (Face.ShareRelation.IReference reference in rel.Owners) min = System.Math.Min(reference.Point.Color.R, min);
                            System.Drawing.Color color = System.Drawing.Color.FromArgb(0xFF, min, min, min);
                            // このShareRelationでUVを共有する全てのオーナーのカラーを最小値で更新
                            foreach (Face.ShareRelation.IReference reference in rel.Owners) reference.Point.Color = color;
                        }
                    }

                    // おしまい。
                    break;
            }
        }
    }

    private float m_whiteoutAngle;
    private float m_blackoutAngle;
    private double m_div;
    private double m_ofs;

    //! @brief パラメータがセットされたときに呼び出して、付随する内部パラメータを更新
    private void OnResetParams()
    {
        double wout = 1.0 - (m_whiteoutAngle / 180.0);
        double bout = 1.0 - (m_blackoutAngle / 180.0);
        m_div = wout - bout;
        m_ofs = 1.0 - wout / m_div;
    }
}