« C4D C.O.F.F.E.E. 前回作成したスプラインのポイント間の長さを取得する関数の修正。 | トップページ | C4D C.O.F.F.E.E. スナップツール »

C4D C.O.F.F.E.E. スプラインのポイント間の長さが取得できるようになったので、ベジェスプラインに勾配を付けてみる。

さて、ベジェスプラインに勾配?
わかりやすく言えば、坂道です。

ベジェスプラインの始点から終点までを、一定の傾斜になるようにします。

適当なXZ平面上のスプラインを用意します。

Inclination_01

始点から終点の高低差が200だとします。

Inclination_02

このベジェスプラインは曲線ですが、仮に直線だとします。
ハンドルの長さは、変えません。
では、このスプラインをグラフにするのですが、必要な数値は、下のスクリプトで取得します。

スプラインの始点からの各ポイントまでの長さを表示するスクリプトです。

/*
スプラインのポイント間の長さをコンソールに表示
2009.6.26
*/


//スプラインのポイント間の長さを取得
GetBetweenLength(spline , number)
{
  if(!instanceof(spline , SplineObject))return;
  if(spline#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_BSPLINE)return;
  if(spline->GetSegmentCount() >= 2)return;

  var ptc = spline->GetPointCount();
  if(number < 0 || number > ptc - 1)return;

  //開いているスプラインの場合で終点を指定した場合は、長さ0.0を返す
  if(number > ptc - 2 && !spline#SPLINEOBJECT_CLOSED)return 0.0;

  var dummy = spline->GetClone(CL_NO_HIERARCHY);

  var ct = new(BaseContainer);

  //ベジェに変更。線形の場合は変更後ハード補間
  var liner = FALSE;

  if(dummy#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_LINEAR)liner = TRUE;

  dummy#SPLINEOBJECT_TYPE = SPLINEOBJECT_TYPE_BEZIER;
  if(liner)SendModelingCommand(MCOMMAND_SPLINE_HARDINTERPOLATION , NULL , dummy , ct , MODIFY_ALL);

  //指定したポイントを選択
  var sel = new(BaseSelect);
  sel->Select(number);
  dummy->SetPointSelection(sel);
  //始点の設定
  SendModelingCommand(MCOMMAND_SPLINE_REORDER , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //次のポイントを選択
  sel = dummy->GetPointSelection();
  sel->Select(1);
  dummy->SetPointSelection(sel);

  //選択の反転
  SendModelingCommand(MCOMMAND_SELECTINVERSE , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //ポイントの削除
  SendModelingCommand(MCOMMAND_DELETE , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //スプラインが閉じていれば開きます。
  if(dummy#SPLINEOBJECT_CLOSED)dummy#SPLINEOBJECT_CLOSED = FALSE;

  //長さの取得し返す
  dummy->InitLength(0);

  return dummy->GetLength();
}



main(doc , op)
{
  if(!instanceof(op , SplineObject))return;

  var ptc = op->GetPointCount();
  var i;
  var sum = 0;

  println("------" , op->GetName() , " 始点から各ポイントまでの長さ------");
  for(i = 0 ; i < ptc ; i++){
    println(i , " : " , sum);
    sum += GetBetweenLength(op , i);
  }
  println("全長 : " , sum);
}

表示結果です。

------スプライン 始点から各ポイントまでの長さ------
0 : 0
1 : 629.968201
2 : 1013.641418
3 : 1166.529297
4 : 1259.288208
5 : 1834.032471
全長 : 1834.032471

ハンドルの長さをコンソールに表示するスクリプトです。

/*
スプラインのハンドルの長さをコンソールに表示します。
2009.6.26
*/


main(doc , op)
{
  //対象外を排除
  if(!instanceof(op , SplineObject))return;
  if(op#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_LINEAR)return;
  if(op#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_BSPLINE)return;

  //ポイント数とハンドルの取得
  var ptc = op->GetPointCount();
  var tgs = op->GetTangents();

  var i;

  //コンソールにハンドルの長さを表示
  println("------" , op->GetName() , " ハンドルの長さ------");
  for(i = 0 ; i < ptc ; i++){
    println(i , " <- : " , vlen(tgs[i * 2]));
    println(i , " -> : " , vlen(tgs[i * 2 + 1]));
  }
}

表示結果です。

------スプライン ハンドルの長さ------
0 -> : 0.000000
0 <- : 141.007233
1 -> : 141.007202
1 <- : 53.202797
2 -> : 48.728447
2 <- : 26.370855
3 -> : 26.394646
3 <- : 22.638380
4 -> : 22.638369
4 <- : 64.663605
5 -> : 79.772812
5 <- : 0.000000

求めた数値をグラフにします。
始点からの長さを横軸。
ハンドルの長さは、各ポイントから差し引きした位置に配置しました。

わかり易くするために、見た目の高さは5倍の長さにしてあります。

Inclination_03_2

では、各ポイントを斜めの線に配置します。

Inclination_04

ハンドルの調整をしていないので、スプラインがうねっています。

ハンドルも垂直に移動させ斜線に揃え、スプラインを直線にします。

Inclination_05

これで、完成です。
さて、これを曲線のスプラインで、同じように作業します。

/*
ベジェスプラインに勾配を付けます。
2009.6.26
*/


//スプラインのポイント間の長さを取得
GetBetweenLength(spline , number)
{
  if(!instanceof(spline , SplineObject))return;
  if(spline#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_BSPLINE)return;
  if(spline->GetSegmentCount() >= 2)return;

  var ptc = spline->GetPointCount();
  if(number < 0 || number > ptc - 1)return;

  //開いているスプラインの場合で終点を指定した場合は、長さ0.0を返す
  if(number > ptc - 2 && !spline#SPLINEOBJECT_CLOSED)return 0.0;

  var dummy = spline->GetClone(CL_NO_HIERARCHY);

  var ct = new(BaseContainer);

  //ベジェに変更。線形の場合は変更後ハード補間
  var liner = FALSE;

  if(dummy#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_LINEAR)liner = TRUE;

  dummy#SPLINEOBJECT_TYPE = SPLINEOBJECT_TYPE_BEZIER;
  if(liner)SendModelingCommand(MCOMMAND_SPLINE_HARDINTERPOLATION , NULL , dummy , ct , MODIFY_ALL);

  //指定したポイントを選択
  var sel = new(BaseSelect);
  sel->Select(number);
  dummy->SetPointSelection(sel);
  //始点の設定
  SendModelingCommand(MCOMMAND_SPLINE_REORDER , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //次のポイントを選択
  sel = dummy->GetPointSelection();
  sel->Select(1);
  dummy->SetPointSelection(sel);

  //選択の反転
  SendModelingCommand(MCOMMAND_SELECTINVERSE , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //ポイントの削除
  SendModelingCommand(MCOMMAND_DELETE , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //スプラインが閉じていれば開きます
  if(dummy#SPLINEOBJECT_CLOSED)dummy#SPLINEOBJECT_CLOSED = FALSE;

  //長さの取得し返す
  dummy->InitLength(0);

  return dummy->GetLength();
}


main(doc , op)
{
  if(!instanceof(op , SplineObject))return;
  if(op#SPLINEOBJECT_CLOSED)return;
  if(op#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_LINEAR)return;
  if(op#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_BSPLINE)return;

  var ptc = op->GetPointCount();
  var pts = op->GetPoints();
  var tgs = op->GetTangents();
  var lns = new(array , ptc);
  var sum = 0;
  var i;
  var height = 200.0;

  //各ポイントの始点からの長さを格納
  for(i = 0 ; i < ptc ; i++){
    lns[i] = sum;
    sum += GetBetweenLength(op , i);
  }

  var rate = height / lns[ptc - 1];

  //ポイントとハンドルの調整
  for(i = 0 ; i < ptc ; i++){
    pts[i].y = pts[0].y + lns[i] * rate;
    tgs[i * 2].y = -vlen(tgs[i * 2]) * rate;
    tgs[i * 2 + 1].y = vlen(tgs[i * 2 + 1]) * rate;
  }

  //オブジェクトの更新
  op->SetPoints(pts);
  op->SetTangents(tgs);
  op->Message(MSG_UPDATE);
}

さて、このスクリプトを先ほどのスプラインに実行すると、こうなります。

Inclination_06

見事に、目標地点に到達しています。

では、このスプラインの傾斜は均等なのだろうか?

調べる方法として、スプライン上の位置とその高さを調べます。

高さが、200なので…

スプライン上の位置が50%なら、高さは100.0という事ですね。
下が関係式と数値です。これと比較します。

10% 200.0 × 10% 20.0
20% 200.0 × 20% 40.0
30% 200.0 × 30% 60.0
40% 200.0 × 40% 80.0
50% 200.0 × 50% 100.0
60% 200.0 × 60% 120.0
70% 200.0 × 70% 140.0
80% 200.0 × 80% 160.0
90% 200.0 × 90% 180.0

こんな具合だね。
では、勾配を適用したスプラインを均等補間にします。

Inclination_10

あとは、ヌルオブジェクトに「スプラインに沿う」エクスプレッションタグを取り付けて、Y座標を確認します。

Inclination_07

では、実際に作業して見ます。

実数値 目標数値
10% 17.669 m 20.0
20% 35.163 m 40.0
30% 57.731 m 60.0
40% 81.392 m 80.0
50% 100.492 m 100.0
60% 119.799 m 120.0
70% 140.35 m 140.0
80% 161.69 m 160.0
90% 181.035 m 180.0

数値は、遠からず…でも近くもなく…誤差が大きい。

特にポイントが離れている所の誤差が、大きくなっています。

では、ポイント0番と1番、1番と2番、4番と5番の間を細分化してから勾配を実行します。

Inclination_08

勾配になっているスプラインを細分化しても駄目ですよ。

細分化したスプラインに勾配を適用します。

Inclination_09_2

このスプラインで、先ほどと同じように試してみます。

実数値 目標数値
10% 19.968 m 20.0
20% 40.011 m 40.0
30% 59.261 m 60.0
40% 80.291 m 80.0
50% 99.926 m 100.0
60% 119.804 m 120.0
70% 140.211 m 140.0
80% 160.079 m 160.0
90% 180.02 m 180.0

先ほどの結果より、誤差は少なくなりました。

ポイントが少ないときは、増やしてから適用しましょう。

ただね、これはCAD(図面)ではないので、そんなに正確でなくてもいいのなら、少しぐらい誤差があっても見た目に問題なければ、重要ではないのだ。

このスクリプトは、高さ200.0に限定なので、ダイアログを使って入力できるようにしましょう。

ベジェスプラインが平面ではない場合があります。
処理を開始する前に、始点を基準に平らにします。

/*
ベジェスプラインに勾配を付けます。
2009.6.26
*/


//スプラインのポイント間の長さを取得
GetBetweenLength(spline , number)
{
  if(!instanceof(spline , SplineObject))return;
  if(spline#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_BSPLINE)return;
  if(spline->GetSegmentCount() >= 2)return;

  var ptc = spline->GetPointCount();
  if(number < 0 || number > ptc - 1)return;

  //開いているスプラインの場合で終点を指定した場合は、長さ0.0を返す
  if(number > ptc - 2 && !spline#SPLINEOBJECT_CLOSED)return 0.0;

  var dummy = spline->GetClone(CL_NO_HIERARCHY);

  var ct = new(BaseContainer);

  //ベジェに変更。線形の場合は変更後ハード補間
  var liner = FALSE;

  if(dummy#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_LINEAR)liner = TRUE;

  dummy#SPLINEOBJECT_TYPE = SPLINEOBJECT_TYPE_BEZIER;
  if(liner)SendModelingCommand(MCOMMAND_SPLINE_HARDINTERPOLATION , NULL , dummy , ct , MODIFY_ALL);

  //指定したポイントを選択
  var sel = new(BaseSelect);
  sel->Select(number);
  dummy->SetPointSelection(sel);
  //始点の設定
  SendModelingCommand(MCOMMAND_SPLINE_REORDER , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //次のポイントを選択
  sel = dummy->GetPointSelection();
  sel->Select(1);
  dummy->SetPointSelection(sel);

  //選択の反転
  SendModelingCommand(MCOMMAND_SELECTINVERSE , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //ポイントの削除
  SendModelingCommand(MCOMMAND_DELETE , NULL , dummy , ct , MODIFY_POINTSELECTION);

  //スプラインが閉じていれば開きます
  if(dummy#SPLINEOBJECT_CLOSED)dummy#SPLINEOBJECT_CLOSED = FALSE;

  //長さの取得し返す
  dummy->InitLength(0);

  return dummy->GetLength();
}


//ダイアログ
class tools_dialog : GeModalDialog
{
  public:
    var height;

    tools_dialog();
    Init();
    CreateLayout();
    AskClose();
}

tools_dialog::tools_dialog()
{
  super();
  height = 100.0;
}

tools_dialog::Init()
{
  SetFloat(1002 , height , 0.0 , 1000000000.0 , 0.1);
}

tools_dialog::CreateLayout()
{
  SetTitle("スプラインに勾配");
  AddGroupBeginV(1000 , BFV_SCALEFIT , 2 , "tools group" , 0);
    AddStaticText(1001 , 0 , 0 , 0 , "高さ" , 0);
    AddEditNumber(1002, 0, 150, 0);
  AddGroupEnd();
  AddDlgGroup(DR_DLGGROUP_OK | DR_DLGGROUP_CANCEL);
}

tools_dialog::AskClose()
{
  height = GetFloat(1002);
}

ToolsExecution(obj , height)
{
  var ptc = obj->GetPointCount();
  var pts = obj->GetPoints();
  var tgs = obj->GetTangents();
  var lns = new(array , ptc);
  var sum = 0;
  var i;

  //スプラインがXZ同一平面上に無い場合の対処
  for(i = 0 ; i < ptc ; i++){
    pts[i].y = pts[0].y;
    tgs[i * 2].y = 0.0;
    tgs[i * 2 + 1].y = 0.0;
  }
  obj->SetPoints(pts);
  obj->SetTangents(tgs);
  obj->Message(MSG_UPDATE);

  //各ポイントの始点からの長さを格納
  for(i = 0 ; i < ptc ; i++){
    lns[i] = sum;
    sum += GetBetweenLength(obj , i);
  }

  var rate = height / lns[ptc - 1];

  //ポイントとハンドルの調整
  for(i = 0 ; i < ptc ; i++){
    pts[i].y = pts[0].y + lns[i] * rate;
    tgs[i * 2].y = -vlen(tgs[i * 2]) * rate;
    tgs[i * 2 + 1].y = vlen(tgs[i * 2 + 1]) * rate;
  }

  //オブジェクトの更新
  obj->SetPoints(pts);
  obj->SetTangents(tgs);
  obj->Message(MSG_UPDATE);
}




main(doc , op)
{
  if(!object(0))return;

  var t_dialog = new(tools_dialog);
  t_dialog->Open(-1 , -1);
  if(!(t_dialog->GetResult()))return;

  var i;

  doc->StartUndo();
  for(i = 0 ; object(i) ; i++){
    if(!instanceof(object(i) , SplineObject))continue;
    if(object(i)#SPLINEOBJECT_CLOSED)continue;
    if(object(i)->GetSegmentCount() >= 2)continue;
    if(object(i)#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_LINEAR)continue;
    if(object(i)#SPLINEOBJECT_TYPE == SPLINEOBJECT_TYPE_BSPLINE)continue;

    doc->AddUndo(UNDO_OBJECT_BASEDATA , object(i));

    ToolsExecution(object(i) , t_dialog->height);
  }
  doc->EndUndo();
}

メニューの状態です。

var i;
ENABLE = FALSE;
for(i = 0 ; object(i) ; i++){
  if(instanceof(object(i) , SplineObject))ENABLE = TRUE;
}

これで、完成だろうか?

|

« C4D C.O.F.F.E.E. 前回作成したスプラインのポイント間の長さを取得する関数の修正。 | トップページ | C4D C.O.F.F.E.E. スナップツール »