1 module widgets.opengl;
2 
3 import bindbc.opengl;
4 import dlangui;
5 import dlangui.graphics.glsupport;
6 import dlangui.graphics.gldrawbuf;
7 
8 static if(ENABLE_OPENGL):
9 
10 class OpenGLExample : VerticalLayout {
11     this() {
12         super("OpenGLView");
13         layoutWidth = FILL_PARENT;
14         layoutHeight = FILL_PARENT;
15         alignment = Align.Center;
16         // add some UI on top of OpenGL drawable
17         Widget w = parseML(q{
18             VerticalLayout {
19                 alignment: center
20                 layoutWidth: fill; layoutHeight: fill
21                 // background for window - tiled texture
22                 backgroundImageId: "tx_fabric.tiled"
23                 VerticalLayout {
24                     // child widget - will draw using OpenGL here
25                     id: glView
26                     margins: 20
27                     padding: 20
28                     layoutWidth: fill; layoutHeight: fill
29 
30                     TextWidget { text: "DlangUI OpenGL custom drawable example"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" }
31 
32                     TextWidget { text: "Choose OpenGL drawable:" }
33                     VerticalLayout {
34                         RadioButton { id: rbExample1; text: "Shaders based example - Cube"; checked: true }
35                         RadioButton { id: rbExample2; text: "Legacy OpenGL API example - glxGears" }
36                     }
37 
38                     TextWidget { text: "Some controls to draw on top of OpenGL scene"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" }
39 
40                     // arrange controls as form - table with two columns
41                     TableLayout {
42                         colCount: 2
43                         TextWidget { text: "param 1" }
44                         EditLine { id: edit1; text: "some text" }
45                         TextWidget { text: "param 2" }
46                         EditLine { id: edit2; text: "some text for param2" }
47                         TextWidget { text: "some radio buttons" }
48                         // arrange some radio buttons vertically
49                         VerticalLayout {
50                             RadioButton { id: rb1; text: "Item 1" }
51                             RadioButton { id: rb2; text: "Item 2" }
52                             RadioButton { id: rb3; text: "Item 3" }
53                         }
54                         TextWidget { text: "and checkboxes" }
55                         // arrange some checkboxes horizontally
56                         HorizontalLayout {
57                             CheckBox { id: cb1; text: "checkbox 1" }
58                             CheckBox { id: cb2; text: "checkbox 2" }
59                         }
60                     }
61                     VSpacer { layoutWeight: 10 }
62                     HorizontalLayout {
63                         Button { id: btnOk; text: "Ok" }
64                         Button { id: btnCancel; text: "Cancel" }
65                     }
66                 }
67             }
68         });
69         // assign OpenGL drawable to child widget background
70         w.childById("glView").backgroundDrawable = DrawableRef(new OpenGLDrawable(&doDraw));
71 
72         w.childById("rbExample1").click = delegate(Widget w) {
73             _exampleIndex = 0; // new API
74             return true;
75         };
76         w.childById("rbExample2").click = delegate(Widget w) {
77             _exampleIndex = 1; // old API
78             return true;
79         };
80 
81         addChild(w);
82     }
83 
84     int _exampleIndex = 0;
85 
86     /// returns true is widget is being animated - need to call animate() and redraw
87     @property override bool animating() { return true; }
88     /// animates window; interval is time left from previous draw, in hnsecs (1/10000000 of second)
89     override void animate(long interval) {
90         if (_exampleIndex == 1) {
91             // animate legacy API example
92             // rotate gears
93             angle += interval * 0.000002f;
94         } else {
95             // TODO: some other animation for new API example
96             angle += interval * 0.000002f;
97         }
98         invalidate();
99     }
100 
101     /// this is OpenGLDrawableDelegate implementation
102     private void doDraw(Rect windowRect, Rect rc) {
103         if (!openglEnabled) {
104             Log.v("GlGears: OpenGL is disabled");
105             return;
106         }
107         bool canUseOldApi = !!glLightfv;
108         bool canUseNewApi = true;
109         if (_exampleIndex == 0 || !canUseOldApi)
110             drawUsingNewAPI(windowRect, rc);
111         else if (_exampleIndex == 1 || !canUseNewApi)
112             drawUsingOldAPI(windowRect, rc);
113     }
114 
115     /// Legacy API example (glBegin/glEnd) - glxGears
116     void drawUsingOldAPI(Rect windowRect, Rect rc) {
117         static bool _initCalled;
118         if (!_initCalled) {
119             Log.d("GlGears: calling init()");
120             _initCalled = true;
121             glxgears_init();
122         }
123         glxgears_reshape(rc);
124         glEnable(GL_LIGHTING);
125         glEnable(GL_LIGHT0);
126         glEnable(GL_DEPTH_TEST);
127         glxgears_draw();
128         glDisable(GL_LIGHTING);
129         glDisable(GL_LIGHT0);
130         glDisable(GL_DEPTH_TEST);
131     }
132 
133     ~this() {
134         if (_program)
135             destroy(_program);
136         if (_vao)
137             destroy(_vao);
138         if (_vbo)
139             destroy(_vbo);
140         if (_tx)
141             destroy(_tx);
142     }
143 
144     MyGLProgram _program;
145     GLTexture _tx;
146     VAO _vao;
147     VBO _vbo;
148 
149     /// New API example (OpenGL3+, shaders)
150     void drawUsingNewAPI(Rect windowRect, Rect rc) {
151         if (!_program) {
152             _program = new MyGLProgram;
153             _program.compile();
154             createMesh();
155             auto buf = _program.createBuffers(vertices, colors, texcoords);
156             _vao = buf[0];
157             _vbo = buf[1];
158         }
159         if (!_program.check())
160             return;
161         if (!_tx.isValid) {
162             Log.e("Invalid texture");
163             return;
164         }
165 
166         checkgl!glEnable(GL_CULL_FACE);
167         checkgl!glEnable(GL_DEPTH_TEST);
168         checkgl!glCullFace(GL_BACK);
169 
170         // ======== Projection Matrix ==================
171         mat4 projectionMatrix;
172         float aspectRatio = cast(float)rc.width / cast(float)rc.height;
173         projectionMatrix.setPerspective(45.0f, aspectRatio, 0.1f, 100.0f);
174 
175         // ======== View Matrix ==================
176         mat4 viewMatrix;
177         viewMatrix.translate(0, 0, -6);
178         viewMatrix.rotatex(-15.0f);
179         //viewMatrix.lookAt(vec3(-10, 0, 0), vec3(0, 0, 0), vec3(0, 1, 0));//translation(0.0f, 0.0f, 4.0f).rotatez(angle);
180 
181         // ======== Model Matrix ==================
182         mat4 modelMatrix;
183         modelMatrix.scale(1.5f);
184         modelMatrix.rotatez(30.0f + angle * 0.3456778);
185         modelMatrix.rotatey(angle);
186         modelMatrix.rotatez(angle * 1.98765f);
187 
188         // ======= PMV matrix =====================
189         mat4 projectionViewModelMatrix = projectionMatrix * viewMatrix * modelMatrix;
190 
191         _program.execute(_vao, cast(int)vertices.length / 3, _tx.texture, true, projectionViewModelMatrix.m);
192 
193         checkgl!glDisable(GL_CULL_FACE);
194         checkgl!glDisable(GL_DEPTH_TEST);
195     }
196 
197     // Cube mesh
198     float[] vertices;
199     float[] texcoords;
200     float[4*6*6] colors;
201     void createMesh() {
202         if (!_tx)
203             _tx = new GLTexture("crate");
204 
205         // define Cube mesh
206         auto p000 = [-1.0f, -1.0f, -1.0f];
207         auto p100 = [ 1.0f, -1.0f, -1.0f];
208         auto p010 = [-1.0f,  1.0f, -1.0f];
209         auto p110 = [ 1.0f,  1.0f, -1.0f];
210         auto p001 = [-1.0f, -1.0f,  1.0f];
211         auto p101 = [ 1.0f, -1.0f,  1.0f];
212         auto p011 = [-1.0f,  1.0f,  1.0f];
213         auto p111 = [ 1.0f,  1.0f,  1.0f];
214         vertices = p000 ~ p010 ~ p110 ~  p110 ~ p100 ~ p000 // front face
215                  ~ p101 ~ p111 ~ p011 ~  p011 ~ p001 ~ p101 // back face
216                  ~ p100 ~ p110 ~ p111 ~  p111 ~ p101 ~ p100 // right face
217                  ~ p001 ~ p011 ~ p010 ~  p010 ~ p000 ~ p001 // left face
218                  ~ p010 ~ p011 ~ p111 ~  p111 ~ p110 ~ p010 // top face
219                  ~ p001 ~ p000 ~ p100 ~  p100 ~ p101 ~ p001 // bottom face
220             ;
221         // texture coordinates
222         float[2] uv = _tx.uv;
223         float tx0 = 0.0f;
224         float tx1 = uv[0];
225         float ty0 = 0.0f;
226         float ty1 = uv[1];
227         float[12] facetx = [tx1, ty1, // triangle 1
228                             tx0, ty0,
229                             tx0, ty1,
230                             tx0, ty1, // triangle 2
231                             tx1, ty0,
232                             tx1, ty1];
233         texcoords = facetx ~ facetx ~ facetx ~ facetx ~ facetx ~ facetx;
234         // init with white color (1, 1, 1, 1)
235         foreach(ref cl; colors)
236             cl = 1.0f;
237     }
238 }
239 
240 // ====================================================================================
241 // Shaders based example
242 
243 // Simple texture + color shader
244 class MyGLProgram : GLProgram {
245     @property override string vertexSource() {
246         return q{
247             in vec4 vertex;
248             in vec4 colAttr;
249             in vec4 texCoord;
250             out vec4 col;
251             out vec4 texc;
252             uniform mat4 matrix;
253             void main(void)
254             {
255                 gl_Position = matrix * vertex;
256                 col = colAttr;
257                 texc = texCoord;
258             }
259         };
260 
261     }
262     @property override string fragmentSource() {
263         return q{
264             uniform sampler2D tex;
265             in vec4 col;
266             in vec4 texc;
267             out vec4 outColor;
268             void main(void)
269             {
270                 outColor = texture(tex, texc.st) * col;
271             }
272         };
273     }
274 
275     // attribute locations
276     protected GLint matrixLocation;
277     protected GLint vertexLocation;
278     protected GLint colAttrLocation;
279     protected GLint texCoordLocation;
280 
281     override bool initLocations() {
282         matrixLocation = getUniformLocation("matrix");
283         vertexLocation = getAttribLocation("vertex");
284         colAttrLocation = getAttribLocation("colAttr");
285         texCoordLocation = getAttribLocation("texCoord");
286         return matrixLocation >= 0 && vertexLocation >= 0 && colAttrLocation >= 0 && texCoordLocation >= 0;
287     }
288 
289     import std.typecons : Tuple, tuple;
290     Tuple!(VAO, VBO) createBuffers(float[] vertices, float[] colors, float[] texcoords) {
291 
292         VBO vbo = new VBO;
293         vbo.fill([vertices, colors, texcoords]);
294 
295         VAO vao = new VAO;
296         glVertexAttribPointer(vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, cast(void*) 0);
297         glVertexAttribPointer(colAttrLocation, 4, GL_FLOAT, GL_FALSE, 0, cast(void*) (vertices.length * vertices[0].sizeof));
298         glVertexAttribPointer(texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, cast(void*) (vertices.length * vertices[0].sizeof + colors.length * colors[0].sizeof));
299 
300         glEnableVertexAttribArray(vertexLocation);
301         glEnableVertexAttribArray(colAttrLocation);
302         glEnableVertexAttribArray(texCoordLocation);
303 
304         return tuple(vao, vbo);
305     }
306 
307     void execute(VAO vao, int vertsCount, Tex2D texture, bool linear, float[16] matrix) {
308 
309         bind();
310         checkgl!glUniformMatrix4fv(matrixLocation, 1, false, matrix.ptr);
311 
312         texture.setup();
313         texture.setSamplerParams(linear);
314 
315         vao.bind();
316         checkgl!glDrawArrays(GL_TRIANGLES, 0, vertsCount);
317 
318         texture.unbind();
319         unbind();
320     }
321 }
322 
323 
324 
325 
326 //=====================================================================================
327 // Legacy OpenGL API example
328 // GlxGears
329 //=====================================================================================
330 
331 import std.math;
332 static __gshared GLfloat view_rotx = 20.0, view_roty = 30.0, view_rotz = 0.0;
333 static __gshared GLint gear1, gear2, gear3;
334 static __gshared GLfloat angle = 0.0;
335 alias M_PI = std.math.PI;
336 
337 /*
338 *
339 *  Draw a gear wheel.  You'll probably want to call this function when
340 *  building a display list since we do a lot of trig here.
341 *
342 *  Input:  inner_radius - radius of hole at center
343 *          outer_radius - radius at center of teeth
344 *          width - width of gear
345 *          teeth - number of teeth
346 *          tooth_depth - depth of tooth
347 */
348 static void
349     gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
350             GLint teeth, GLfloat tooth_depth)
351 {
352     GLint i;
353     GLfloat r0, r1, r2;
354     GLfloat angle, da;
355     GLfloat u, v, len;
356 
357     r0 = inner_radius;
358     r1 = outer_radius - tooth_depth / 2.0;
359     r2 = outer_radius + tooth_depth / 2.0;
360 
361     da = 2.0 * M_PI / teeth / 4.0;
362 
363     glShadeModel(GL_FLAT);
364 
365     glNormal3f(0.0, 0.0, 1.0);
366 
367     /* draw front face */
368     glBegin(GL_QUAD_STRIP);
369     for (i = 0; i <= teeth; i++) {
370         angle = i * 2.0 * M_PI / teeth;
371         glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
372         glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
373         if (i < teeth) {
374             glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
375             glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
376                         width * 0.5);
377         }
378     }
379     glEnd();
380 
381     /* draw front sides of teeth */
382     glBegin(GL_QUADS);
383     da = 2.0 * M_PI / teeth / 4.0;
384     for (i = 0; i < teeth; i++) {
385         angle = i * 2.0 * M_PI / teeth;
386 
387         glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
388         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
389         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
390                     width * 0.5);
391         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
392                     width * 0.5);
393     }
394     glEnd();
395 
396     glNormal3f(0.0, 0.0, -1.0);
397 
398     /* draw back face */
399     glBegin(GL_QUAD_STRIP);
400     for (i = 0; i <= teeth; i++) {
401         angle = i * 2.0 * M_PI / teeth;
402         glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
403         glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
404         if (i < teeth) {
405             glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
406                         -width * 0.5);
407             glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
408         }
409     }
410     glEnd();
411 
412     /* draw back sides of teeth */
413     glBegin(GL_QUADS);
414     da = 2.0 * M_PI / teeth / 4.0;
415     for (i = 0; i < teeth; i++) {
416         angle = i * 2.0 * M_PI / teeth;
417 
418         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
419                     -width * 0.5);
420         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
421                     -width * 0.5);
422         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
423         glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
424     }
425     glEnd();
426 
427     /* draw outward faces of teeth */
428     glBegin(GL_QUAD_STRIP);
429     for (i = 0; i < teeth; i++) {
430         angle = i * 2.0 * M_PI / teeth;
431 
432         glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
433         glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
434         u = r2 * cos(angle + da) - r1 * cos(angle);
435         v = r2 * sin(angle + da) - r1 * sin(angle);
436         len = sqrt(u * u + v * v);
437         u /= len;
438         v /= len;
439         glNormal3f(v, -u, 0.0);
440         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
441         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
442         glNormal3f(cos(angle), sin(angle), 0.0);
443         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
444                     width * 0.5);
445         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
446                     -width * 0.5);
447         u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
448         v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
449         glNormal3f(v, -u, 0.0);
450         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
451                     width * 0.5);
452         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
453                     -width * 0.5);
454         glNormal3f(cos(angle), sin(angle), 0.0);
455     }
456 
457     glVertex3f(r1 * cos(0.0), r1 * sin(0.0), width * 0.5);
458     glVertex3f(r1 * cos(0.0), r1 * sin(0.0), -width * 0.5);
459 
460     glEnd();
461 
462     glShadeModel(GL_SMOOTH);
463 
464     /* draw inside radius cylinder */
465     glBegin(GL_QUAD_STRIP);
466     for (i = 0; i <= teeth; i++) {
467         angle = i * 2.0 * M_PI / teeth;
468         glNormal3f(-cos(angle), -sin(angle), 0.0);
469         glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
470         glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
471     }
472     glEnd();
473 }
474 
475 
476 static void glxgears_draw()
477 {
478     glPushMatrix();
479     glRotatef(view_rotx, 1.0, 0.0, 0.0);
480     glRotatef(view_roty, 0.0, 1.0, 0.0);
481     glRotatef(view_rotz, 0.0, 0.0, 1.0);
482 
483     glPushMatrix();
484     glTranslatef(-3.0, -2.0, 0.0);
485     glRotatef(angle, 0.0, 0.0, 1.0);
486     glCallList(gear1);
487     glPopMatrix();
488 
489     glPushMatrix();
490     glTranslatef(3.1, -2.0, 0.0);
491     glRotatef(-2.0 * angle - 9.0, 0.0, 0.0, 1.0);
492     glCallList(gear2);
493     glPopMatrix();
494 
495     glPushMatrix();
496     glTranslatef(-3.1, 4.2, 0.0);
497     glRotatef(-2.0 * angle - 25.0, 0.0, 0.0, 1.0);
498     glCallList(gear3);
499     glPopMatrix();
500 
501     glPopMatrix();
502 }
503 
504 
505 /* new window size or exposure */
506 static void
507     glxgears_reshape(Rect rc)
508 {
509     GLfloat h = cast(GLfloat) rc.height / cast(GLfloat) rc.width;
510     glMatrixMode(GL_PROJECTION);
511     glLoadIdentity();
512     glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
513     glMatrixMode(GL_MODELVIEW);
514     glLoadIdentity();
515     glTranslatef(0.0, 0.0, -40.0);
516 }
517 
518 
519 static void glxgears_init()
520 {
521     static GLfloat[4] pos = [ 5.0, 5.0, 10.0, 0.0 ];
522     static GLfloat[4] red = [ 0.8, 0.1, 0.0, 1.0 ];
523     static GLfloat[4] green = [ 0.0, 0.8, 0.2, 1.0 ];
524     static GLfloat[4] blue = [ 0.2, 0.2, 1.0, 1.0 ];
525 
526     glLightfv(GL_LIGHT0, GL_POSITION, pos.ptr);
527     glEnable(GL_CULL_FACE);
528     glEnable(GL_LIGHTING);
529     glEnable(GL_LIGHT0);
530     glEnable(GL_DEPTH_TEST);
531 
532     /* make the gears */
533     gear1 = glGenLists(1);
534     glNewList(gear1, GL_COMPILE);
535     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red.ptr);
536     gear(1.0, 4.0, 1.0, 20, 0.7);
537     glEndList();
538 
539     gear2 = glGenLists(1);
540     glNewList(gear2, GL_COMPILE);
541     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green.ptr);
542     gear(0.5, 2.0, 2.0, 10, 0.7);
543     glEndList();
544 
545     gear3 = glGenLists(1);
546     glNewList(gear3, GL_COMPILE);
547     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue.ptr);
548     gear(1.3, 2.0, 0.5, 10, 0.7);
549     glEndList();
550 
551     glEnable(GL_NORMALIZE);
552 }