忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


Java 列挙型

久々にJavaを使っていると(JOGL)、
列挙型の使い方がわからなくなったので
今後のためメモしておきます。

まず文脈を話しておくと、
Java用のOpenGL(JOGL)を使っていたのですが、
この定数が汚いんですね。

多くの定数がGLというクラスの中に定義されていて、
しかもその定数の種類というのが、
プリミティブのタイプもカリングモードも
行列の種類もアルファブレンディングも
なにもかもごっちゃになっています。

//プリミティブのタイプを表す定数
GL.GL_POINTS
GL.GL_LINES
GL.GL_LINE_STRIP
GL.GL_LINE_LOOP
GL.GL_TRIANGLES
GL.GL_TRIANGLE_FAN
...
//行列
GL.GL_PROJECTION
GL.GL_MODELVIEW

...(以下略)


ごちゃごちゃです。
まず第一、"GL"をなぜ2度も書かなければならないのか
そこが不条理です。

そして第二に、目的の異なる定数を1つのクラスに
大量に詰め込むのはよくありません。
どのくらい大量かというと、このくらいです。
もうわけがわかりません。

こんなときには列挙型を使って用途別に分別すべきです。
(そしてGLクラス全体のラッパーを作り始めたのですが、
ここではあまり関係ありません)

とりあえず最初は描画する図形のタイプを
表す定数を列挙型にすることにしました。

しくじってしまったのはここで、
C#のようなやり方をしてはいけません

Compile Error!

import javax.media.opengl.*;

public enum PrimitiveType {
	Points = GL.GL_POINTS,
	Lines = GL.GL_LINES,
	LineStrip = GL.GL_LINE_STRIP,
	LineLoop = GL.GL_LINE_LOOP,
	Triangles = GL.GL_TRIANGLES,
	TriangleFan = GL.GL_TRIANGLE_FAN,
	Quads = GL.GL_QUADS,
	QuadStrip = GL.GL_QUAD_STRIP,
	Polygon = GL.GL_POLYGON
}

正しくはこうです。
(javaのコードとして記法に問題がありますが、
まあほうっておきましょう(PointsではなくPOINTS))

import javax.media.opengl.*;

public enum PrimitiveType {
	Points(GL.GL_POINTS),
	Lines(GL.GL_LINES),
	LineStrip(GL.GL_LINE_STRIP),
	LineLoop(GL.GL_LINE_LOOP),
	Triangles(GL.GL_TRIANGLES),
	TriangleFan(GL.GL_TRIANGLE_FAN),
	Quads(GL.GL_QUADS),
	QuadStrip(GL.GL_QUAD_STRIP),
	Polygon(GL.GL_POLYGON);
	
	private int value;
	
	PrimitiveType(int value){
		this.value = value;
	}
	
	public int value(){
		return value;
	}
}

Javaでは = ではなく () を使うんですね・・・
知りませんでした。
そしてその値がコンストラクタの引数となるようです。
その値をわざわざ保存して、ゲッターで手に
入れられるようにしなければなりません。

そして、さらにC#と違う点として、値を複数指定することも出来ます。
例えば、サンの解説によると、太陽系の各惑星の列挙型を作ったとすれば、
質量と半径の両方を列挙型の値とすることができます。

このことから、Javaの列挙型はC#のものよりも柔軟であるといえるでしょう。
それがいいほうに向かうか悪い方に向かうかはわかりませんが。


拍手[0回]

PR

かんたんXNA その18 BasicEffectで平行光

このページは古いです
最新版はこちら

BasicEffect.EnableDefaultLightingメソッドを使うと
簡単なライティングを行うことが出来ますが、
BasicEffect.DirectionalLight0プロパティを使うと、
もう少し詳しいライティングの設定が出来ます。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect basicEffect;

    VertexPositionNormalTexture[] vertices;

    ContentManager content;
    Texture2D texture;

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);

        vertices  = new VertexPositionNormalTexture[3];
        vertices[0].Position = new Vector3(0, 1, 0);
        vertices[0].Normal = new Vector3(0, 0, 1);
        vertices[0].TextureCoordinate = new Vector2(0.5f, 0);

        vertices[1].Position = new Vector3(1, 0, 0);
        vertices[1].Normal = new Vector3(0, 0, 1);
        vertices[1].TextureCoordinate = new Vector2(1, 1);

        vertices[2].Position = new Vector3(-1, 0, 0);
        vertices[2].Normal = new Vector3(0, 0, 1);
        vertices[2].TextureCoordinate = new Vector2(0, 1);
        
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            basicEffect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45), 
                Window.ClientBounds.Width/(float)Window.ClientBounds.Height,
                1, 100
                );


            texture = content.Load<Texture2D>("FlyingSpaghettiMonster");
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionNormalTexture.VertexElements
            );

        basicEffect.TextureEnabled = true;
        basicEffect.Texture = texture;

        basicEffect.LightingEnabled = true;
        basicEffect.DirectionalLight0.Enabled = true;
        basicEffect.DirectionalLight0.Direction = new Vector3(1, 0, -1);
        basicEffect.DirectionalLight0.DiffuseColor = new Vector3(0.5f, 0.5f, 0.5f);
        basicEffect.DirectionalLight0.SpecularColor = Color.White.ToVector3();

        basicEffect.Begin();

        foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
        {
            pass.Begin();

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        basicEffect.End();
    }
}

directionalLight1.JPG
このサンプルプログラムでは、斜め45度の角度から三角形に光を照射しています。
左が明るく、右が暗くなっています。
ここでは、太陽光のように光源が十分に遠くにある、光をシミュレートしています。
(そのため、指定するのは光の方向だけで、光源の位置は指定しません
「十分に遠くにある」という情報だけで十分なのです。
十分に遠くにあれば光の方向は大体どこでも同じだからです。
もし光源が近くにあるのなら、
光の方向はちょっと動いただけでも変わりますが、
遠くにあるのならほとんど変わりません。
ちょうど車で移動しているときに月がどこまでも
追いかけてくるように見えるのと同じ理由です。)


まず、BasicEffectで(手作業で)ライティングを行いたいときには、
BasicEffect.LightingEnabledプロパティをtrueにセットします。
(EnableDefaultLightingメソッドを使うときにはこの作業がいらなかったのは、
多分メソッドの内部でこのプロパティをtrueにセットしてくれているからでしょう。)


public bool LightingEnabled { get; set; }

これをtrueにセットしないとライティングが考慮されなくなって
普通に描画されてしまいます。(影がつきません)
xnaSimplextTriangleTextured.JPG

そして、無限遠から来る光、平行光を使うには
BasicEffect.DirectionalLight0プロパティを使います。

public BasicDirectionalLight DirectionalLight0 { get; }

一番最後に"0"がついているのは、平行光を複数使いたいときのために、
まだまだ他にも用意されているからです。
(ここで使っているのは最初の0だけですが)

public BasicDirectionalLight DirectionalLight1 { get; }
public BasicDirectionalLight DirectionalLight2 { get; }


こういうのは本当は配列にすべきなのですが、そうなっていないのは
BasicEffectが実はきたならしいHLSLのラッパー
であることと関係があるのでしょう。

BasicDirectionalLightを使うにはまずEnabledプロパティをtrueにセットします。

public bool Enabled { get; set; }

これをtrueにしないと、そもそも光が当たりません。
真っ黒になります。
(BasicEffect.LightingEnabledとは違います。)

directionalLightWithoutEnabled.JPG

その後、BasicDirectionalLight.Directionプロパティで光の方向を決めます。

public Vector3 Direction { get; set; }

光の色を決めるのがBasicDirectionalLight.DiffuseColorプロパティと
BasicDirectionalLight.SpecularColorプロパティです。
(この2つは色を表すプロパティですが、
型がColorではなくVector3になっています。
これはBasicEffectが実はきたならしいHLSLのラッパーで
あることと関係があるのでしょう。)


public Vector3 DiffuseColor { get; set; }
public Vector3 SpecularColor { get; set; }


どうして光の色を決めるプロパティが2つもあるのかというと、
この2つは性質が違うからです。
DiffuseColorは乱反射による色ですが、
SpecularColorは鏡面反射による色です。
この2つを合成した色が実際には表示されます。

この違いをはっきりさせるには、片方だけセットしてみるといいでしょう。

乱反射(Diffuse)

まず、DiffuseColor(乱反射)だけセットするとこうなります。
directionalLightDiffuse.JPG

全体的に暗めでのっぺりしています。
どこも同じような明るさです。
この明るさは面に当たる光の角度に依存しています。

diffuse90.JPG90度(三角形に平行:横からの光)diffuse75.JPG75度
diffuse60.JPG60度diffuse30.JPG30度
diffuse15.JPG15度diffuse0.JPG0度(三角形に垂直:まっすぐな光)

面に垂直に光が当たるようになるほど明るくなるのがわかると思います。
これは赤道直下が暑くて北極や南極が寒くなる理由と同じです。(赤道は緯度0度で、北極点は北緯90度です。)

鏡面反射(Specular)

次に、SpecularColor(鏡面反射)だけセットするとこうなります。

directionalLightSpecular.JPG

左側がぼんやりと明るく、右側は暗くなっています。
この明るくなっている部分は鏡に映った太陽とでも思えばいいでしょう。

実際、三角形を動かしていくとこうなります。
directionalLightSpecularMoved3.JPGdirectionalLightSpecularMoved2.JPGdirectionalLightSpecularMoved1.JPG


directionalLightSpecularMoved0.5f.JPGdirectionalLightSpecular.JPG

光の方向と面とカメラの位置によって明るさが変わります。
カメラの位置によって明るさが変わるというのは乱反射ではありえないことです。
こういうのは鏡面反射でのみおこります。
ちょうどメガネがキラリと光るようなものです。

最終的には、この2つの効果が組み合わさります。
そうすることによって、そこそこ自然な描画が出来るのです。
環境光(Ambient Color)

このサンプルでは使いませんでしたが、環境光というものもあります。
これは、光がどのような角度で当たっても、
カメラがどのような方向から見ても、
同じような明るさになります。

どういういうことかというと、環境光とは周囲を反射しまくった微妙な光のことなのですが、
それをわざわざ計算してシミュレートするのはとても莫大な計算が必要になるため、
とりあえず「どんなときにも当たる光」ということにしているのです。

これを表すプロパティは、BasicEffect.AmbientColorです。

public Vector3 AmbientLightColor { get; set; }

拍手[0回]


"Orcas"とC# 3.0 その3 暗黙的に型付けされたローカル変数

C# 3.0ではローカル変数の宣言時に型を指定しなくて良いのだそうです。
class Program
{
    static void Main()
    {
        var id = 1;  //Int32
        System.Console.WriteLine(id);  //1
    }
}

これは、コンパイラが初期化の仕方から自動的に型を推定してくれるからで、
推定しようがない場合はコンパイルエラーになります。
(別に動的型付け言語になったわけではありません)

Compile Error!
class Program
{
    static void Main()
    {
        var id;
        id = 1;
        System.Console.WriteLine(id);
    }
}

これによって何が良くなったかと言うと、1つには多分変更のしやすさでしょう。
コードコンプリートにあったように、IDを数から文字列に変更したくなった場合、
これだと変更するのは一ヶ所で済みます。
(この例よりも複雑な場合でも、少なくとも変更すべき場所が減るはずです)

class Program
{
    static void Main()
    {
        var id = "1";  //String
        System.Console.WriteLine(id);  //1
    }
}

もし明示的に型を指定してしまえば、その部分もintから
stringへ変更しなければなりません。
暗黙的に型付けしてしまえばその必要はないということです。
より変更しやすくなった、アジャイル向けな機能といったところでしょうか。

もう少し重要な理由としてC# 3.0の匿名型が関係しているのでしょう。
using System;

class Program
{
    static void Main()
    {
        var book = new { 
           Name = "利己的な遺伝子",
           Price = 2940 };
        Console.WriteLine("{0} ¥{1}", book.Name, book.Price);
    }
}

驚くべきことに型を即興で作ることが出来ます。
そしてその型には名前がありません。
(もちろん共通中間言語レベルではあるようですが)

このようなことをすると、varが必要不可欠になります。
型の名前がわからないので指定しようが無いからです。

この匿名型はLinqで結果を手っ取り早くまとめるのに使われるようです。

拍手[0回]


"Orcas"とC# 3.0 その2 拡張メソッド

C# 3.0 には拡張メソッドなるものがあるそうで、
あるクラスのメソッドを後から増やせるそうです。
using System;

class Person { public int Age { get; set; } }

static class PersonExtension
{
    public static bool IsAdult(this Person person) {
        return person.Age >= 20;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person child = new Person { Age = 10 };
        Person father = new Person { Age = 35 };
        Console.WriteLine(child.IsAdult()); //False
        Console.WriteLine(father.IsAdult()); //True 
    }
}

一見したところこれはクラスの凝集性を破壊するだけ
のように思えます。
別々に書く意味は全くありません。
PersonクラスにIsAdultメソッドも書くべきです。
実際、公式でもあまり軽々しく使うなと言われているようです。

ではなぜ拡張メソッドが存在するかと言うと、
1つにはインターフェースが実装しやすくなる
と言うのがあるようです。

using System;
using System.IO;

interface IPerson { int Age { get; set; } }
class Person : IPerson { public int Age { get; set; } }
class PersonFile : IPerson
{
    public int Age
    {
        get { return Int32.Parse(File.ReadAllText("person.txt")); }
        set { File.WriteAllText("person.txt", value.ToString()); }
    }
}

static class PersonExtension
{
    public static bool IsAdult(this IPerson person)
    {
        return person.Age >= 20;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person child = new Person { Age = 10 };
        PersonFile father = new PersonFile { Age = 35 };
        Console.WriteLine(child.IsAdult());
        Console.WriteLine(father.IsAdult());
    }
}
とりあえずPersonクラスに似た、PersonFileクラスを作りました。
メモリではなくファイルに年齢を格納します。
(ここは本当はファイルではなくネットワークに接続する
Proxyパターンにするべきなのでしょうけど長くなると困りますからね。)

もしPersonのインターフェースを作った場合、
それを使ってIsAdult()を呼べるべきです。

しかしそうすると、もし拡張メソッドを使わなかったら、
全ての実装したクラスでも
Age >= 20;
とやらなければなりません。

新たにクラスを1つ作るたびに重複が多くなってしまいます。

拡張メソッドを使えばその問題が解決されます。
Age >= 20;
は一ヶ所、拡張メソッドの中に集中します。
すばらしい。

実際の.netライブラリではオブジェクトのコレクション(?)
を表すIEnumerableインターフェースが拡張されています。

IEnumerableはオブジェクトを列挙する能力しかありませんが、
System.Linq.Enumerableによってさまざまな便利な能力を
付与されています。

拍手[1回]


"Orcas"とC# 3.0 その1 自動実装プロパティ

遅ればせながら次期Visual Studio、
"Orcas" Express Editionをインストールしてあそんでみました。

インストールしてさっそく起動してみると、
・・・特に2005と変わらないように見えます。
orcasStartPage.JPG
しいて違うところをあげるならタブの色がよりLunaっぽく、
かっこよくなっているということくらいでしょうか。

とりあえずプロジェクトを作って何かやってみることにしました。
orcasNewProject.JPG
当たり前ですが作れるプロジェクトの種類が2005より少ないようですね。
ともかく、一番単純な、コンソールプログラムで遊ぶことにしました。

今回の目玉はLinqらしいので
おいしいものは最後に取っておくと言う意味で
まずは地味な機能を使うことにします。
なんでもC# 3.0ではプロパティの宣言が
簡単になったとの事ですので。(自動プロパティ)

using System; struct Vector2 { public float X { get; set; } public float Y { get; set; } } class Program { static void Main(string[] args) { Vector2 vector = new Vector2(); vector.X = 0; vector.Y = 1; Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }

結果はこうです:

x = 0, y = 1

Rubyのように、プロパティを宣言するだけで、
変数の宣言の必要は無いらしいです。
それにしてもこのプロパティの書き方はインターフェースで
プロパティを宣言するやり方に似ています。(伏線)

次にコンストラクタを書いてみました。
わざわざ2行使ってプロパティに値を代入するのは
面倒に思えたからです。
しかし、失敗しました

Compile Error!

using System; struct Vector2 { public float X { get; set; } public float Y { get; set; } public Vector2(float x, float y) { this.X = x; this.Y = y; } } class Program { static void Main(string[] args) { Vector2 vector = new Vector2(0, 1); Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
"The 'this' object cannot be used before all of its fields are assigned to"
とでました。他にもそれに関連したエラーが2つ。
どうやらコンストラクタ中でプロパティに代入してはいけないようです。

「だったら一体どうやって初期化するんだ」という気になります。
調べてみると、オブジェクトを初期化する特別な方法も出たそうで、
それを使えばよさそうです。
using System; struct Vector2 { public float X { get; set; } public float Y { get; set; } } class Program { static void Main(string[] args) { Vector2 vector = new Vector2 { X = 0, Y = 1 }; Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
すばらしい・・・。

ものすごく簡潔です。
引数をとるコンストラクタを書く必要がありません。

もしかするとWindows Presentation Foundation(WPF)で
引数をコンストラクタにもつクラスが少なかったのは
これへの布石だったのかもしれません。
単に怠慢でコンストラクタを用意しなかったのではなく、
必要なかったから用意しなかっただけなのかもしれませんね。
引数(?)の意味もより明確になっています。

なんとなくCωの初期化の仕方を思い出します。

次に気になるのは「読み取り専用のプロパティを作れるのかどうか」
ということです。

そこで確かめるために次のようなコードを書いてみましたが、
失敗です。

Compile Error!
using System; struct Vector2 { public float X { get; } public float Y { get; } public static Vector2 Create(float x, float y) { return new Vector2 { X = x, Y = y }; } } class Program { static void Main(string[] args) { Vector2 vector = Vector2.Create(0, 1); Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
"'Vector2.X.get' must declare a body because it is not marked abstract or extern.
 Automatically implemented properties must define both get and set accessors."

・・・だそうです。どうもabstractやexternのプロパティと勘違いされたようです。
実際書き方は同じですし。
と言うか書き方が同じと言うより意味的に同じなのかもしれません。
エラーメッセージによると、自動プロパティは
自動的に実装される」と言う意味らしいからです。

ネーミングの由来は納得しましたが
だからと言って問題が解決したわけではありません。
やはり調べてみると、盲点でした、setの前にprivateをつけるらしいです。
using System; struct Vector2 { public float X { get; private set; } public float Y { get; private set; } public static Vector2 Create(float x, float y) { return new Vector2 { X = x, Y = y }; } } class Program { static void Main(string[] args) { Vector2 vector = Vector2.Create(0, 1); Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
この方法はプロパティを手動で実装するときと同じですが、
その場合はたいていprotected set...となります。
まさかprivate setになるとは・・・完全に盲点でした。

しかしおかしな話です。
上のブログでも言われていますが、private setくらい
自動的に補完して欲しいものです。
setできないのならプロパティに意味なんて無いわけですから。
(そしてこの疑問に対する回答もやはり要領を得ません。)

将来は改善されるといいのですが・・・

拍手[0回]