こんにちは、Habr!
私は3年目にD言語に出会いましたが、1年後の本Alexandrescuが出版されたときに使用することにしました。 すぐに研究室の学期論文を書くことを引き受けました。 主な問題は、必要なライブラリ(グラフィック、便利な数学)の不足、またはそれらの不便なインストールでした。 現在、多くの変更が行われ、ライブラリが作成され(
gfm 、
dlib 、
dlanguiなど)、ダブが登場しています。 この投稿では、これらのライブラリの1つを紹介します。 彼女の名前は
DESです。 彼女は研究所の業績から育ったので、おそらく誰かが勉強したり、言語を学ぶきっかけになったりするのに役立つでしょう。
画面に一種の抽象的なメッシュを描く簡単なアプリケーションを書きましょう。

通常行われる最初のことは、必要な設定でdub.jsonを作成することです。
非表示のテキスト{ "name": "example", "targetPath": "bin", "targetType": "executable", "sourcePaths": [ "src" ], "dependencies": { "des": ">=1.3.0" } }
ダブの詳細については、
こちらをご覧ください 。
いくつかのフォルダーを作成します。
- src-ソースコードはここにあります
- データ-シェーダー付きのファイルがあります
ソースコードを見てみましょう。
Src / main.dファイル import des.app; // SDL: , , , , GL import window; // void main() { auto app = new DesApp; // , , , app.addWindow({ return new MainWindow; }); // while( app.isRuning ) app.step(); // , }
DesAppクラスは、SDL、GLを初期化し、SDLイベントをウィンドウにリダイレクトし、ウィンドウ自体を保存します。
さらに興味深いことに移りましょう。
Src / window.dファイル module window; import des.app; import draw; // Sphere import camera; // MCamera // class MainWindow : DesWindow { protected: MCamera cam; // Sphere obj; // , /+ , DesApp OpenGL : , GL, +/ override void prepare() { cam = new MCamera; obj = newEMM!Sphere( 1, 12, 12 ); // // , connect( draw, { obj.draw( cam ); } ); connect( key, &keyControl ); // connect( mouse, &(cam.mouseReaction) ); // connect( event.resized, (ivec2 sz) { cam.ratio = cast(float)sz.w / sz.h; } ); } // void keyControl( in KeyboardEvent ke ) { if( ke.scan == ke.Scan.ESCAPE ) app.quit(); // DesApp, } public: // title , , this() { super( "example", ivec2(800,600), false ); } }
奇妙なデザインから説明を始めましょう。
obj = newEMM!Sphere( 1, 12, 12 )
そして、遠くから始めましょう。 D言語には、ガベージコレクタに関連する不快な機能があります。デストラクタでメモリを管理することはできません。 そして時々私はしたい。 そして、デストラクタ自体は明示的に呼び出されません。 EMM-ExternalMemoryManagerの略-外部メモリ管理。 これは、ライブラリ内のほとんどすべてのクラスの基本的なインターフェイスです。 外部メモリとは、GCで初期化を解除するのが難しいオブジェクトを指します。 たとえば、これらはOpenGLバッファー、SDLウィンドウへのポインターなどです。 ExternalMemoryManagerは、このインターフェイスを実装する他の子オブジェクトを格納できます。 destroyメソッド内のすべてのsalt。すべての子に対してselfDestroyおよびdestroyを呼び出します。 オブジェクトの所有権の特定のモデルが判明します。親が初期化解除されると、すべての子オブジェクトが初期化解除されます。 fooの子として新しく作成されたbarオブジェクトを追加するには、次のコードを記述する必要があります。
auto bar = new Bar; foo.registerChildEMM( bar );
この状況はしばしば発生するため、削減のために、これをExternalMemoryManagerインターフェイスメソッドに含めることにしました。
auto bar = foo.newEMM!Bar();
newEMMメソッドは、作成されるクラスと、メソッド内のクラスコンストラクターに渡される引数のリストによってパラメーター化されます。その後、新しいものが登録され、子として登録されます。
信号に移りましょう。 DesWindowはDesObjectの子孫であり、connectメソッドが宣言されています。 各DesObjectは、SlotHandlerのような概念の管理者です。 デリゲートからSlotオブジェクトを作成するとき、SlotHandlerも渡されますが、それなしでは不可能です。 そして、すでにSlotオブジェクトは登録のためにシグナルに転送されています。 「配列にデリゲートを書き込むだけではどうしてですか?」という疑問が生じる場合があります。 このような複雑さは、将来の利便性のためにアーキテクチャに導入されています。 単純に配列にデリゲートを書き込むと、すべてが正常になり、コンテキストへのリンクが保存され、GCオブジェクトは影響を受けませんが、これは別の状況です。 ExternalMemoryManagerを実装するオブジェクトも存在する場合がありますが、destroy()の呼び出し後、そのメソッドのいずれかの呼び出しは適切でなくなります。 たとえば、この時点でOpenGLバッファーを解放でき、信号のデリゲートが書き込まれます。 良くない。 SlotHandlerは、ExternalMemoryManagerを介したコンテキストの擬人化です。 DesObjectの子であり、削除されると、接続されているすべてのシグナルとの接続を切断するので、心配する必要はありません。 シグナルにはconnectメソッドもあり、このように書くことができます
draw.connect( this.newSlot( { obj.draw( cam ); } );
いずれにしても、デリゲートコンテキストの有効性について責任を負うことができる人が必要です。
ivec2はVectorの単なるエイリアスです!(2、int、 "xy | wh | uv")...
次はsrc / draw.dファイルです
彼はすでに少しボリュームがあります module draw; import std.math; import des.math.linear; // , import des.util.stdext.algorithm; // amap array( map ... ) import des.util.helpers; // appPath -- import des.space; // , SpaceNode, import des.gl; // OpenGL class Sphere : GLSimpleObject, SpaceNode { /+ SpaceNode self_mtr matrix +/ mixin SpaceNodeHelper; protected: // GLBuffer pos, ind; void prepareBuffers() { // auto loc = shader.getAttribLocations( "pos" ); // createArrayBuffer createIndexBuffer GLSimpleObject, GLObject pos = createArrayBuffer; // ind = createIndexBuffer; // // setAttribPointer( pos, loc[0], 3, GLType.FLOAT ); // vec3[] buf; vec3 sp( vec2 a, float R ) { return spheric(a) * R; } buf ~= amap!(a=>a+vec3(0,0,0))( amap!(a=>sp(a,R))( planeCoord( uivec2( resU, resV/2 ), vec2(0,PI*2), vec2(PI,PI/2) ) ) ); buf ~= amap!(a=>a-vec3(0,0,0))( amap!(a=>sp(a,R*0.9))( planeCoord( uivec2( resU, resV/2 ), vec2(0,PI*2), vec2(PI/2,PI) ) ) ); // pos.setData( buf ); // ind.setData( triangleStripPlaneIndex( uivec2( resU+1, resV+2 ), uint.max ) ); } // uint resU, resV; float R; public: this( float r, uint u, uint v ) { R = r; resU = u; resV = v; import std.file; super( newEMM!CommonShaderProgram( // parseShaderSource( // 1 readText( // appPath( "..", "data", "object.glsl" ) // )))); prepareBuffers(); } void draw( Camera cam ) { // shader.setUniform!col4( "col", col4(1,0,0,1) ); glEnable( GL_PRIMITIVE_RESTART ); glPrimitiveRestartIndex(uint.max); glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); // shader.setUniform!mat4( "prj", cam.projection.matrix * cam.resolve(this) ); // drawElements( DrawMode.TRIANGLE_STRIP ); glDisable( GL_PRIMITIVE_RESTART ); } } // vec2[] planeCoord( uivec2 res, vec2 x_size=vec2(0,1), vec2 y_size=vec2(0,1) ) { vec2[] ret; float sx = (x_size[1] - x_size[0]) / res.u; float sy = (y_size[1] - y_size[0]) / res.v; foreach( y; 0 .. res.y+1 ) foreach( x; 0 .. res.x+1 ) ret ~= vec2( x_size[0] + sx * x, y_size[0] + sy * y ); return ret; } vec3 spheric( in vec2 c ) pure { return vec3( cos(cx) * sin(cy), sin(cx) * sin(cy), cos(cy) ); } uint[] triangleStripPlaneIndex( uivec2 res, uint term ) { uint[] ret; foreach( y; 0 .. res.y-1 ) { ret ~= [ y*res.x, (y+1)*res.x ]; foreach( x; 1 .. res.x ) ret ~= [ y*res.x+x, (y+1)*res.x+x ]; ret ~= term; } return ret; }
そして、ファイルデータ/ object.glsl
フリルなし //### vert #version 330 in vec4 pos; uniform mat4 prj; void main() { gl_Position = prj * pos; } //### frag #version 330 uniform vec4 col; out vec4 color; void main() { color = col; }
カメラとSpaceNodeについて少し。 OpenGLバージョン3までは、マトリックススタック、個別のビュー、モデルマトリックスの概念がありました。 必要に応じて同じスタックを作成できますが、ここではすべてが少し単純です。 各SpaceNodeには、親SpaceNodeへのポインターと、ローカル座標系から親への変換マトリックスが含まれています。 親SpaceNodeはnullの場合があります。 カメラもSpaceNodeですが、いくつかの追加フィールドとメソッドがあります。 主な違いは、空間ノードを受け入れ、オブジェクトの座標系からカメラの座標系への変換マトリックスを返す解決メソッドの可用性です。 このために、別個のリゾルバークラスが使用されます。 実際、SpaceNodeオブジェクトから他のオブジェクトを見るために使用できます。
そして最後のsrc / camera.d module camera; import std.math; import des.space; import des.app; class MCamera : SimpleCamera { protected: vec3 orb; vec2 rot; float rotate_coef = 80.0f; float offset_coef = 50.0f; float y_angle_limit = PI_2 - 0.01; public: this() { super(); orb = vec3( 5, 1, 3 ); look_tr.target = vec3(0,0,0); look_tr.up = vec3(0,0,1); near = 0.001; updatePos(); } // void mouseReaction( in MouseEvent ev ) { // if( ev.type == MouseEvent.Type.WHEEL ) moveFront( -ev.whe.y * 0.1 ); if( ev.type == ev.Type.MOTION ) { // if( ev.isPressed( ev.Button.LEFT ) ) { auto frel = vec2( ev.rel ) * vec2(-1,1); auto angle = frel / rotate_coef; addRotate( angle ); } // if( ev.isPressed( ev.Button.MIDDLE ) ) { auto frel = vec2( ev.rel ) * vec2(-1,1); auto offset = frel / offset_coef * sqrt( orb.len ); moveCenter( offset ); } } } protected: void moveFront( float dist ) { orb += orb * dist; if( orb.len2 < 1 ) orb = orb.e; updatePos(); } void addRotate( in vec2 angle ) { rot = normRotate( rot + angle ); orb = vec3( cos(rot.x) * cos(rot.y), sin(rot.x) * cos(rot.y), sin(rot.y) ) * orb.len; updatePos(); } void moveCenter( in vec2 offset ) { auto lo = (look_tr.matrix * vec4(offset,0,0)).xyz; look_tr.target += lo; updatePos(); } void updatePos() { pos = orb + look_tr.target; } vec2 normRotate( in vec2 r ) { vec2 ret = r; if( ret.y > y_angle_limit ) ret.y = y_angle_limit; if( ret.y < -y_angle_limit ) ret.y = -y_angle_limit; return ret; } }
それは基本的にそれです。 ダブビルドを実行し、binフォルダーにバイナリを取得します。
この記事にはほとんど情報がないことは明らかなので、興味のある人はドキュメントを収集することをお勧めします。
harved-modプロジェクトのドキュメントに焦点を当てたので、組み込みのdmd関数を使用してドックを生成したい場合、結果は失望します。
UNIXシステムの場合は、〜/ .dub / packages / des-1.3.0フォルダーに移動し、path / to / harbored-mod / bin / hmodを実行して、お気に入りのブラウザーでdoc / index.htmlを開きます。 また、descoreのことも忘れないでください。descoreはdesの隣にあるはずです。ドキュメントは同じ方法で収集されます。
線形代数からロギングとローカリゼーションのシステムに至るまで、多くの興味深いことがあります。
ちなみに、この例はdes- / example / object_drawフォルダーにあります。
これが誰かに役立つことを願っています、あなたの注意に感謝します。