このようなパラメトリックなログキャビンをほんの数行のC ++コードでモデル化する方法を誰が気にしますか?
ここ数年、私たちはプロジェクトでsgCoreソリッドモデリングライブラリの無料版を使用しています( ここに機能の簡単な概要があります)。 最近、著者はiOS用のバージョンをリリースしました。このプラットフォームは活発に開発されているので、このライブラリの小さな紹介例は多くの人にとって大きな関心事になると思います。 わずかな手順で、かなり複雑なモデルを作成するために使用する方法を小さな例を使って示したいと思います。
この記事のデモプロジェクトのソースは次のとおりです。
iOS
マック
勝つ
パラメトリックログハウスのモデルを作成します。 複雑にならないように、ログセクションの半径、家の幅、家の長さの3つのパラメーターのみを紹介します。 実際の問題ではパラメーターの数がはるかに多くなることは明らかですが、この例では、新しいパラメーターを導入しても何も新しくなりません。 したがって、1つの関数を呼び出すだけで再構築される家のモデルを構築する必要があります
void Build(sgFloat log_size, sgFloat houseSizeX, sgFloat houseSizeY);
最初に、ライブラリコアを初期化します。 これは、sgCore関数を最初に使用する前に、メインスレッドで1回実行する必要があります。
sgInitKernel();
最初のステップは、ログのセクションを作成することです。 ログが互いに横たわるだけでなく、多少安定した構造を形成するために、断面形状を円形ではなく、2つの円弧で構成します。
同様のセクションのログは、2つの方法で取得できます。フラットセクション自体を作成し、フラットな輪郭を押し出す操作を使用するか、同じ半径の2つのシリンダーを作成し、シリンダーの1つをシフトしてブール減算操作を適用します。 押し出し操作はブール減算よりも速いため、最初の方法を使用します。
2つの円弧のセクションを作成します。 ログを互いにぴったりと合わせるには、これらの円弧の半径を同じにする必要があります。
2つの円弧を作成するには、同じ半径の2つの円の交点を計算する必要があります。 この半径はモデルのパラメーターの1つです。したがって、ログの輪郭を構成する関数には1つの入力パラメーター(ログの半径)があります。
void BuildLogSection(sgFloat log_size);
3つのポイントで最初のセクションアークを作成します。
これら2つの円の交点を定義する角度を45度に等しくします。 その場合、最初の円弧を定義するポイントは次の座標になります。
SG_POINT arc1BeginP = {log_size*cosCalc, -log_size*sinCalc, 0}; SG_POINT arc1EndP = {-log_size*cosCalc, -log_size*sinCalc, 0}; SG_POINT arc1MidP = {0, log_size, 0};
ここで、log_sizeはログの半径であり、sinCalcとcosCalcは角度のサインとコサインであり、合意したとおり、45度です。 異なる角度を設定することにより、2つの円弧で構成されるセクションに対して膨大な数の異なるオプションを取得できます。
次の3つのポイントで円弧構造を作成します。
SG_ARC arc1; arc1.FromThreePoints(arc1BeginP, arc1EndP, arc1MidP, false);
最初の3つの引数は計算されたポイントであり、最後の引数は3つのポイントによって与えられるアークを反転するかしないかです。 このパラメーターがfalseの場合、アークの中間点は作成中のアーク上にあり、trueの場合、最初の方法で作成されたアークを完全な円に補完するアークを作成します。
別の方法で2番目の円弧を作成します-円弧の平面の法線と半径の2つの終点で。 2番目の弧の終点は最初の弧の終点と一致し、弧の平面の法線はZ軸、つまりベクトル(0,0,1)であり、半径は最初の弧と同じlog_sizeです。 2番目のアークの構造を構築します。
SG_ARC arc2; arc2.FromBeginEndNormalRadius(arc1BeginP, arc1EndP, zAxe, log_size, false);
次に、ジオメトリカーネル自体のオブジェクト(sgCObjectの継承者)を構築する必要があります。 アークの場合、これらはクラスsgCArcのオブジェクトになります。 将来、これらの弧に沿って輪郭を作成し、すぐに弧を配列に保存するので:
sgCObject* acrsObjs[2]; acrsObjs[0] = sgCArc::Create(arc1); acrsObjs[1] = sgCArc::Create(arc2);
そして、ログを作成するために将来使用するパス自体を作成します。
m_log_section = sgCContour::CreateContour(acrsObjs, 2);
セクションを作成したら、ログを作成できます。 さまざまな長さのログを作成する必要があるため、セクションを任意の長さに押し出す関数を作成します。
sgC3DObject* BuildLog(sgFloat logH);
XOY平面に平らな輪郭を作成したので、Z軸に沿って(断面平面に垂直に)対数断面輪郭を押し出します。
SG_VECTOR extrVect = {0,0, logH}; sgCObject* logObj = sgKinematic::Extrude(*m_log_section, NULL, 0, extrVect, true);
押し出し関数の最初の引数は押し出す外側の輪郭、2番目の引数は押し出されたオブジェクトの穴の配列、外側の輪郭の内側にある平らな輪郭、そして3番目の引数は穴の数です。 家が建てられた丸太には丸太の全長に貫通穴がないため、これらの2つの引数はゼロです。 4番目の引数-押し出しベクトル-Z軸に沿って方向付けられ、ログ作成関数の引数-logH-を持ちます。 最後の引数は、作成されたオブジェクトをソリッドにロックするか、単純に側面押し出しサーフェスを作成することです。 つまり、「ボトム」を作成する必要があるかどうか。 私たちにはそれは本当です。
任意の長さの最初のログを作成して、シーンに追加しましょう:
sgGetScene()->AttachObject(BuildLog(houseSizeX));
プログラムを開始すると、空のシーンが表示されます。 ログはどこにありますか?
実際、オブジェクトの三角形の配列ではなく、ワイヤフレームモデルを使用してログを作成すると、ログが作成されます。 これまでのところ、ログには三角形がありません。したがって、何も表示されませんでした。
3次元オブジェクトを三角形の形で表示するには、オブジェクトを三角形分割する必要があります。 これは、作成されたすべての3次元オブジェクトに対して自動的に行うことも、各オブジェクトに対して手動で行うこともできます。 自動三角形分割の場合、フラグを有効にする必要があります。
sgC3DObject::AutoTriangulate(true, SG_DELAUNAY_TRIANGULATION);
2番目の引数は、三角形分割のタイプです。 三角測量のタイプについては、sgCoreのドキュメントで詳しく説明しています。
しかし、微妙な点が1つあります。 非常に複雑なオブジェクトの三角測量には時間がかかる場合があります。 また、オブジェクトを使用して操作するには、三角測量自体は必要ありません-最終結果を視覚化することのみが必要です。 したがって、複雑なオブジェクトを中間オブジェクトとして構築する場合は、自動三角測量をオフにして、ユーザーに表示される最終結果を三角測量する必要があります。 最終結果が得られるまで多くの複雑な操作があるため、自動三角測量をオフにし、実際に視覚化する必要があるオブジェクトのみを手動で三角測量します。
したがって、ログの作成をわずかに変換します。
sgC3DObject* log1 = BuildLog(houseSizeX); log1->Triangulate(SG_DELAUNAY_TRIANGULATION);
取得するものは次のとおりです。
次のステップは、作成された各ログの溝の構築です。 溝は、別のログがログに収まる場所のログのくぼみです。 ログハウスの建設で採用されたマージンを考慮して、ログが置かれるログに溝を作ります。
これを行うには、新しいパラメーターを導入してログ構築関数を拡張します。
sgC3DObject* BuildLog(sgFloat logH, bool withGroove, sgFloat logH, bool withGroove, sgFloat groove_size, sgFloat grooveShift);
withGroove-グルーブを構築するかどうかのフラグ
groove_size-溝の半径(ログの半径に等しい)
grooveShift-グルーブが構築されるログの端からインデントします。
ログの両端から-2つの溝があります。
グルーブを作成するには、ブール減算を使用します。 速度については、減算されるオブジェクトは円柱になります。 もちろん、別のログを使用することもできますが、ログは互いにシリンダーと見なされるように交差するため(押し出された輪郭の2番目の円弧によって形成される凹部は溝の構築に関与しません)、シリンダーを差し引きます。
任意の長さのログの半径に等しい半径の2つの円柱を作成しましょう(この一時オブジェクトの長さは重要ではありません。作業の速度も、影響する結果も関係ありません)。 このステップで最も重要なことは、これらの一時オブジェクトを空間に正しく配置することです。 ログベースはXOY平面上にあり、Z軸に沿って方向付けられているため、これらの一時オブジェクトを適切な方法で配置する必要があります。 プリミティブオブジェクトとしての円柱も、XOY平面を基点として作成され、中心に原点があり、Z軸に沿って方向付けられます。したがって、最初に一時オブジェクトをY軸の周りに回転させ、次に溝の深さが考慮されるようにシフトします(この場合、ログの厚さの半分(つまり、log_sizeパラメーターのみ)とログの端からのマージンになります。 したがって、これらの一時オブジェクトの作成は次のようになります。
if (withGroove) { sgFloat cylH = 10*groove_size; sgC3DObject* cyl1 = sgCreateCylinder(groove_size, cylH, 24); cyl1->InitTempMatrix()->Rotate(zeroP, yAxe, 90.0*M_PI/180.0); SG_VECTOR transVect = {-cylH/2, -groove_size, grooveShift}; cyl1->GetTempMatrix()->Translate(transVect); cyl1->ApplyTempMatrix(); cyl1->DestroyTempMatrix(); sgC3DObject* cyl2 = sgCreateCylinder(groove_size, cylH, 24); cyl2->InitTempMatrix()->Rotate(zeroP, yAxe, 90.0*M_PI/180.0); transVect.z = logH-grooveShift; cyl2->GetTempMatrix()->Translate(transVect); cyl2->ApplyTempMatrix(); cyl2->DestroyTempMatrix(); }
これらのオブジェクトを三角測量する必要はありませんが、次のように配置したかどうかを確認するために、一時的にこれを実行してシーンに追加しましょう。
素晴らしい。 オブジェクトは必要に応じて配置されます。 これらのオブジェクトの三角形分割をコードから削除し、シーンに追加しません。
次に、ブール減算を使用して、グルーブ内のログを取得する必要があります。
最初に、最初の一時オブジェクトを減算します。 グループはブール減算の結果として作成されるため、そこでグループを分解し、グループオブジェクトを削除し、ログへのポインターを最初の(および折りたたまれたグループの唯一のオブジェクト)に再割り当てする必要があります。 この場合、その一時的なシリンダーと古いログオブジェクトが不要になったため、その一時的なシリンダーを削除することを忘れないでください。
sgCGroup* boolRes1 = sgBoolean::Sub((const sgC3DObject&)(*logObj), *cyl1); int ChCnt = boolRes1->GetChildrenList()->GetCount(); sgCObject** allChilds = (sgCObject**)malloc(ChCnt*sizeof(sgCObject*)); boolRes1->BreakGroup(allChilds); sgCObject::DeleteObject(boolRes1); sgCObject::DeleteObject(logObj); logObj = allChilds[0]; sgCObject::DeleteObject(cyl1);
2番目のvernmentalシリンダーでも同じことを行います。
boolRes1 = sgBoolean::Sub((const sgC3DObject&)(*logObj), *cyl2); ChCnt = boolRes1->GetChildrenList()->GetCount(); allChilds = (sgCObject**)malloc(ChCnt*sizeof(sgCObject*)); boolRes1->BreakGroup(allChilds); sgCObject::DeleteObject(boolRes1); sgCObject::DeleteObject(logObj); logObj = allChilds[0]; sgCObject::DeleteObject(cyl2);
これらの2つの操作の後、次の結果が得られます。
次のステップは、壁を作成することです。 実際、この手順は新しいことをもたらすものではありません。適切な量のログを作成し、適切な場所に配置するだけです。 同じログを作成するには、オブジェクトのクローン作成機能(Clone())を使用するだけで、ゼロから作成するのではない、とだけ言います。 ここで、どのログをどこに配置するかの説明をスキップし、壁を構築する手順を示します。
したがって、壁が構築されます。 これで、家の基礎の完全にパラメトリックなモデルが作成され、オブジェクトを使用したソリッドステート操作により、ログが相互に浸透するための物理的要件を完全に満たしました。 これは、家のモデルが展示用だけでなく、建設中の実際の家と完全に一致していることを意味します。
パラメーターを少し変更して、これがどのようにハウスを変更するかを見てみましょう。
ご覧のとおり、パラメトリックモデルを使用して、数個の数だけを変更すると、簡単に無限の数のモデルを取得できます。
次のステップは、モデルにウィンドウを追加することです。
ウィンドウを作成するには、押し出しとブール減算の2つの操作を使用します。 もちろん、実際の家には通常長方形の窓がありますが、通常とは異なる形の窓を作りたいので、(長方形の窓の場合のように)一時的なボックスだけでなく、丸い輪郭を絞り出します。
そのため、最初にそのようなパスを作成します。 円弧と3つのセグメントで構成されます。 輪郭の作成と押し出しの機能は、ログの作成に使用したものと完全に類似しているため、これに焦点を合わせません。 ウィンドウの輪郭を押し出し、適切な場所に配置すると、次の結果が得られます。
最初の壁のログからウィンドウを引きます。 したがって、作成機能で三角測量を無効にし、ウィンドウ減算を適用した結果を三角測量します。 壁から窓を引くには、壁の各ログから窓オブジェクトを引く必要があります。 これは、最適に行うことができます。どのログがどこにあり、ウィンドウがどこにあるかが正確にわかっており、ブール演算はウィンドウと実際に交差するログにのみ適用されるためです。 ただし、ブール減算の結果について説明します。 ゼロの場合、オブジェクトは交差しません。 この場合、ブール演算の結果は複数のオブジェクトで構成される可能性があることも考慮する必要があります(ウィンドウはログを2つに分割します)。 すべてのブール演算後、一時オブジェクトと古いログを削除することを忘れないでください。
size_t oldCnt = m_walls[0].size(); for (int i=0;i<oldCnt;i++) { sgCGroup* boolRes = sgBoolean::Sub(*m_walls[0][i], *winObj); if (boolRes) { int ChCnt = boolRes->GetChildrenList()->GetCount(); sgCObject** allChilds = (sgCObject**)malloc(ChCnt*sizeof(sgCObject*)); boolRes->BreakGroup(allChilds); sgCObject::DeleteObject(boolRes); sgGetScene()->DetachObject(m_walls[0][i]); sgDeleteObject(m_walls[0][i]); for (int j=0;j<ChCnt;j++) { ((sgC3DObject*)allChilds[j])->Triangulate(SG_VERTEX_TRIANGULATION); sgGetScene()->AttachObject(allChilds[j]); } m_walls[0][i] = (sgC3DObject*)allChilds[0]; for (int j=1;j<ChCnt;j++) m_walls[0].push_back((sgC3DObject*)allChilds[j]); free(allChilds); } else m_walls[0][i]->Triangulate(SG_VERTEX_TRIANGULATION); } sgDeleteObject(winObj);
その後、窓のある家のモデルを取得します。
最後のステップは残っています-屋根の傾斜を追加します。 これを2段階で行います。まず、屋根の表面で交差する丸太を切り取り、次に斜面のまさに表面を作成します。
まず、以前に作成されたすべてのツリーのすべての三角形分割の課題を削除します。 屋根を作成するとき、屋根の表面によるすべてのログの切り捨て、つまりブール減算が使用されます。 これらの減算のすべての結果を三角測量します。
したがって、押し出し操作を使用して、屋根の表面の下でログを切り捨てる一時オブジェクトを作成します。
そして、最初の壁のログからウィンドウを減算したのと同じ方法で、すべてのログからこの一時オブジェクトを減算します。
ランプの表面を作成するためだけに残ります。 これを行うには、押し出し操作も使用します。 その後、ログハウスの完成モデルを取得します。
また、パラメーターを変化させることにより、このモデルのバリエーションが無限にあります。