フルシチョフ手続き型発電機

私はかつて家に座って、フルシチョフについての記事を読み、建築家の天才を賞賛しました。 それから私は手放され、フルシチョフの鈍さと均一性は数学的に非常に簡単に説明できると思った。 直角、等間隔、最小限の装飾-よりシンプルなものは何ですか?



実際、フルシチョフには数十の修正がありますが、フルシチョフの本質である一定の根拠はまだ追跡できます。



一般的に、考え直すことなく、 Unity3dの下でC#でフルシチョフジェネレーターを作成しました 。 カットの下には、アルゴリズムの説明と、UVカード、サブメッシュ、およびシェーダーのトピックに関する考察があります。



▣最初に試してください。 すべてのジオメトリのトータルプログラミング。



手始めに、私はすべてをプログラムで実行しようとしました。 このアプローチにはいくつかの利点があります。エディターを開く必要がなく、配布キットのサイズが小さく、モデルの数学的な側面を簡単に操作できます。 すばやく印刷する場合、欠点はありません。大量の紙と良い想像力があります。 手動で頂点を三角形に接続することは、それほど簡単ではありません。



知らない人のために:

3Dモデルはポリゴンで構成されていると聞いたことがあるでしょう。 ポリゴンは通常、三角形として理解されます。 三角形は、頂点と頂点間の接続を使用して記述されます。 この情報に加えて、法線のリストが適用されます。これは、グラフィックエンジンにモデルを照らす方法を伝えるベクトルです。



たとえば、ウィンドウを見てみましょう。 それが完全に平らで、小さな正方形の中の小さな正方形である場合、それはすでに8つの頂点と10個の三角形を出しています。 ウィンドウが壁にわずかに沈んでいる場合、これらはさらに4つのピークと8つの三角形です。 また、窓枠や花の箱を追加するとどうなりますか? 個人的に、そのような計算から、蒸気は私の耳から注ぎ始めます。



プリミティブウィンドウを作成するためのコード
ウィンドウのあるパネルの4つの頂点が入力に送られます。 ベクトルの積から法線を見つけます。 ウィンドウのコーナーについて、さらに4つの頂点を計算します。 すべてを適切な配列に集め、頂点から三角形を作ります。 sabmeshによって三角形を配布します。 sabemesの詳細をご覧ください。



Mesh EntrancePanel(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3) { var normal = Vector3.Cross((vertex1 - vertex0), (vertex2 - vertex0)).normalized; var window0 = vertex0 + (vertex3 - vertex0) * 0.25f + (vertex1 - vertex0) * 0.25f; var window1 = vertex0 + (vertex3 - vertex0) * 0.25f + (vertex1 - vertex0) * 0.75f; var window2 = vertex0 + (vertex3 - vertex0) * 0.75f + (vertex1 - vertex0) * 0.75f; var window3 = vertex0 + (vertex3 - vertex0) * 0.75f + (vertex1 - vertex0) * 0.25f; var mesh = new Mesh { vertices = new[] {vertex0, vertex1, vertex2, vertex3, window0, window1, window2, window3, window0, window1, window2, window3}, normals = new[] { normal, normal, normal, normal, normal, normal, normal, normal, normal, normal, normal, normal }, uv = new[] {new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0), new Vector2(0.25f, 0.25f), new Vector2(0.25f, 0.75f), new Vector2(0.75f, 0.75f), new Vector2(0.75f, 0.25f), new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0)}, triangles = new[] { 0, 1, 4, 4, 1, 5, 1, 2, 5, 5, 2, 6, 2, 3, 6, 6, 3, 7, 3, 0, 7, 7, 0, 4, 8, 9, 10, 10, 11, 8}, subMeshCount = 2 }; mesh.SetTriangles(new[] { 0, 1, 4, 4, 1, 5, 1, 2, 5, 5, 2, 6, 2, 3, 6, 6, 3, 7, 3, 0, 7, 7, 0, 4}, 0); mesh.SetTriangles(new[] { 8, 9, 10, 10, 11, 8 }, 1); return mesh; }
      
      





このオプションは興味深いものですが、壁、窓、玄関、ガラス張りのバルコニーを作成するのに多くの時間を費やした後、私は突然悲しみを感じ、反対側から入ることに決めました。



▣2回目の試行。 すべてがエディターを介して行われ、sabmesと多くの資料があります



一般的に言って、エンジンは頂点、三角形、法線の原点にはまったく関係ありません。 2つのモデルのうち1つはコードで「生まれた」モデルで、もう1つはBlenderで生まれたモデルです。 さらに、それらはサブメッシュ(サブモデル? サブネット?サブメッシュ? )で1つのモデルにマージでき、そのすべての変換はサブメッシュに影響します。 サブメッシュは頂点インデックスを持つ単なる追加リストであり、それ以上のものはありません。



サブメッシュのそれぞれに、独自の素材を掛けることができます。これにより、ガラスが輝き、壁が粗くなります。 このアイデアは非常に許容範囲が広く、各マテリアルのみが追加のシェーダー呼び出しであり、sabmeshインデックスの追跡は非常に退屈な作業であることが判明しました。 私はサブメッシュで少し遊んで、このベンチャーを放棄しました。



▣3回目の試行。 Samuraiシェーダーとテクスチャマップ



ビデオカードは、ポリゴンとテクスチャの大部分が与えられると大好きです。 たとえば、100万個の三角形を持つモデルよりも、100万個の三角形を持つ1つのモデルを飲み込む方がはるかに簡単です。 同じ原則がテクスチャに適用されます。 多数の写真を1つの大きな写真に接着すると、テクスチャアトラスが得られます。 多くの場合、複数のテクスチャアトラスを作成します。1つは色用、もう1つは照明用、3つ目は反射用です。



シェーダーを書くことにしました。Unityでの利点は簡単で、モデルのテクスチャリングに単一のアトラスを使用しました。 シェーダーはこの地図と光沢のある表面の追加の地図を受け入れ、そこにすべての窓を持ち込みました。



フルシチョフシェーダーコード
以下のコードは、Unityにバンドルされている通常のスペキュラーシェーダーで、ウィンドウは別のカードに配置され、色を変更するオプションが追加されています。



 Shader "Custom/Khrushchyovka" { Properties { _Color ("Main Color", Color) = (1,1,1,1) _MainTex ("Base (RGBA)", 2D) = "white" {} _GlassColor ("Glass Color", Color) = (0.5, 0.5, 0.5, 1) _Shininess ("Shininess", Range (0.01, 1)) = 0.078125 _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1) _SpecTex ("Specular (RGB)", 2D) = "gray" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf BlinnPhong sampler2D _MainTex, _SpecTex; fixed4 _Color, _GlassColor; half _Shininess; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { half4 main = tex2D (_MainTex, IN.uv_MainTex); half4 spec = tex2D(_SpecTex, IN.uv_MainTex); o.Albedo = main.rgb * _Color.rgb + spec.rgb * _GlassColor.rgb; o.Gloss = spec.rgb; o.Specular = _Shininess; } ENDCG } FallBack "Diffuse" }
      
      







wall壁を作ります



今、最も興味深いのは建物の組み立てです。 開始する最も簡単な方法は、1つの壁を使用することです。 複数のパネルで壁を作るには、それらを並べて配置する必要があります。 前の記事で、モデルを結合するために使用されるCombineMeshesについて言及しました 。 モデルと一緒に、彼はモデルを移動、回転、およびサイズ変更できる変換行列を入力できます。 論理は単純です。サイクルでは、必要な数のモデルを収集し、それぞれが特定の距離だけシフトすると、連続した壁が得られます。 特定のサイズの壁が必要な場合は、単に1つのパネルの長さで割って、必要なパネルの数を見つけます。



短いグーグルの後、フルシチョフのパネルは異なるサイズであることが判明しました。 面倒なことがないように、私はすべてのプレートを長さ2.5 mと3 mの2つのサイズで作成しました。



2つの異なるプレートでは、指定された長さの間隔を埋めることははるかに困難です。 このタスクには独自の名前- サブセット合計問題があります。 この問題を解決するには多くのオプションがありますが、単純な再帰アルゴリズムを選択しました。



最初に、使用可能なパネルの長さと入力する必要のあるセグメントを持つ配列があります。 アルゴリズムの結果として、別の配列が取得されます。この数字は、最初の配列と同じインデックスの長さを持つ必要なパネルの数を意味します。 つまり、最初の配列は{3、2.5f}のようになります。 そして、11メートルのセグメントの2番目は{2、2}のようになります。 また、パネルの配列は降順でソートされていることに注意してください。



整数セグメントは、最大パネルの長さで整数で除算され、結果が2番目の配列に追加され、残りが変数に書き込まれます。 余りがゼロの場合、タスクは完了し、結果は2番目の配列にあります。 余りがゼロより大きい場合、これは最大のパネルがそれに重なり、1つの大きなパネルを捨て、その長さを余りに追加し、リストの残りと次のパネルで最初の操作を繰り返すことを意味します。 2番目の配列の最大パネルが最初の配列で最小になるか、残りがゼロになるまで、大きなパネルを捨てます。



アルゴリズムコード
上記に加えて、残りのサイズのチェックが追加され、最小のパネルよりも小さい場合は、1つの小さなパネルが追加されます。



 int[] ExteriorWallSizesDraft(float remainder, int[] draft = null, int startIndex = 0) { if (draft == null) { draft = new int[panels.Length]; for (int i = 0; i < draft.Length; i++) { draft[i] = 0; } } if (remainder < panels[panels.Length - 1]) { draft[draft.Length - 1] = 1; return draft; } for (var i = startIndex; i < panels.Length; i++) { draft[i] += (int)(remainder / panels[i]); remainder %= panels[i]; } if (remainder > 0) { for (var i = 0; i < draft.Length; i++) { if (draft[i] != 0) { if (i == draft.Length - 1) { return draft; } draft[i]--; remainder += panels[i]; startIndex = i+1; break; } } draft = ExteriorWallSizesDraft(remainder, draft, startIndex); } return draft; }
      
      





どんなに頑張っても、長さの異なるパネルの分布の原理を理解できなかったので、量の配列を長さの配列に変換した後、 Fisher – Yatesアルゴリズムに従って混同します。 かなりまともな結果が得られます。







▣ファサードの設計



フルシチョフの異なる階では、異なるパネルが使用されています。 1階には窓と空の壁があり、2階にはバルコニーがあります。 また、建物には地下室と屋根裏部屋があります。 ファサードマップを作成するには、壁の長さとフロアの数を含む配列を取得し、パネルタイプが各フロアの配列に格納される2次元配列を作成します。 次に、このテンプレートを使用して、適切なパネルを作成できます。



フロントファサードのアルゴリズムは次のとおりです。 1階は地下パネルで満たされています。 1階は完全に窓でいっぱいです。 2階は1階を完全にコピーしますが、一部の窓がバルコニーに置き換わっています。 3階以上では、2階のパターンを屋根にコピーするだけです。 屋根裏部屋がある場合は、屋根裏部屋のパネルが上に追加されます。



バルコニーを完全にランダムな方法で押すと、hashいハッシュが得られるため、ファサードの両側にすぐにバルコニーを中心に対して対称に別のサイクルで配置することをお勧めします。 同様に、正面玄関の窓では、1階に空の壁と窓があります。 すべての見栄えを良くするには、ウィンドウを対称にする必要があります。 端の2階では、正面のファサードのような窓の一部がバルコニーに置き換えられていますが、中央のパネル、極端なパネルのみ、バルコニーは非常にまれです。



最後の仕上げはドアです。 パネルの数をドアの数に1を加えた数で割ると、ドアを配置する必要があるセグメントを取得して、ドアがほぼ均等に分散されるようにすることができます。 実際、実際のフルシチョフでは、建物の入り口は写真と計画から判断して異なる場所にありますが、これまでのところ、これ以上良いものを思い付くことができませんでした。 1階にはメインの入り口があり、次の階にはモデルを作成する段階で少し移動する必要がある窓があります。 窓の代わりに最上階に、空の低いパネルが配置されます。 一部の建物では、玄関がポーチで行われ、その後スキームが変更されますが、私はすでにモデルを作成するのが面倒だったため、このオプションは考慮されません。



ファサードジェネレーターコード
パネルのタイプを示すために列挙を使用しますが、次のようになります。



 public enum PanelType { Wall, Window, Balcony, Entrance, EntranceWall, EntranceWallLast, Socle, Attic, };
      
      







以下のブランチコードは非常に単純で、変数名はそれ自体を物語っています。



 List<List<PanelType>> FacadePattern(int panelCount, int floorCount, bool haveAttic=false, bool longFacade=false, int entrancesCount=0) { var panelPattern = new List<List<PanelType>>(); var entranceIndex = panelCount / (entrances + 1); var entranceCount = 1; for (var i = 0; i < floorCount+1; i++) { panelPattern.Add(new List<PanelType>()); for (var j = 0; j < panelCount; j++) { if (i == 0) { if (entrancesCount > 0 && j == entranceIndex && entranceCount <= entrances) { panelPattern[0].Add(PanelType.Entrance); entranceCount++; entranceIndex = panelCount*entranceCount/(entrances + 1); } else { panelPattern[0].Add(PanelType.Socle); } } else if (i == 1) { if (panelPattern[0][j] == PanelType.Entrance) { panelPattern[1].Add(PanelType.EntranceWall); } else if (longFacade) { panelPattern[1].Add(PanelType.Window); } else { panelPattern[1].Add(PanelType.Wall); } } else { panelPattern[i].Add(panelPattern[i - 1][j]); } if (i == floorCount) { if (panelPattern[i - 1][j] == PanelType.Entrance || panelPattern[i - 1][j] == PanelType.EntranceWall) { panelPattern[i][j] = PanelType.EntranceWallLast; } } } if (i == 1 && !longFacade) { for (int j = 0; j <= panelPattern[1].Count / 2; j++) { if (j != 0 && j != panelCount - 1 && Random.value > 0.5f) { panelPattern[1][j] = PanelType.Window; panelPattern[1][panelPattern[1].Count - 1 - j] = PanelType.Window; } } } if (i == 2) { for (int j = 0; j <= panelPattern[2].Count/2; j++) { if (panelPattern[2][j] == PanelType.Window && panelPattern[2][panelPattern[2].Count - 1 - j] == PanelType.Window && Random.value > 0.5f) { panelPattern[2][j] = PanelType.Balcony; panelPattern[2][panelPattern[2].Count - 1 - j] = PanelType.Balcony; } } } } if (haveAttic) { panelPattern.Add(new List<PanelType>()); for (var j = 0; j < panelCount; j++) { panelPattern[panelPattern.Count-1].Add(PanelType.Attic); } } return panelPattern; }
      
      











▣フルシチョフをカバーします



4つのファサードはまだ家ではありません。屋根が必要です。 結局のところ、フルシチョフの屋根は異なっています。 フラット、切妻、4切妻があります。 残念ながら、インターネット上のフルシチョフの屋根の写真はフロアプランよりも少ないため、屋根をむき出しにしました。 一般的に言えば、建物の寸法が一定であれば設置しやすい換気口と屋根の出口があるはずですが、私の場合、配置のアルゴリズムを考え出す必要がありましたが、どこが明確でないのかは明らかではありません。 とりあえず裸にしておきましょう。



屋根については、幅と長さが1メートルのモデルを作成し、必要に応じて伸ばしました。



屋根の選択
 switch (roofType) { case RoofType.Flat: combine.Add(RandomItem(roofFlat)); matrices.Add(Matrix4x4.TRS(roofHeight, Quaternion.identity, new Vector3(length, 1, width))); break; case RoofType.FlatOverhang: combine.Add(RandomItem(roofFlat)); matrices.Add(Matrix4x4.TRS(roofHeight, Quaternion.identity, new Vector3(length + 1, 1, width + 1))); break; case RoofType.Gabled: combine.Add(RandomItem(roofGabled)); matrices.Add(Matrix4x4.TRS(roofHeight, Quaternion.identity, new Vector3(length, 1, width + 1))); break; case RoofType.Hipped: combine.Add(RandomItem(roofHipped)); matrices.Add(Matrix4x4.TRS(roofHeight, Quaternion.identity, new Vector3(length + 1, 1, width + 1))); break; }
      
      





以上です。 フルシチョフの準備ができました。







▣結論



説明したアプローチには1つのボトルネックがあります。 CombineMeshesは遅いだけでなく、結合する頂点と三角形の数にも制限があります。 急にフルシチョフの超高層ビルを作ることにした場合、ピークの数はすぐに65,000を超え、下の写真のように廃getができます。 これを防ぐには、モデルを接着するための独自の関数を作成する必要があります。







さまざまなプラットフォームのプロジェクトソースとバイナリは、以下のリンクからダウンロードできます。



Unity Web Player | Windows | Linux | Mac | GitHubソース



マウスの左ボタン-新しい建物、Esc-終了。



PS誰かがモデルの完成を手伝ってくれたらうれしいです、私にとってはモデラーは悪いです。



All Articles