前回、The Elder Scrolls: Daggerfallというゲームがどのようなものか、簡単に紹介させていただきましたが、このDOSベースのゲームについて調べるうちに、なにやら"Daggerfall Unity"という言葉が混在してくることに気が付きました。

 

なんじゃ、これ?

 

ということで、今度は積極的に"Daggerfall Unity"を調べてみることに。

 

すると、

 

どうもゲームヲタクの有志がこのゲームプログラムを、オリジナルのXnGine Engineではなく、より現代的なUnityをつかって遊べるようにしたもので、ゲームのリソースや構造は全く同じで表示(即ちUI)関係が現在のPC環境に合わせて改善されているもののようです。

 

これで遊ぶには、「Steamから DOS Daggerfall の無料コピー(解説:即ち,Daggerfall Unityで遊ぶには、オリジナルのDOS版のDaggerfallが必要ということです-Microsoft Storeからも入手できます)を、リリースページ(解説:↑のことですが、私はココからダウンロードしました)からDaggerfall Unityの無料コピー を入手できます。その後、最新バージョンのDaggerfall Unityを専用のフォルダーに解凍し、DOS バージョンを指定するだけです。あとはすべて Daggerfall Unity が処理します。」とのことです。詳しくは↓等を参考にしてください。

 

【"Daggerfall Unity"の開発者Interkarmaのインタビュー】

【"Daggerfall Unity"の開発者Gavin Claytonのインタビュー】

 

(同じ記事の別メディアーGamesPark-の記事)

 

 

私もこのブログネタの為にDaggerfall Unityを導入しましたが、手順は以下の通りです。

 

(1)オリジナルのDOS版DaggerfallはMicrosoft Storeで入手。(前回記事参照)

(2)フリーのDaggerfall Unity Ver 1.0をココから入手。

(3)Daggerfall Unityを起動するとセットアッププロセスに入るので、指示通りに進んでください。重要な点は「"ARENA2"フォールダーのあるフォールダーへpathを設定する」ということだけで、これは各自でDOS版Daggerfallの入手、展開先に応じて指定する必要があります。(

:私の場合は前々回書いた通りなので、"C:\XboxGames\The Elder Scrolls- Daggerfall\Content\DF\DAGGER\"を指定しています。

 

そして現在私のPCは、

 

DOS版Daggerfall

 

と、

 

Unity版Daggerfall

 

が動いていますが、これが

 

どーすんだよっ!?

 

という問題となっています。詳しくは次回。

 

前回、懐かしいVGA画面

 

 

に魅せられて取り敢えずダウンロード、インストールしてしまった"Daggerfall"ですが、実際どういうゲームなのか、を調べてみました。

 

概要は前回お伝えした通りです。

ゲーム名                            :The Elder Scrolls: Daggerfall
リリース日                         :1996/12/31
開発元                               :Bethesda Softworks
公式サイト                         :http://www.elderscrolls.com/daggerfall/
使用ゲームエンジン             :XnGine Engine(Wikiの「マイクロソフト系の旧ゲームエンジン」参照)
ジャンル                            :RPG
マルチプレイ/シングルプレイ:シングルプレイ
OS                                   :Windows/DOS/Unity(非公式/Windows/Mac/Linux)

 

そしてこのゲームについて先ずWikiで調べてみました。このゲームは単一のものではなく、どうもスポーツ系ゲームを得意としたベセスダ・ゲーム・スタジオが、「タムリエル」という架空の大陸の過去・現在・未来のすべてが記された巻物The Elder Scroll-和名を「星霜の書(せいそうのしょ)」というらしい)を巡る連作としてRPGに進出した第2作のようで、第3~5作迄Game of the Yearを取得して、現在は有償のオンラインゲーム(The Elder Scrolls Online)をリリースしているらしい。

 

ゲームは、スキルツリー方式で成長してゆく主人公が「タムリエルの皇帝がダガーフォールの王にあてた手紙を探す」とともに「嘗てタムリエルを治めていたLysandusの魂を解放する」ことをゴールとするマルチエンディングRPGで、探索できる都市・町・村・ダンジョンが15,000か所もあるそうです。(

:私は最初のダンジオンにまだいるので、後14,999もある計算になり、心が折れます。更にマルチエンディングを総て極めたいとか、そういった連作ゲームを総てやりたいとかいう話になると、猫の様に8回生まれ変わっても(英語圏では9回、"Cat has nine lives.")終わらないでしょう。

 

いずれにしても、私が米国でIBM互換PCでDungeons & Doragonsで遊んだ頃結構流行っていたDOOMというゲームのソフトハウスが出したDOS 6用のゲーム、というだけで(そしてこの一作だけでお腹いっぱいでしょうが)、じっくりと楽しめそうです。

 

 

所が、とある米国人「ヲタク」Gamerにより、話は更に発展してゆきます。

 

 

後は次回。

 

前回「匂わせ」をしてしまいましたが、旧いPC雑誌を探しているうちにレトロノスタルジックモードに入ってしまい、ネタ探しに入ったMicrosoft Storeで偶々見つけた、評価点満点(5)の「如何にも1990年代のダンジオン系RPGのDOSゲーム風」なこれ、

 

 

が目について、思わずポチっとダウンロードしてしまいました。(フリーゲームですが...汗;)

 

調べてみると矢張り、

 

ゲーム名                            :The Elder Scrolls: Daggerfall
リリース日                         :1996/12/31
開発元                               :Bethesda Softworks
公式サイト                         :http://www.elderscrolls.com/daggerfall/
使用ゲームエンジン             :
XnGine Engine(Wikiの「マイクロソフト系の旧ゲームエンジン」参照)
ジャンル                            :RPG
マルチプレイ/シングルプレイ:シングルプレイ
OS                                   :Windows/DOS/Unity(非公式/Windows/Mac/Linux)

 

というもので、「ご本家Microsoftが、どうやってx64のWin 11で1996年のDOSゲームを走らせるんだ?」という疑問もあり、早速インストール()してみました。

:Microsoft Storeは同社のセキュリティ認証を得たソフトウェアを公開しており、有償無償を問わず、ダウンロードしてインストールすると、"Program Files"の「隠しフォールダー」"WindowsApps"に保存されます。このフォールダーは強いセキュリティ保護がかかっており、一般ユーザー権限では入れません。("Administrator"も制限されており、既定値のフルコントロール権限者は"TrustedInstaller"のみとなっています。)変更は可能ですが、リスクを伴うのでお勧めできません。ご参考)なお、Win 11の「設定」-「アプリ」でインストールされたプログラムの一覧を開き、「・・・」を押してみるとファイル移動の可否が分かります。例えばMicrosoft StoreからダウンロードしてインストールしたiTunesはこのようになります。

 

Daggerfallを起動するとDaggerfallのウィンドウ画面が出た後、「DOSBox」というロゴ画面が出て、その後非常に懐かしいMS-DOS時代のDOSBoxのシステム情報画面が表示され()、ゲームプログラムが開始されました。

:フルスクリーン表示なのでPrintScreenも効かず、皆さんにお目にかけられないので、DOSBox.exeを単体で起動して画面を取得してみました。

「DOSBox起動画面」

「DOSBox」画面

 

このDOSBoxは、1996年のDOSゲームも走る「完全なCPUエミュレータ」なんだそうです。面白いのは、↑で述べたようにMicrosoft Storeで取得したソフトは通常"Program Files"の「隠しフォールダー」"WindowsApps"に保存されますが、Daggerfallは、

 

"C:\XboxGames\The Elder Scrolls- Daggerfall\"

 

Contentフォールダー内に、DOSBox-0.74フォールダー以下のDOSBoxと共にDFフォールダー以下に保存されています。(勿論、DOSベースのDAGGER.EXEを直接実行しようとすれば、「このアプリはお使いのPCでは実行できません。」というおなじみのエラーメッセージが出ます。)

 

いずれにせよ、ここ迄で「如何にも1990年代のダンジオン系RPGのDOSゲーム風」と思った通りの「1996年のVGAベースの懐かしいレトロダンジオン系RPGのDOSゲーム」が動き出し始めました。

 

次回は「取り敢えず、懐かしさの余りダウンロード、インストールした"Daggerfall"が、実際どういうゲームなのか、を調べる」という後先逆の話をしましょう。

 

前々回前回と「温故知新法」でネタを探していた私、

 

とうとうネタを見つけたかも。

 

と感じるテーマと巡り合いました。今回は「(自分で)プログラミングというよりは、(人の)プログラミング」で、レトロネタでもあり、C#関連のUnity()ネタでもあります。

Unity Technologies(日本法人はユニティ・テクノロジーズ・ジャパン株式会社)が開発・販売している、IDEを内蔵するゲームエンジン。

 

それは、...

 

なお、私はまだこれがどういうものかもよく分かっていませんので、毎回の話は可也ランダムになることをご容赦願います。

 

前回の続きです。

 

米国にこんな良いアーカイブライブラリーがあるなら日本にはないのか?ということで、

 

それは国立国会図書館でしょ?

 

ということで、調べてみたら、国立国会図書館デジタルコレクションというものがあり、登録なしでも一定利用できますが、個人の遠隔複写サービスの為に一応登録し、入ってみました。サンプルとして検索したのは、既に私が自分versionで作ったMENACE。

 

すると、アッター!

 

 

桜田幸嗣さんの「Programing C Language」シリーズの一環で矢張り「マッチ箱名産機MENACE」という題で書かれていました。

 

 

喜び勇んで記事を読もうとクリックしたら、

 

ん、んん、んんん、んんんん!?!?!

 

PDFがでんがなっ!!!!!!!!

 

ということで、どうもPDFかされていても表示してくれないようです。(確かに「ただし、音源、映像、一部の電子書籍・電子雑誌についてはサービス対象外です。」とは書いてあったが。)

 

がっかり!

【OpenGL】シリーズが終わり、やや気が抜けてしまった今日この頃。慢性の「ネタ無し」病が再発し、色々とWEBからヒントをもらおうと四苦八苦しています。

 

1.PCでできること

「家庭で個人がPCを使ってできること、やることは何だろう」という切り口は何度かやりました。因みに「PCでできること」でググるとプログラミング教室系のサイトが多く出てきますが、それらは大体次のような括りとしています。これを吟味すると

(1)プログラミングするまでもなく、既にアプリがある。

(2)既に開発済。

(3)ネタの可能性はあるが、PCだけではできないのでややハードルが高い。

(4)ネタとして取り組むことが可能。

に分類されると思います。「プログラミング」「PCゲーム」は「何を?」が無ければ答えになっていませんので、このアプローチでは「プログラミングの『何を?』について、『絵を描く』と『音(楽)を作る、出す/演奏する』」という答えしか見当たりません。またこれらは素人向けにはMS Paintや Media Playerなどで一定満たされていることは確かです。

  • 1.1 文章作成              }これらはMS Officeやフリーの
  • 1.2 表計算・図形作成  }Libre Officeなどを使った方が良い。
  • 1.3 プリントアウト     }これは「機能」でしかなく、WorkはおろかJobですらない。
  • 1.4 インターネットの利用  }これはEdgeやChromeを使った方が良い。
  • 1.5 プログラミング     }正しいが、畢竟「何を?」で「問が循環」してしまう。
  • 1.6 遠隔操作              }可能性はあるが、他のハードウェアが必要。
  • 1.7 イラスト・マンガ制作  }というか「絵を描く」でしょう?
  • 1.8 動画・画像・音楽の編集  }これも「絵を描く」「音楽を作る/演奏する」の延長です。
  • 1.9 動画・音楽の視聴  }これはBCCSkeltonのDirectShowで実施済。又フリーの既定ソフトもあります。
  • 1.10 ネットショッピング   }これもブラウザーがあれば十分なのでEdgeやChromeの世界です。
  • 1.11 X(Twitter)・FacebookなどSNSの利用   }これもブラウザーがあれば十分。
  • 1.12 テレビ電話・チャット   }これもブラウザーがあれば十分。
  • 1.13 本や新聞を読む   }情報サプライヤーはインターネット経由となるので、これもブラウザーがあれば十分。
  • 1.14 テレビの視聴      }これもインターネット経由のストリーミング動画再生で、DirectShowで実施済。
  • 1.15 PCゲーム           }最も簡単なPC仕事です。既に各種のものを開発済。
  • 1.16 VRゲームの開発にもパソコンを使う         }これは「プログラミング」という意味?
  • 1.17 ネットビジネス   }「ネットビジネス」という仕事はありません。畢竟「具体的に何を?」の答えが必要。
  • 1.18 株・FX・仮想通貨       }ここまで来ると「無理くり感」強すぎ。

 

2.温故知新

前にも「温故知新」ということで、過去のプログラムをC++やC#を使って移植したことがありましたが、「絵を描く」「音楽を作る/演奏する」については、そういえば昔のPC雑誌でBasic等で作る「お絵かきソフト」とか「音楽演奏ソフト」などが載っていましたね。しかし、もう昔の雑誌なんて国立国会図書館に行く等して調べないと手に入りませんよね?

ん?んん?んんん?んんんん?

 

これだけインターネットで情報が溢れていて、2-30年前のしょーもないPC雑誌などが本当にPDF等で読めないんだろうか?

 

という疑問が生じ、当時の色々な雑誌名でググってみると...

 

1.(月間)ASCII

「月刊アスキーの2006年8月号は、創刊からちょうど350号で、PC雑誌としての最終号」で以降「週刊アスキー」として現在も発行されていますね。そしてこの最終号が

倶楽部 読み放題

で読める、ということで「ヤッター!」と思いましたが、「会員登録」が必要なようです。(まぁ、その後はマーケッティングに利用されるのでしょうが。)しかし、内容は(爺さんには懐古的で)可也面白そうではありますが、即プログラミングに役立つようなものではないみたいです。

 

2.Oh!PC

当時市場の9割を占めていた日電(NEC)の8800、9800シリーズを持っている人は読んでいたと思われるこの雑誌()、調べてみたのですが、オークションなどで一冊一冊取得するしか読めないようですね。

:私は「反日電派」だったので一度も読んだことはありませんし、64KBのセグメントを切り替えてプログラミングする気もありませんでした。

 

3.Oh!MZ(その後、Oh!Xに改名)

米国研修から帰国した翌年の1986年、マンションを購入し、自分の部屋が出来て、大枚20万円をはたいて買ったSharpの「スーパーMZ」(MZ2500)を使っていたこともあり、また読者が「反日電」、反Intelであることもあって、(1991年に米国に駐在するまで)これは時々お小遣いで買って読んでいましたね。そして、"Oh!MZ"でググった所...

 

なーんと、フリーのPDFサイトがありましたぁ!!!!!

 

さーすが、「反日電」、反Intelのマニアックな読者で固められた雑誌だけのことはあります。

Oh!MZ(1986-1989)

Oh! X(1990-1995)

 

これらのサイトは当時の雑誌そのものがPDFで読めるので、掲載プログラムも読むことが出来ます。そして

 

更に更に、

 

このサイト、よく見ると海外(米国かな?)のアーカイブサイト("Internet Archive")のようで、" BOOKS”、"VIDEO"、"AUDIO"、"SOFTWARE"、"IMAGES"のジャンルで膨大な情報がアップロードされており、その中に(日本語の)「日本の雑誌」がアップロードされているだけみたいです。

 

これを探検するだけで相当な時間がかかりそう

 

な予感がします。

 

が、これはお宝の山を当てたかな?

 

OpenGLで疲弊し、少しリハビリ中で新しいネタも見つかっていないので、昨日発作的に行動した経験から思いつくままに書いてみます。

 

1.背景

(1)私が現役で大学に入ったのが昭和48年(1973)で、その時日本は高度成長期で「麻雀」「」「ゴルフ」がサラ公の「三種の神器」ともてはやされていました。私も例外ではなく、麻雀で大学に5年通い、「酒は18から」と信じ、ゴルフは同好会を勝手に作って河川敷で回っていました。

 

(2)今年古希になる私、1990年代に米国ニューヨーク州ニューヨーク市(所謂マンハッタン)に5年、2000年初頭にシンガポールに5年駐在していたことは何度か触れましたが、1991年からの米国時代に「IBM PC互換機()」が安価に手に入ったことでプログラミングのみならず、ゲームにも結構嵌りました。

:当時の日本は「悪徳」日電(NEC)の16bit 9800シリーズが市場の9割を席巻していたので、世界標準のIBM PC互換のマシンがこのように呼ばれていました。16bit CPU8086は当時は確か8MH程度の速度でアクセスできる64KBのメモリー空間を16のセグメントに分けて1MB使える、という代物でした。(日電はV30 etcとかの互換チップを作っていたな。)その後、80186、80286(32bit 仮想モードが使えるようになる)、80386、80486、Pentium、Xeon、Itanium、Core等へ発展していきました。(ご参考

 

ソフトも廉価だったのでよく"Egg Head"というソフト屋に行き、そこで色々と買って遊んだソフトの一つに当時マイクロソフトが出していたMicrosoft Golfというゴルフゲームがありましたが、後述の操作上の難点からすぐにやめてしまいました。

 

日本に帰ってからは、ソフトが高いので雑誌の付録CD-ROMについてくるおまけソフトを当てにしていました。その中に東場の4局だけ遊べるシステムソフトの「アクセスガールズのオンライン麻雀」のサンプルがありました。これはさっと遊べてさっと止められるのでハンディなソフトで気に入りました。

 

また、1999年にシンガポールにした後、「森林中心(Sim Lim Centre)」という「昔の秋葉原のようなビル」で違法コピーソフトが定価の1/100~1/10くらいで売っていたので結構買い込みました。その中に米国でも結構買っていたSierra-OnLine社の"PGA Championship Golf 1999"がありました。これも正価だとUS$50程度するのでしょうが、金髪のチンピラから買うとS$2~3でした。これはマウスを引いてバックスイングをし、押してダウンスイング、インパクト、フォロースルーとなる優れもので、(今でも)美しいグラフィックとコースのアンジュレーションやボールのはね方等の本物感がある優良ソフトです。

 

2.その後

私はプログラミングもやりますが、若い時は結構なゲーマーで、米国駐在当時好きだったのは正統派RPGであるWizardry(最初はテキストベースです)、アドベンチャーゲーム(Mystなどもこの時代です)、特に嵌ったのはDungeons and Dragonsシリーズでしたね。

 

日本に帰ってからはDiabloシリーズに嵌り、シンガポールに行ってもMicrosoftのDungeon SiegeシリーズやFableシリーズを楽しみましたが、50歳台になるともう気力や集中力がなく、この手の「長物」は苦手になりました。

 

そして60歳~現在まではもうゲームにはまるような知力、気力、体力がなく、簡単に早く決着がつくものしかやらなくなり、東場4局だけの「アクセスガールズのオンライン麻雀」と(ミスが続くと直ぐにやめられる)"PGA Championship Golf 1999"しかやらなくなりました。

 

3.そして昨日

昨日何気なくWEBをさまよっていたら、Microsoft Golf 3.0のダウンロードサイトがあり、

 

余りに懐かしかったこともあり、

 

64bit Windows 11での走らせ方も調べて(Win 7の互換モードで実行させます)動かせるようにするまで1時間超かけ、実際に動かしたらちゃんと動くのですが、

Microsoft Golfは右下のスイングサークルを適切なタイミングでクリックするタイプで、それが

 

実際のゴルフスイングとの違いの違和感

 

でこのゲームを止めたことを思い出しました。また、当時は「美しい」と感じたVGA(640 x 480)のグラフィックも現在の解像度に慣れた私には受け入れがたいものでした。その結果、1時間超掛けてインストールしたものを

 

5分ですべてアンインストール

 

することになりました。

 

しかし、その結果毎日「今日の運試し」としてやっている二つのゲーム、

 

 

が、グラフィック的にも内容的にもよく練られており、

 

良いゲームソフトの評価ポイント

  =

飽きが来ない

 

ことだということを改めて実感させました。

 

尤も、これは老人の私だけに限ったことかもしれませんが...何か?

 

ps. 余談ですが、WEBで"Sierra OnLine"を検索するともうソフトハウスはありません。調べてみるとソフト部門は売っ払らって現在はActivision Blizzardの傘下のようです。一方キーワードに"Golf"を入れると本物のゴルフコースのSierra Resort(日本にも進出している観光デベロッパー)などが出てきますがロゴから違う会社のようです。米国サイトを調べていたら、(ロゴから見ても分かる)本体はアウトドア品販売事業になったようです。年が経ち。皆変わったんだなぁ、という感慨が強いですね。

 

前回

 

「今年四月末に「次ネタ」として思いつき、五月から始めた【OpenGL】シリーズですが、ほぼ1か月半かかり、OpenTK導入、GLUT及びWindows SDK、GLUT及びBCCSkeltonを経て、再度OpenTKで無事締めることが出来ました。懸案であったOpenGLの学習(と言っても「初めの一歩」程度ですが)も何とかでき、Windows SDK、(久々の)BCCCSkelton、そして現代的なC#による夫々の、そしてそれなりの成果物も残すことが出来ました。」

 

と書いたように、今回の【OpenGL】シリーズは、質量ともにヘビーで今年古希の私にはやや重い内容でした。

 

しかし、

 

プログラミングというよりも、純粋に何かを体系的に学ぶというテーマとしては(従来苦手意識があって避けてきたこともあり)良かったと考えています。何とかOpenGLの何たるか、その入り口までは辿り着けたような気がします。

 

次のネタの前に、脳味噌を少し休めて

 

見たいと思います。又面白いネタを見つけてきますので、

 

Stay tuned!

 

ps. 余談ですが、今朝偶然に「ループ量子重力力学」の話を読みました。「時間は存在しない」によれば、時間はエントロピーの増大に過ぎないというものです。なーるほど、とは思いますが、これが証明されたとしても私たちの生活が変わることはない("So what?")というのも面白いなと思いますね。

前回予告した通り、今回は「OpenTK、再び」シリーズの最終回として、DLLに入れた

 

基本図形(プリミティブ)6種のサンプルのお披露目

 

をします。

前々回紹介したGLControl_BaseとGameWindow_Baseの内、後者を利用して作りました。コンパイルには

(1)前回紹介したOpenTK_Primitives.csをMSCompAssの"Option"で「ライブラリー」をターゲットにしてコンパイルしてDLL(OpenTK_Primitives.dll)を作り

(2)今回のサンプルプログラムではプログラム本文で"using OpenTK_Primitives;"とし、

(3)今回のサンプルプログラムではそのライブラリー(OpenTK_Primitives.dll)をMSCompAssの"Option"で参照します。

 

1.立(直)方体(Cube)と(正)三角錐(Triangular)

先ずはGLUTでも使った立方体と正三角錐から。OpenTK_Primitives名前空間で定義されたOTKPrimsクラスのインスタンスを生成します。(以下すべて同じ為、以下省略。)

 

        OTKPrims pv = new OTKPrims();                    //otkPrimitivesクラスインスタンス

 

インスタンス"pv”のメソッドとしてプリミティブの描画を行うOnRenderFrameのコードは以下の通り。

 

        //ウィンドウ描画が更新された場合に実行される
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            if(drawStop)
                return;
                                    //描画停止
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.MatrixMode(MatrixMode.Modelview);
        //モデルビューモードを選択
            //視点を設定して原点(0, 0, 0)を見る
            Vector3 eye = new Vector3(0.0f, 0.0f, 5.0f);
            Matrix4 modelview = Matrix4.LookAt(eye, Vector3.Zero, Vector3.UnitY);
    //gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
            GL.LoadMatrix(ref modelview);                //現在の変換マトリックスに設定
            //以下に描画処理を行う

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.PushMatrix();
            {
                GL.Translate(1.0, 0.0, 0.0);
                GL.Rotate(RotateX, 1, 0, 0);
                GL.Rotate(RotateY, 0, 1, 0);
                GL.Rotate(RotateZ, 0, 0, 1);
                GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color4.Red);
                pv.Cube(SorW);
            }
            GL.PopMatrix();
            GL.PushMatrix();
            {
                GL.Translate(-1.0, 0.0, 0.0);
                GL.Rotate(RotateX, 1, 0, 0);
                GL.Rotate(RotateY, 0, 1, 0);
                GL.Rotate(RotateZ, 0, 0, 1);
                GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color4.Blue);
                pv.Triangular(SorW);
            }
            GL.PopMatrix();
            RotateX += 1.5f; 
            RotateY += 5.0f; 
            RotateZ += 1.0f; 
            this.SwapBuffers();
        }

 

するとこうなります。(ソリッドとワイアー)

 

2.(Sphere)とt円環(Torus)

次は球と円環。描画を行うOnRenderFrameのコードは以下の通り。

 

        //ウィンドウ描画が更新された場合に実行される
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            if(drawStop)
                return;
                                    //描画停止
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.MatrixMode(MatrixMode.Modelview);
        //モデルビューモードを選択
            //視点を設定して原点(0, 0, 0)を見る
            Vector3 eye = new Vector3(0.0f, 0.0f, 5.0f);
            Matrix4 modelview = Matrix4.LookAt(eye, Vector3.Zero, Vector3.UnitY);
    //gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
            GL.LoadMatrix(ref modelview);                //現在の変換マトリックスに設定
            //以下に描画処理を行う

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.PushMatrix();
            {
                GL.Rotate(RotateX, 1, 0, 0);
                GL.Rotate(RotateY, 0, 1, 0);
                GL.Rotate(RotateZ, 0, 0, 1);
                pv.Sphere(SorW);
                pv.Torus(SorW);
            }
            GL.PopMatrix();
            RotateX += 1.5f; 
            RotateY += 5.0f; 
            RotateZ += 1.0f; 
            this.SwapBuffers();
        }

 

するとこうなります。(ソリッドとワイアー)

 

3.円柱(Pipe)と円錐(Cone)

次は円柱と円錐。描画を行うOnRenderFrameのコードは以下の通り。

 

        //ウィンドウ描画が更新された場合に実行される
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            if(drawStop)
                return;
                                    //描画停止
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.MatrixMode(MatrixMode.Modelview);
        //モデルビューモードを選択
            //視点を設定して原点(0, 0, 0)を見る
            Vector3 eye = new Vector3(0.0f, 0.0f, 5.0f);
            Matrix4 modelview = Matrix4.LookAt(eye, Vector3.Zero, Vector3.UnitY);
    //gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
            GL.LoadMatrix(ref modelview);                //現在の変換マトリックスに設定
            //以下に描画処理を行う

            GL.PushMatrix();
            {
                GL.Rotate(RotateX, 1, 0, 0);
                GL.Rotate(RotateY, 0, 1, 0);
                GL.Rotate(RotateZ, 0, 0, 1);
                GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color4.Red);
                pv.Pipe(SorW, 0.35, 1.5);
                GL.PushMatrix();
                    GL.Rotate(90, 0, 0, 1);
                    GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color4.Cyan);
                    pv.Cone(SorW, 0.5, 2);
                GL.PopMatrix();
            }
            GL.PopMatrix();
            RotateX += 1.5f; 
            RotateY += 5.0f; 
            RotateZ += 1.0f; 
            this.SwapBuffers();
        }

 

するとこうなります。(ソリッドとワイアー)

 

4.おまけ(惑星軌道)

最後に(既に「におわせ画像」でご存じと思いますが)、

球を使って太陽、地球、月の自公転の様子(勿論縮尺等は正確ではありませんよ)っぽくしたGLUTのサンプルがあったので、OpenTKに移植してみました。描画を行うOnRenderFrameのコードは以下の通り。(なお、"Theta_"はクラスフィールドとして予め宣言しておきます。)

 

        //ウィンドウ描画が更新された場合に実行される
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            if(drawStop)
                return;
                                    //描画停止
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.MatrixMode(MatrixMode.Modelview);
        //モデルビューモードを選択
            //視点を設定して原点(0, 0, 0)を見る
            Vector3 eye = new Vector3(0.0f, 0.0f, 5.0f);
            Matrix4 modelview = Matrix4.LookAt(eye, Vector3.Zero, Vector3.UnitY);
    //gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
            GL.LoadMatrix(ref modelview);                //現在の変換マトリックスに設定
            //以下に描画処理を行う

            //鏡面光成分のセット
            //float[] material_specular = new float[4] {0.2f, 0.2f, 0.2f, 1.0f};        //鏡面光成分
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, Color4.White);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, 32.0f);
            GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color4.OrangeRed);
            pv.Sphere(SorW,20.0);                        
//太陽
            GL.PushMatrix();
            {
                GL.Rotate(Theta_Sun, 0.0f, 0.0f, 1.0f);    
//z軸周りにTheta_Sun度回転
                GL.Translate(60.0f, 0.0f, 0.0f);        //x軸に60平行移動
                GL.Rotate(Theta_Earth, 0.0f, 0.0f, 1.0f);//z軸周りにTheta_Earth度回転
                GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color4.SeaGreen);
                pv.Sphere(SorW, 10.0);                  
 //地球
                GL.Rotate(Theta_Moon, 0.0f, 0.0f, 1.0f);//z軸周りにTheta_Moon度回転
                GL.Translate(20.0f, 0.0f, 0.0f);        //x軸に20平行移動
                GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color4.Yellow);
                pv.Sphere(SorW, 5.0);                    
//月
            }
            GL.PopMatrix();
            Theta_Sun += 2.0f;
            Theta_Earth -= 10.0f;
            Theta_Moon += 6.0f;
            
//ダブルバッファー
            this.SwapBuffers();
        }

 

するとこうなります。(ソリッドとワイアー)

 

今年四月末に「次ネタ」として思いつき、五月から始めた【OpenGL】シリーズですが、ほぼ1か月半かかり、OpenTK導入、GLUT及びWindows SDK、GLUT及びBCCSkeltonを経て、再度OpenTKで無事締めることが出来ました。懸案であったOpenGLの学習(と言っても「初めの一歩」程度ですが)も何とかでき、Windows SDK、(久々の)BCCCSkelton、そして現代的なC#による夫々の、そしてそれなりの成果物も残すことが出来ました。

 

ということで、本シリーズは一応ここで仮締め

 

ということに致します。皆さま、ご清聴ありがとうございました。

 

前回予告した通り、今後はGameWindow版スケルトンを使ってGLUTでやってきたプリミティブ等を移植してみようかと思います。

 

で、その主旨は、

 

OpenTKにすぐ使える基本図形(プリミティブ)のライブラリーが無かったので、OpenTKの学習と動作確認用に始めたものです。現在、6つのプリミティブを作ってみました。(コードは巻末参照)これらを同じような使い方ができるよう「緩い仕様」として、(x, y, zの-1.0~1.0内に収まる既定サイズにすることは勿論)


共通仕様:
(1) 第1引数がソリッド・ワイアーのフラグ(bool)
(2) 以降の引数に既定値を与える

 

だけ決めました。これで出力ターゲットをDLLにしてMSCompAssでコンパイルすると、

 

using OpenTK_Primitives;            //OpenTK自作図形ライブラリー
 

とするだけで6つの基本図形を描画できるようになります。

 

現在利用できるプリミティブは以下の6つ()でサンプルプログラムを作る程度であれば耐えられるのではないでしょうか?また、(3)(5)(6)は分割数を変えると正4面体などになるので実験用には楽しいです。

 

(1)立(直)方体(Cube)

(2)(正)三角錐(Triangular)

(3)(Sphere)

(4)円環(Torus)

(5)円柱(Pipe)

(6)円錐(Cone)

:(1)、(2)はGLUTシリーズで使ったやつの移植です。後はGLUTは関数化しているので、webに出ていたプログラムを眺め、比較し、いいとこどりして纏めてみました。

 

なお、使用するスケルトンはGameWindow用ですが、メンバーフィールドとして、

 

        bool SorW = true;                                //Solidか、Wireかのフラグ
        bool drawStop = false;                            //描画停止
 

およびマウスイベント

 

        //Mouse割込み
        this.MouseDown += gW_MouseDown;
 

 

        //Mouse割込み
        private void gW_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if(e.Button == MouseButton.Left)
                SorW = !SorW;
            else if(e.Button == MouseButton.Right)
                drawStop = !drawStop;
         }

 

を追加し、ウィンドウ描画が更新された場合に実行されるOnRenderFrame(FrameEventArgs e)メソッドを

 

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            if(drawStop)
                return;                                    
//描画停止
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.MatrixMode(MatrixMode.Modelview);      
 //モデルビューモードを選択
            //視点を設定して原点(0, 0, 0)を見る
            Vector3 eye = new Vector3(0.0f, 0.0f, 5.0f);
            Matrix4 modelview = Matrix4.LookAt(eye, Vector3.Zero, Vector3.UnitY);  
 //gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
            GL.LoadMatrix(ref modelview);                //現在の変換マトリックスに設定
            //以下に描画処理を行う
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.PushMatrix();
            {
                GL.Rotate(RotateX, 1, 0, 0);
                GL.Rotate(RotateY, 0, 1, 0);
                GL.Rotate(RotateZ, 0, 0, 1);
               
//ここで描画メソッドを呼び出します。

            }
            GL.PopMatrix();
            RotateX += 1.5f; 
            RotateY += 5.0f; 
            RotateZ += 1.0f; 
            this.SwapBuffers();
        }

 

としてクルクルと回転させ、左ボタンでソリッドとワイアーの切り替え、右ボタンで描画停止を行うことにしました。

 

追伸:因みに追加説明すると、メソッドはGLUTの関数と非常に名称が似ているので「大体何をするのか」についてアタリが付くと思います。逆に結構戸惑ったのは、GLUTではデータはfloatかdoubleだけだったのですが、OpenTKでは行列変数(Vector3等-注1)というものがあり、これを使って処理することが多いことです。しかし、慣れるとかえってこっちの方が便利で楽になりました。又「OpenGLの関数」はGLクラスの静的メソッドになっているので、「オブジェクト」というよりも「関数」という感じで使用します。これはOpenGLの使用を崩したくなかったのでしょう。そんなわけで私も「プリミティブ毎にクラス化する考え」(注2)もあったのですが、あえてC#の「プリミティブクラス」として6つのプリミティブをメソッドで呼ぶようにしました。

注1:Vector3.UnitZ, Vector3.UnitX, Vector3.UnitZ、Vector.Zero等を始めてみた時は???となりました。

注2:例えばSphereクラスを作って、オブジェクトを生成し、Showメソッドで表示、Tranlatesde移動、Rotateで回転、Color、Materialで色や質感を出す、等のオブジェクト化が図れますが、それは既にOpenGLではないですよね。

 

次回はこれらのサンプルのお披露目を行います。

 

【OpenTK_Primitives.cs】

/////////////////////////////////////////////////
//                        OpenTK_Primitives.cs
//                        OpenTKの基本図形集
//                        Copyright (c) 2024 by Ysama
//共通仕様:
//(1) 第1引数がソリッド・ワイアーのフラグ(bool)
//(2) 以降の引数に既定値を与える
/////////////////////////////////////////////////

using System;
using OpenTK;                                    //OpenTKを使用する際に必須
using OpenTK.Graphics;                        //OpenTKのグラフィックを使用する際に必須
using OpenTK.Graphics.OpenGL;            //OpenGLを使用する際に必要

namespace OpenTK_Primitives
{
    public class OTKPrims
    {
        //立(直)方体(引数:ソリッドまたはワイアー、横、縦、高さ)
        public void Cube(bool SorW = true, double hor = 1.0, double ver = 1.0, double dep = 1.0)
        {
            //立(直)方体データ
            double[][] Qvertex = new double[8][];    //四角形の8頂点(奥左下から反時計回り4点、手前左下から反時計回り4点)
            Qvertex[0] = new double[3] {-hor / 2, -ver / 2, -dep / 2};
            Qvertex[1] = new double[3] { hor / 2, -ver / 2, -dep / 2};
            Qvertex[2] = new double[3] { hor / 2,  ver / 2, -dep / 2};
            Qvertex[3] = new double[3] {-hor / 2,  ver / 2, -dep / 2};
            Qvertex[4] = new double[3] {-hor / 2, -ver / 2,  dep / 2};
            Qvertex[5] = new double[3] { hor / 2, -ver / 2,  dep / 2};
            Qvertex[6] = new double[3] { hor / 2,  ver / 2,  dep / 2};
            Qvertex[7] = new double[3] {-hor / 2,  ver / 2,  dep / 2};
            int[][] Qedge = new int[12][];            //頂点を結ぶ12線分
            Qedge[0] = new int[2] {0, 1};
            Qedge[1] = new int[2] {1, 2};
            Qedge[2] = new int[2] {2, 3};
            Qedge[3] = new int[2] {3, 0};
            Qedge[4] = new int[2] {4, 5};
            Qedge[5] = new int[2] {5, 6};
            Qedge[6] = new int[2] {6, 7};
            Qedge[7] = new int[2] {7, 4};
            Qedge[8] = new int[2] {0, 4};
            Qedge[9] = new int[2] {1, 5};
            Qedge[10] = new int[2] {2, 6};
            Qedge[11] = new int[2] {3, 7};
            int[][] Qface = new int[6][];            //四角形6面
            Qface[0] = new int[4] {0, 1, 2, 3};        //奥面
            Qface[1] = new int[4] {1, 5, 6, 2};        //右面
            Qface[2] = new int[4] {5, 4, 7, 6};        //手前面
            Qface[3] = new int[4] {4, 0, 3, 7};        //左面
            Qface[4] = new int[4] {4, 5, 1, 0};        //底面
            Qface[5] = new int[4] {3, 2, 6, 7};        //天面
            Vector3[] vecNormal = new Vector3[6] {-Vector3.UnitZ, Vector3.UnitX, Vector3.UnitZ, -Vector3.UnitX, -Vector3.UnitY, Vector3.UnitY};
            //立(直)方体描画
            GL.PushMatrix();
            if(SorW)
            {
                GL.Begin(PrimitiveType.Quads);        //四角面を描画(Solid)
                    for(int j = 0; j < 6; j++)
                    {
                        GL.Normal3(vecNormal[j]);    //法線の設定
                        for(int i = 0; i < 4; i++)
                            GL.Vertex3(Qvertex[Qface[j][i]][0], Qvertex[Qface[j][i]][1], Qvertex[Qface[j][i]][2]);
                    }
                GL.End();
            }
            else
            {
                GL.Begin(PrimitiveType.Lines);        //四角面を描画(Wire)
                    for(int j = 0; j < 12; j++)
                    {
                        for(int i = 0; i < 2; i++)
                            GL.Vertex3(Qvertex[Qedge[j][i]][0], Qvertex[Qedge[j][i]][1], Qvertex[Qedge[j][i]][2]);
                    }
                GL.End();
            }
            GL.PopMatrix();
        }

        //(正)三角錐(引数:ソリッドまたはワイアー、辺長さ、縦辺(ver - 既定1.0)の倍数)
        public void Triangular(bool SorW = true, double len = 1.225, double ratio = 1.0)
        {
            double hor = len / 2;
            double dep = len * Math.Sin(Math.PI / 3);                //Sin(60)
            double ver = len * ratio * Math.Sqrt(2.0 / 3.0);    //sqrt(power(len, 2.0) + power(dep * 2.0 / 3.0, 2.0))
            //(正)三角錐データ
            double[][] Tvertex = new double[4][];    //三角錐の4頂点
            Tvertex[0] = new double[3] {0.0, ver * 2 / 3, 0.0};            //頂天
            Tvertex[1] = new double[3] {hor, -ver / 3, -dep / 3};            //右奥
            Tvertex[2] = new double[3] {-hor,  -ver / 3, -dep / 3};    //左奥
            Tvertex[3] = new double[3] {0.0,  -ver / 3, dep * 2 / 3};    //手前下
            int[][] Tedge = new int[6][];                //頂点を結ぶ5線分
            Tedge[0] = new int[2] {0, 1};
            Tedge[1] = new int[2] {1, 2};
            Tedge[2] = new int[2] {2, 0};
            Tedge[3] = new int[2] {0, 3};
            Tedge[4] = new int[2] {3, 2};
            Tedge[5] = new int[2] {1, 3};
            int[][] Tface = new int[4][];                //三角錐の4面
            Tface[0] = new int[3] {0, 1, 2};
            Tface[1] = new int[3] {0, 2, 3};
            Tface[2] = new int[3] {0, 3, 1};
            Tface[3] = new int[3] {1, 2, 3};
                if(SorW) {
                GL.Begin(PrimitiveType.Triangles);
                        for(int j = 0; j < 4; ++j)
                        {
                            Vector3 v1 = new Vector3((float)Tvertex[Tface[j][0]][0], (float)Tvertex[Tface[j][0]][1], (float)Tvertex[Tface[j][0]][2]);
                            Vector3 v2 = new Vector3((float)Tvertex[Tface[j][1]][0], (float)Tvertex[Tface[j][1]][1], (float)Tvertex[Tface[j][1]][2]);
                            Vector3 v3 = new Vector3((float)Tvertex[Tface[j][2]][0], (float)Tvertex[Tface[j][2]][1], (float)Tvertex[Tface[j][2]][2]);
                            GL.Normal3(Vector3.Normalize(Vector3.Cross(v2 - v1, v3 - v1)));
                            for(int i = 0; i < 3; ++i)
                                    GL.Vertex3(Tvertex[Tface[j][i]][0], Tvertex[Tface[j][i]][1], Tvertex[Tface[j][i]][2]);
                        }
                GL.End();
            }
            else {
                GL.Begin(PrimitiveType.Lines);
                        for(int i = 0; i < 6; i++)
                        {
                            GL.Vertex3(Tvertex[Tedge[i][0]]);
                            GL.Vertex3(Tvertex[Tedge[i][1]]);
                        }
                GL.End();
            }
        }

        //球(引数:ソリッドまたはワイアー、半径、360°の分割数)
        public void Sphere(bool SorW = true, double radius = 0.5, int slices = 36)
        {
            double angle = Math.PI / slices * 2;    //angle(radian) = (360 / slices) * (Math.PI / 180);
            if(SorW)
                GL.Begin(PrimitiveType.TriangleStrip);            //球体を描画(Solid)
            else
                GL.Begin(PrimitiveType.LineStrip);                //球体を描画(Wire)
            {
                for(int j = -slices / 2; j < slices / 2; j++)    //slices along Z軸
                {
                        for(int i = 0; i <= slices; i++)    //slices around Z軸
                        {
                            Vector3d vec1 = new Vector3d(radius * Math.Cos(angle * j) * Math.Cos(angle * i), radius * Math.Cos(angle * j) * Math.Sin(angle * i), radius * Math.Sin(angle * j));
                            GL.Normal3(vec1);
                            GL.Vertex3(vec1);
                            Vector3d vec2 = new Vector3d(radius * Math.Cos(angle * (j - 1)) * Math.Cos(angle * i), radius * Math.Cos(angle * (j - 1)) * Math.Sin(angle * i), radius * Math.Sin(angle * (j - 1)));
                            GL.Normal3(vec2);
                            GL.Vertex3(vec2);
                        }
                }
            }
            GL.End();
        }

        //円環(引数:ソリッドまたはワイアー、半径、円環の太さ、面の肌理)
        public void Torus(bool SorW = true, double r1 = 1.0, double r2 = 0.2, int count = 128)
        {
            if(!SorW) count = 32;    //ワイアーの場合、肌理を粗くする
            GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color4.Cyan);
            double x = r1 * Math.Sqrt(2 * (1 - Math.Cos((Math.PI / 180.0) * (360f / count))));
            for(int k = 0; k < count; k++)
            {
                GL.PushMatrix();
                GL.Rotate(360f / count * k, 0, 1, 0);            //Y軸を中心として一周する
                GL.Translate(0, 0, r1 - r2);                    //Z軸方向へ半径 - 円環太さ移動
                for(int i = 0; i <= count; i++)
                {
                    GL.Rotate(360f / count, 1, 0, 0);            //X軸を中心に指定角回転
                    if(SorW)
                        GL.Begin(PrimitiveType.Quads);            //四角面を描画(Solid) count = 128;
                    else
                        GL.Begin(PrimitiveType.LineStrip);    //四角面を描画(Wire) count = 32;
                    {
                        Vector3 p1 = new Vector3((float)(-x / 2), (float)r2, (float)(x / 2));
                        Vector3 p2 = new Vector3((float)(x / 2), (float)r2, (float)(x / 2));
                        Vector3 p3 = new Vector3((float)(x / 2), (float)r2, (float)(-x / 2));
                        GL.Normal3(Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1)));
                        GL.Vertex3(-x / 2, r2, x / 2);
                        GL.Vertex3(x / 2, r2, x / 2);
                        GL.Vertex3(x / 2, r2, -x / 2);
                        GL.Vertex3(-x / 2, r2, -x / 2);
                    }
                    GL.End();
                }
                GL.PopMatrix();
            }
        }

        //円柱(引数:ソリッドまたはワイアー、半径、縦の長さ)
        public void Pipe(bool SorW = true, double radius = 0.5, double length = 2.0)
        {
            double step = 0.1;
            for(double rad = -Math.PI; rad <= Math.PI; rad += step)
            {
                if(SorW)
                    GL.Begin(PrimitiveType.Quads);
                else
                    GL.Begin(PrimitiveType.LineStrip);
                    //側面法線
                    Vector3 v0 = new Vector3((float)(radius * Math.Cos(rad)), (float)(length / 2), (float)(radius * Math.Sin(rad)));
                    Vector3 v1 = new Vector3((float)(radius * Math.Cos(rad)), (float)-(length / 2), (float)(radius * Math.Sin(rad)));
                    Vector3 v2 = new Vector3((float)(radius * Math.Cos(rad + step)), (float)-(length / 2), (float)(radius * Math.Sin(rad + step)));
                    GL.Normal3(Vector3.Normalize(Vector3.Cross(v1 - v0, v2 - v0)));
                    //側面
                    GL.Vertex3(radius * Math.Cos(rad + step), length / 2, radius * Math.Sin(rad + step));
                    GL.Vertex3(radius * Math.Cos(rad), length / 2, radius * Math.Sin(rad));
                    GL.Vertex3(radius * Math.Cos(rad), -length / 2, radius * Math.Sin(rad));
                    GL.Vertex3(radius * Math.Cos(rad + step), -length / 2, radius * Math.Sin(rad + step));
                GL.End();
                if(SorW)
                    GL.Begin(PrimitiveType.Triangles);
                else
                    GL.Begin(PrimitiveType.LineStrip);
                //天面法線
                GL.Normal3(Vector3.UnitY);
                //天面
                    GL.Vertex3(0.0, length / 2, 0.0);
                    GL.Vertex3(radius * Math.Cos(rad), length / 2, radius * Math.Sin(rad));
                    GL.Vertex3(radius * Math.Cos(rad + step), length / 2, radius * Math.Sin(rad + step));
                GL.End();
                if(SorW)
                        GL.Begin(PrimitiveType.Triangles);
                else
                        GL.Begin(PrimitiveType.LineStrip);
                    //底面法線
                    GL.Normal3(-Vector3.UnitY);
                    //底面
                    GL.Vertex3(0.0, -length / 2, 0.0);
                    GL.Vertex3(radius * Math.Cos(rad), -length / 2, radius * Math.Sin(rad));
                    GL.Vertex3(radius * Math.Cos(rad + step), -length / 2, radius * Math.Sin(rad + step));
                GL.End();
            }
        }

        //円錐(引数:ソリッドまたはワイアー、半径、縦の高さ)
        public void Cone(bool SorW = true, double radius = 0.5, double height = 2.0)
        {
            double step = 0.1;
            for(double rad = -Math.PI; rad <= Math.PI; rad += step)
            {
                if(SorW)
                    GL.Begin(PrimitiveType.Triangles);
                else
                    GL.Begin(PrimitiveType.LineStrip);
                    //側面法線
                    Vector3 v1 = new Vector3((float)(radius * Math.Cos(rad)), (float)-(height / 2), (float)(radius * Math.Sin(rad)));
                    Vector3 v2 = new Vector3((float)(radius * Math.Cos(rad + step)), (float)-(height / 2), (float)(radius * Math.Sin(rad + step)));
                    GL.Normal3(Vector3.Normalize(Vector3.Cross(v1, v2)));
                    //側面
                    GL.Vertex3(0.0, height / 2, 0.0);
                    GL.Vertex3(radius * Math.Cos(rad), -height / 2, radius * Math.Sin(rad));
                    GL.Vertex3(radius * Math.Cos(rad + step), -height / 2, radius * Math.Sin(rad + step));
                    //底面法線
                    GL.Normal3(-Vector3.UnitY);
                    //底面
                    GL.Vertex3(0.0, -height / 2, 0.0);
                    GL.Vertex3(radius * Math.Cos(rad), -height / 2, radius * Math.Sin(rad));
                    GL.Vertex3(radius * Math.Cos(rad + step), -height / 2, radius * Math.Sin(rad + step));
                GL.End();
            }
        }
    }
}