Anatomy Three.js — インタラクティブな3D技術図面
Three.js + GLSLでインタラクティブな等角投影3D技術イラストを構築します。すべての図は、技術的なアイデアを、机の上に置ける物理的なベンチトップ計器やカットアウェイ試験装置として説明します。面取りされた重厚なベースプレート、旋盤加工された脚、ドメインに忠実な筐体とリンケージ、ヒーローとなる内部の回転体や可動物、直接ドラッグ可能な3Dコントロールとその機械専用に作られた計器、そして機械内部の物理媒体(火、プラズマ、流体、ダウンウォッシュ、ビーム)に厳密に制限されたカスタムGLSLシェーダーを備えています。
1500行のモノリシックHTMLファイルを書いたり、頭の中で3D座標を推測したりする代わりに、まずユーザーと装置とインタラクションについて合意し(ステージ0)、次にそれぞれの図をfigures/<slug>/内で6つの独立したステージで構築します。これらは不変のkit/エンジンを基盤とし、2層の3D空間 + 正投影ビジュアル検証ハーネスによって各ステップで検証されます。
ディレクトリ構成
anatomy-threejs/
├── SKILL.md # ステージ0の概要 + 6ステージワークフロー & エージェントエントリポイント
├── kit/ # 不変エンジン — ここからインポート; 図ごとに編集しない
│ ├── shaders.mjs # 5テーマ、4トーン + トップキャップセルシェーダー、インバーテッドハル、折り目線
│ ├── primitives.mjs # 軸認識スラブ、円筒、ギア、ネジ、ローレット、目盛り
│ ├── registry.mjs # Recorder3D: .group(), .part(), .anchor(), .tube(), .interactable(), .tag()
│ ├── audit3d.mjs # ヘッドレスNode 3D空間監査(衝突、浮き、ボア、チューブ)
│ ├── runtime.mjs # 正投影リグ、レイキャスト3Dドラッグ、カスタムコントロール、スプリング、__ANATOMY_DEBUG__
│ ├── bundle.mjs # 図モジュールを単一の自己完結型HTMLファイルにコンパイル
│ └── shell.html # レスポンシブカードクローム、5テーマCSS変数、機能認識コントロール
├── scripts/
│ ├── inspect.mjs # ヘッドレスChrome (CDP) 2x2正投影ブループリント & ジョイントオーバーレイ
│ ├── drive.mjs # インタラクティブコンタクトシート + ヒロスチル + モバイル390px検証
│ └── chrome-port.mjs # ゼロ依存WebSocket + PNGスティッチャー for ヘッドレスChrome
├── references/
│ ├── craft-3d.md # 3D座標系、トーン階層、専用コントロール、アンチパターン
│ ├── kit-3d.md # Recorder3D、プリミティブ、シェーダー、ランタイムのAPIリファレンス
│ └── verify-3d.md # [COLLISION]、[FLOATING]、[TUBE KINK]、および正投影ビューの解釈と修正方法
└── figures/
└── <slug>/ # 図の作業スペース(例: figures/wankel-engine/)
├── model.mjs # ステージ1: 純粋数学、寸法、SPEC.features、SPEC.controls、アンカー
├── base.mjs # ステージ2: ベースプレート、脚、ハロー、ルーラー、真鍮銘板
├── structure.mjs # ステージ3: 台座、フレーム/筐体、ポート、ファスナー
├── subject.mjs # ステージ4: 可動コアアセンブリ (ローター、ブレード、シール、ギア)
├── mechanism.mjs # ステージ5: 専用コントロール、計器、3Dインタラクティブ、チューブ、updateKinematics()
├── shader.mjs # ステージ6: アクティブな物理媒体に限定されたカスタムGLSLシェーダー
├── build.mjs # CLIエントリ: --test-model, --audit, フルHTMLバンドル
└── dist/
└── <slug>.html # コンパイル済み自己完結型インタラクティブ図
ルール1 —
kit/を決して書き換えない: Three.jsカメラ、セルシェーダー、レイキャストドラッグハンドラ、スプリングソルバー、DOMクローム、デバッグハーネスはkit/にあります。あなたの図ファイルは、ドメインの数学、3Dパーツ、キネマティクス、インタラクティブ、およびカスタムメディアシェーダーのみを定義します。ルール2 — ジオメトリを書く前にリファレンスを読む:
- ステージ0/1の前に
references/craft-3d.mdを読んで、物理的なリグ、専用コントロール、機能フラグ、トーン階層、3D座標フレームを設計します。- ステージ2の前に
references/kit-3d.mdを読んで、Recorder3Dとkit/primitives.mjsの正確なシグネチャを確認します。--audit、inspect.mjs、またはdrive.mjsが問題を報告したときは、references/verify-3d.mdを読んでください。
ステージ0 — 装置 & インタラクション概要(構築前に調整する)
ファイルを作成したりステージ1のコードを書いたりする前に、簡潔な装置 & インタラクション概要をユーザーに提案し、機械が何をするか、読者がどのように操作するかについて合意します(ユーザーが明示的に質問なしで即座に構築するよう指示した場合のみ待機をスキップします):
- ベンチトップ装置 & ヒーロー主題: 物理的なメカニズムと、
litアクセントを担う単一のアセンブリに名前を付けます。 - 直接3D操作 (
reg.interactable): 読者がビューポート内で直接グラブしてドラッグできる3Dアセンブリまたはコントロールを特定します(例: スワッシュプレートをドラッグして傾け/持ち上げ、ローターブレードのフェザリングを観察する、バーニアキャリッジをスライドさせる、リンケージレバーを引く)。回転/シミュレーションが一時停止されている間も含みます。 - ドメインに忠実なコントロール & テレメトリー (
SPEC.controls): この機械に属するベンチハードウェアとコントロールバースライダーを選択します(例: 2軸サイクリックスティック、コレクティブピッチクワッドラント、分度器アーク、マイクロメータードラム、バーニアスケール、マノメーターチューブ)。主題が実際にその正確な計器を使用していない限り、別の図のロータリーノブ、ブルドン圧力計、または油圧ボックスを決してコピーしないでください。 - ビューモード (
SPEC.features): どのオプションモードがその場所に値するかを明示的に決定します:explode: 積み重なった軸方向レイヤーを持つ密閉された同心円状の機械(エンジン、ギアボックス、ポンプなど)のみ有効 (true) にします。リンケージジオメトリを壊して散らかりを増やすオープンな機械的リンケージ、スワッシュプレート、光学ベンチ、または表面リグの場合はexplode: falseに設定します。cutaway: 外側の筐体が内部の可動部を隠している場合に有効 (true) にします。オープンフレームのメカニズムの場合はfalseに設定します。shader: 閉じ込められた物理媒体(燃焼ガス、ローターダウンウォッシュ、ビーム、流体の流れ)がメカニズムを明確にする場合に有効 (true) にします。
ステージ0 ゲート: ユーザーが概要を確認または修正する(または明示的に即座の非インタラクティブビルドを指示する)。
6ステージワークフロー
これらのステージを順番に実行します。各ステージは検証ゲートで終了します — コマンドを実行し、エラーを修正し、レンダリングされたPNGをファイル読み取りツールで視覚的に検査してから次のステージに進みます。
ステージ1 — モデル & キネマティクス (model.mjs + build.mjs)
Three.jsに触れる前に、合意した概要を純粋な物理数学モジュールに変換します:
figures/<slug>/model.mjsに物理的メタファーをエンコードします:SPEC.features:{ explode: boolean, cutaway: boolean, shader: boolean, crank: boolean }ステージ0に一致。SPEC.interactionHint: 左下隅に表示される短いヒントで、ユーザーがどの3Dパーツを直接ドラッグできるかを伝えます(例:"Drag swashplate, stick, or collective lever")。SPEC.controls: ドメイン固有のスライダーコントロールの配列({ id, label, min, max, step, initial, title })。runtime.mjsはこれらをコントロールバーにレンダリングし、スプリングで滑らかにし、updateKinematicsとformatReadoutに渡します。SPEC.auditPoses&SPEC.drivePoses: ドメインを代表するポーズ(極端なコントロール角度、操作の一時停止、カットアウェイ/テーマチェック)で、--auditとscripts/drive.mjsによって使用されます。features.explode === falseの場合は爆発ポーズを省略します。SPEC: すべての重要な寸法(BASE_W、BASE_D、BASE_H、BASE_Y、topSurfaceY、isoElevDeg: 30、isoAzimDeg: -36、initialZoom、initialPanY、minVertSpan、minHorizSpan)。ANCHORS: ベースプレート上の名前付き[x, y, z]マウンティング座標(plaqueCenter、rulerCenter、および図の専用コントロール/計器アンカー)。標準的な等角投影(30° elev, -36° azim)では、+Zエッジが前面であり、-X, +Z/+X, +Zが障害物のない前面コーナーであることを忘れないでください。thetaとSPEC.controlsからのライブparamsによって駆動される純粋な状態とキネマティック関数(Three.jsインポートなし)。formatReadout({ theta, effectiveRpm, params })は、テレメトリバーのための{ readout, phase }文字列を返します。
figures/<slug>/build.mjsをスキャフォールディングします(figures/wankel-engine/build.mjsまたはfigures/helicopter-swashplate/build.mjsを参照)。タイトル、サブタイトル、初期読み出し、凡例を調整します。
ステージ1 ゲート:
node figures/<slug>/build.mjs --test-model
合格基準: 完全な $0 \dots 6\pi$ サイクルにわたってすべてのアンカーが有限であり、読み出しと状態が検証された状態で終了コード 0 を返す。
ステージ2 — ベース (base.mjs)
計器全体を支える重い基準ベースプレートを構築します:
base.groupを作成します(stage: "base")。BASE_Y - 0.24にソフトグラウンドシャドウ(makeHaloMesh)を追加します。buildSlabGeometry(BASE_W, BASE_D, BASE_H, { radius: 0.26, bevel: true, bevelSize: 0.025, bevelThickness: 0.025 })を使用してbase.plateを追加し、ramp: "lo"、mountOn: "ground"を設定します。topSurfaceY = BASE_Y + BASE_H + 0.025は上部面取りの厚さを考慮しており、topSurfaceYにマウントされたパーツがベースプレートの上面に沈み込まずにフラットに設置されることに注意してください。reg.anchor(id, x, y, z, baseGroup)を介してすべてのベースプレートマウンティングアンカーを登録し、ステージ3と5がbase.plateに直接スナップできるようにします。- インセットされた周囲溝(
makePlanOutline)、4つの旋盤加工されたコーナーフット(base.foot.collar.i+base.foot.pad.i)とトップカウンターボアネジ(makeScrewDetail)、目盛り付きスケール(base.ruler+makeRulerTicks)、および可視の前面(+Z)にリベット留めされたbrass仕様銘板(base.plaque)と刻印タグ(reg.tag("tag-plate", ...))を追加します。
ステージ2 ゲート:
node figures/<slug>/build.mjs --audit
node scripts/inspect.mjs figures/<slug>/dist/<slug>.html --isolate base --ortho --out figures/<slug>/dist/inspect-base.png
inspect-base.png を読んで、フットがベースプレートの下にきれいに設置され、真鍮銘板とルーラーが前面で妨害されていないことを確認します。
ステージ3 — 構造 (structure.mjs)
固定された耐荷重サポート、メインフレームまたはカットアウェイ筐体、ポート/マニホールド、およびファスナーを構築します:
- サポート台座またはギアボックスを
base.plateに、フランジベース、ボルト詳細、テーパーコラム、ベアリングサドルとともにマウントします。 - メイン筐体またはフレームを構築します(
ramp: "mid"、outline: "hi")。features.cutawayが有効な場合、内部メカニズムが標準的な等角投影カメラから完全に見えるままになるように、前面(+Z)を開いたままにするか、カットアウェイカバー(cutawayHide: true)を取り付けます。 - ドメインに忠実なポート、アクチュエータ筐体、または静止ブラケットを追加し、きれいに外側を向くようにします。各ローカルサブグループ内に、ローカルの外向きポート法線(
reg.anchor("plug.leading.tip", 0, 0.68, 0, spGroup, { dir: [0, 1, 0] }))とともに3Dポート/ターミナルアンカーを登録し、ステージ5のチューブが正確なワールド位置にスナップし、同軸で出入りできるようにします。 SPEC.features.explodeがtrueで、装置が軸方向に分離することを意図した密閉された同心円状のレイヤーを持つ場合のみ、取り外し可能なサブグループにexplode: [ex, ey, ez]ベクトルを設定します。
ステージ3 ゲート:
node figures/<slug>/build.mjs --audit
node scripts/inspect.mjs figures/<slug>/dist/<slug>.html --isolate base,structure --ortho --out figures/<slug>/dist/inspect-structure.png
inspect-structure.png を読む(TOP、FRONT、RIGHT、CANONICAL ISO): ポートが外側を向いていること、台座がベースプレートと筐体の両方に接触しており、クリッピングがないこと、そして何も浮いていないことを確認します。
ステージ4 — 主題 (subject.mjs)
視覚的な焦点となるヒーロー内部アセンブリを構築します:
- 主要な可動体(例: ローター本体、ハブヨーク)に
ramp: "hi"を割り当て、アクティブな作動要素(rotor.apexSeal.i、blade.airfoil.*)にramp: "lit"を、精密ギアとピボットにramp: "brass"を割り当てます。 - ネストされたキネマティックグループ(
orbit -> spin、またはazimuth -> flap -> pitch)を作成し、階層的な動きがオイラー角のジンバルロックなしでクリーンに合成されるようにします。 - ボア内部で動くパーツに
insideBoreOfとboreClearanceFnを渡し、audit3d.mjsが監査されたすべてのポーズで壁の貫通がゼロであることを数学的に検証できるようにします。 - 機械加工されたポケット、面溝、スパーシーム、またはボルトパターンを追加して、動きが間違いなく認識できるようにします。
ステージ4 ゲート:
node figures/<slug>/build.mjs --audit
node scripts/inspect.mjs figures/<slug>/dist/<slug>.html --isolate subject --ortho --out figures/<slug>/dist/inspect-subject.png
inspect-subject.png を読んで、主題のシルエット、面取り、ジョイントがあらゆる角度から明確に読み取れることを確認します。
ステージ5 — メカニズム、専用コントロール & 直接3Dインタラクション (mechanism.mjs)
主題をドメインに忠実なオペレーターコントロール、テレメトリー計器、直接3Dドラッグインタラクション、および配管に接続します:
- ステージ0/1の
ANCHORSにドメインに忠実なコントロールを構築します(例: 2軸ジンバルサイクリックスティック、ノッチ付きコレクティブピッチクワッドラントレバー、バーニアリードスクリュー、ローレットスロットルドラム)。機械が独自のコントロールハードウェアを必要とする場合、別の図のローレットノブやマニホールドブロックを決してコピーしないでください。 - ドメインに忠実なテレメトリーを構築します(例: 湾曲したブレードピッチ分度器アーク、リニアトラベルスケール、マノメーターカラム、ダイヤルゲージ)。
CANONICAL ISOでメインシルエットの+Z前方または明確な位置に配置します。 - 主要メカニズムおよび/または物理レバーに
reg.interactable(targetId, { cursor, hint, onDrag })を使用して直接3D操作を登録し、setParam(...)を介してユーザーがビューポート内の3Dパーツを直接ドラッグできるようにします。回転が一時停止されている場合(RPM = 0またはPAUSED)でも機能します。 reg.tube(id, fromAnchorId, waypoints, toAnchorId, opts)を使用してすべてのパイプとケーブルを配線します:reg.getAnchorWorldPos(anchorId)とreg.getAnchorWorldDir(anchorId)をクエリして、隣接するパーツからクリアにスイープし、各ポートの外向き法線に沿って同軸で入る、スムーズな3Dアーチウェイポイント(length / chord >= 1.12)を構築します。
- ドライブトレインと連結リンケージを構築します。内部メカニズムを隠す前面側の筐体カバーまたはシュラウドには
cutawayHide: trueを渡します。 - コントロールバーにテレメトリー要素を追加します(例: RPM、負荷、角度)。
ステージ5 ゲート:
node figures/<slug>/build.mjs
node scripts/drive.mjs figures/<slug>/dist/<slug>.html --mobile --out figures/<slug>/dist/
drive.mjs が生成したコンタクトシートを確認します。すべてのビューとポーズが読みやすく、コントロールが機能し、チューブがポートにきれいに接続されていることを確認します。
ステージ6 — 物理メディアシェーダー (shader.mjs)
機械内の閉じ込められた物理媒体のためのカスタムGLSLシェーダーを追加します。このステージは、SPEC.features.shader が true の場合にのみ実行されます:
- 機械内部の閉じ込められた媒体(例: 燃焼ガス、ローターダウンウォッシュ、集束ビーム、または内部流体の流れ)を表す、ドメイン固有のフラグメントシェーダーを作成します。
- シェーダーは、
recorder.geometry()によって提供される均一なボリューム空間内でのみ動作する必要があり、周囲の機械部品には影響を与えません。 runtime.mjsは、標準的な等角投影ビューの場合、シェーダーされたボリュームを不透明/半透明の適切なブレンドでレンダリングします。- シェーダーは、ドラッグ可能なコントロールとキネマティクスに応答する必要があります(例: エンジン回転数に応じて脈動する火炎、コレクティブピッチに応じて変化するダウンウォッシュ)。
ステージ6 ゲート:
node figures/<slug>/build.mjs
node scripts/inspect.mjs figures/<slug>/dist/<slug>.html --isolate all --ortho --out figures/<slug>/dist/inspect-final.png
最終的な図を検査します。物理的媒体が明確に見え、閉じ込められており、動的に応答することを確認します。
図のビルド & バンドル
完全な図を構築するには:
node figures/<slug>/build.mjs
これにより、build.mjs で定義されたすべてのステージがコンパイルされ、figures/<slug>/dist/<slug>.html に自己完結型のインタラクティブHTMLファイルが生成されます。
--audit フラグを使用して、任意のビルドステップで空間検証を実行できます:
node figures/<slug>/build.mjs --audit
ヘッドレスブループリント検査の場合:
node scripts/inspect.mjs figures/<slug>/dist/<slug>.html --isolate <stage1>,<stage2> --ortho --out figures/<slug>/dist/inspect-<name>.png
テーマ
kit/shaders.mjs は5つのビルトインテーマを提供します。これらはランタイムのテーマセレクターを使用して切り替えられます:
daylight: 明るい背景、暗いトーンtwilight: 暗い背景、明るいアクセントmidnight: 黒い背景、ネオンアクセントpaper: 白い背景、インクトーン(印刷用)xray: 半透明、内部の強調表示
各テーマは、4トーンのセルシェーディングパレット(lo、mid、hi、lit)とインバーテッドハルシルエットスタイルを定義します。
アンチパターン
- 浮いている部品: すべての部品は、ベースプレート、台座、またはリンケージを介して機械的に接地されていなければなりません。
--auditを実行して浮きをチェックします。 - ポート/チューブの位置ずれ: チューブは、ポートの外向き法線と一致する同軸方向でポートに出入りしなければなりません。常に
getAnchorWorldDirを使用して方向をクエリします。 reg.anchorの誤った使用:** アンカーは、グループ内のポイント位置を登録する必要があり、サブグループ自体をアンカーにしてはいけません。適切なアンカー位置については、references/kit-3d.mdを参照してください。- 外部シェーダー効果: メディアシェーダーは、機械内部の閉じ込められた物理媒体に限定されるべきであり、周囲の部品に適用されたり、グローバルなポストプロセス効果として使用されたりしてはいけません。
- 爆発図の過剰使用: 爆発図は、積み重なった軸方向レイヤーを持つ密閉された同心円状の機械にのみ使用します。リンケージやオープンフレームのメカニズムでは、無効のままにします。
- コンテキストのない座標のハードコーディング: 直接数値の代わりに
topSurfaceYとANCHORSを使用して、ジオメトリがベースプレートの寸法に適応できるようにします。
例: 図のクイックスタート
新しい図を開始するには:
figures/wankel-engine/をfigures/<your-slug>/にコピーします。model.mjsを編集して、SPEC、ANCHORS、物理的キネマティクスを定義します。base.mjs、structure.mjs、subject.mjs、mechanism.mjs、shader.mjsを順番に埋めます。build.mjsのタイトルと凡例を調整します。- 各ステージの後に
--auditとinspect.mjsを実行します。 - 最終ビルドには
node figures/<your-slug>/build.mjsを実行します。