1 /+
2    Vasaro Copyright © 2018 Andrea Fontana
3    This file is part of Vasaro.
4 
5    Vasaro is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    Vasaro is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with Vasaro.  If not, see <http://www.gnu.org/licenses/>.
17 +/
18 
19 module viewer;
20 
21 import generator;
22 
23 import std.math : abs, sqrt, tan, PI;
24 import std.algorithm : max, min;
25 
26 import derelict.opengl;
27 import derelict.sdl2.sdl;
28 
29 public:
30 
31 struct VaseModel
32 {
33    GLuint 	vertexVbo; 
34    GLuint 	vertexNormalsVbo; 
35 
36    float[]  vertex;
37    float[]  vertexNormals;
38 }
39 
40 __gshared VaseModel[2]  model;
41 
42 void start()
43 {
44    if (status.actual == ViewerStatus.STARTED)
45       return;
46 
47    status.requested = ViewerStatus.STARTED;
48 
49    if (renderWindow == null) initRenderingWindow();
50 }
51 
52 void stop() { status.requested = ViewerStatus.STOPPED; }
53 void clear() { hasModel = false; }
54 bool isRunning() { return !(status.requested == ViewerStatus.STOPPED && status.actual == ViewerStatus.STOPPED); }
55 
56 void uninit()
57 {
58    if (context && renderWindow)
59    {
60       SDL_GL_DeleteContext(context);
61       SDL_DestroyWindow(renderWindow);
62       SDL_Quit();
63 
64       debug
65       {
66          import std.stdio : writeln;
67          writeln("SDL cleared.");
68       }
69    }
70 }
71 
72 void setIcon(char* data, int width, int height)
73 {
74    SDL_Surface* icon = SDL_CreateRGBSurfaceFrom(data, width, height, 32, width*4, 0x000000FF, 0x0000FF00, 0x00FF0000,0xFF000000);
75    SDL_SetWindowIcon(renderWindow, icon);
76    SDL_FreeSurface(icon);
77 }
78 
79 private:
80 
81 SDL_Window     *renderWindow  = null;
82 SDL_GLContext  context        = null;
83 
84 enum ViewerStatus
85 {
86    STARTED = 0,
87    STOPPED
88 }
89 
90 struct ViewerStatusSync
91 {
92    ViewerStatus requested  = ViewerStatus.STOPPED;
93    ViewerStatus actual     = ViewerStatus.STOPPED;
94 }
95 
96 __gshared ViewerStatusSync status;
97 __gshared bool             hasModel = false;
98 
99 // Mixing-in opengl functions declarations
100 mixin glFreeFuncs!(GLVersion.gl21, true);
101 
102 // Camera/Rendering settings
103 float    cameraRotationX = 0;
104 float    cameraRotationY = 0;
105 bool     autoRotate = true;
106 float    lastSign = 1;
107 size_t   lastRendered = -1;
108 float    currentSpeed = 1;
109 int      currentZoom = 0;
110 
111 void setupScene(int w, int h)
112 {
113 	glMatrixMode(GL_PROJECTION);
114    glLoadIdentity();
115 
116 	int width = w, height = h;
117 	double aspect_root = sqrt(width * 1.0f / height);
118 	double nearest = 0.125; 
119 	
120    float DEG2RAD = PI / 180;
121 
122    float front = 10.0f;
123    float back = 500.0f;
124    float fovY = 80.0f;
125    float tangent = tan(fovY/2 * DEG2RAD); 
126    float fheight = front * tangent; 
127    float fwidth = fheight * width/height; 
128 
129    glFrustum(-fwidth, fwidth, -fheight, fheight, front, back);
130 
131 
132 	float[] light_ambient = [ 1.0, 1.0, 1.0, 0.5 ];
133    float[] light_diffuse = [ 1.0, 1.0, 1.0, 0.5 ];
134    float[] light_specular = [ 0.6, 0.6, 0.6, 0.6 ];
135    float[] light_position = [ 1.0, 1.0, 100.0, 0.0 ];
136 
137    // Put lights into from POV
138    glMatrixMode(GL_MODELVIEW);
139 	glLoadIdentity();
140 	   
141    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient.ptr);
142    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse.ptr);
143    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular.ptr);
144    glLightfv(GL_LIGHT0, GL_POSITION, light_position.ptr);
145 }
146 
147 private void createWindow()
148 {
149    if (context) SDL_GL_DeleteContext(context);
150    if (renderWindow) SDL_DestroyWindow(renderWindow);
151 
152    renderWindow = SDL_CreateWindow(
153       "Vasaro Rendering", 
154       SDL_WINDOWPOS_CENTERED,
155       SDL_WINDOWPOS_CENTERED,
156       512,
157       512,
158       SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SKIP_TASKBAR | SDL_WINDOW_MOUSE_CAPTURE
159    );
160 
161    context = SDL_GL_CreateContext(renderWindow);
162 
163    // Some una-tantum settings for opengl rendering   
164    glMatrixMode(GL_PROJECTION);
165    glClearDepth(1.0f);
166    glEnable (GL_DEPTH_TEST);
167    glDepthFunc(GL_LEQUAL);
168    glClearColor(0.1f, 0.18f, 0.24f, 1);  
169    
170    glEnable(GL_LIGHTING);
171 	glEnable(GL_CULL_FACE);
172    glClearDepth(1.0f);
173    glDepthFunc(GL_LEQUAL);
174    glFrontFace(GL_CCW);
175    glCullFace(GL_BACK);  
176 
177    glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT);
178    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
179    glEnable(GL_BLEND);
180    glEnable(GL_MULTISAMPLE);  
181    
182 	glEnable (GL_LIGHTING);
183 	glEnable (GL_LIGHT0);
184 
185    SDL_CaptureMouse(SDL_TRUE);
186 
187    status.actual = ViewerStatus.STARTED;
188 
189 }
190 
191 private void initRenderingWindow()
192 {
193    DerelictGL3.load();
194    DerelictSDL2.load();
195 
196    SDL_Init(SDL_INIT_VIDEO);
197    
198    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
199    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
200    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
201    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
202    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
203 
204    createWindow();
205 
206    DerelictGL3.reload(); 
207 }
208 
209 
210 
211 public bool renderFrame()
212 {
213    float[3] clear_color = [0.10f, 0.18f, 0.24f];
214    
215    int windowWidth;
216    int windowHeight;
217 
218    // Init scene
219    {
220       SDL_GetWindowSize(renderWindow, &windowWidth, &windowHeight);
221       glViewport(0,0,windowWidth,windowHeight);
222       glClear(GL_COLOR_BUFFER_BIT);
223       setupScene(windowWidth, windowHeight);
224    }
225 
226    // Hide request received
227    if (status.actual == ViewerStatus.STARTED && status.requested == ViewerStatus.STOPPED) 
228    {
229       SDL_HideWindow(renderWindow);
230       status.actual = ViewerStatus.STOPPED;
231       hasModel = false;
232    }
233 
234    // Show request received
235    else if (status.actual == ViewerStatus.STOPPED && status.requested == ViewerStatus.STARTED) 
236    {
237       //SDL_ShowWindow(renderWindow);
238       createWindow();
239       status.actual = ViewerStatus.STARTED;
240    }
241 
242    // Render frame if requested
243    else if (status.actual == ViewerStatus.STARTED)
244    {
245       auto lastGenerated = lastGenerated();
246 
247       // Just started. Still no model available.
248       if (lastGenerated != 0)
249       {
250          // New model to show?
251          if (!hasModel || (lastRendered != lastGenerated))
252          {            
253             // Buffers binding
254             if (model[currentModel].vertex.length > 0) {
255                glGenBuffers(1, &model[currentModel].vertexVbo);
256                glBindBuffer(GL_ARRAY_BUFFER, model[currentModel].vertexVbo);
257                glBufferData(GL_ARRAY_BUFFER, model[currentModel].vertex.length*float.sizeof, model[currentModel].vertex.ptr, GL_STATIC_DRAW);
258                glBindBuffer(GL_ARRAY_BUFFER, 0); /* release binding */
259 
260                glGenBuffers(1, &model[currentModel].vertexNormalsVbo);
261                glBindBuffer(GL_ARRAY_BUFFER, model[currentModel].vertexNormalsVbo);
262                glBufferData(GL_ARRAY_BUFFER, model[currentModel].vertexNormals.length*float.sizeof, model[currentModel].vertexNormals.ptr, GL_STATIC_DRAW);
263                glBindBuffer(GL_ARRAY_BUFFER, 0); /* release binding */
264             }
265 
266             hasModel = true;
267             lastRendered = lastGenerated;
268          }
269 
270          if(hasModel) renderVase();
271       }
272    }
273    
274    // Handling window event
275    SDL_Event event;
276    while (SDL_PollEvent(&event))
277    {
278       if (
279          // Hide window on closing request
280          event.type == SDL_QUIT 
281          
282          // If window is minimieze
283          || (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_MINIMIZED)
284 
285          // Also on escape-button press
286          || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
287       ) stop();
288 
289       // Moving mouse with left button down
290       else if (event.type == SDL_MOUSEMOTION && (event.motion.state & SDL_BUTTON_LMASK) == SDL_BUTTON_LMASK)
291       {
292          
293          cameraRotationX += 2*PI/360*event.motion.yrel*10;
294          cameraRotationX = max(-50, min(50, cameraRotationX));
295          
296          cameraRotationY += 2*PI/360*event.motion.xrel*10;
297          currentSpeed = 2*PI/360*event.motion.xrel*10;
298          
299          if (currentSpeed == 0) currentSpeed = 0.1f;
300          lastSign = event.motion.xrel >= 0?1:-1;
301       }
302 
303       // I stop autorotation when user has left button down!
304       else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT)
305       {
306          autoRotate = false;
307          currentSpeed = lastSign*0.1;
308       }
309       else if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) autoRotate = true;
310       
311       // Zoom
312       else if (event.type == SDL_MOUSEWHEEL)
313       {
314          currentZoom += event.wheel.y;
315          currentZoom = max(-20, min(5, currentZoom));
316       }
317    }
318 
319    // Send back buffer to window
320    SDL_GL_SwapWindow(renderWindow);
321    return true;
322 }
323 
324 private void renderVase()
325 {
326    // Clear the whole scene
327    glClear(GL_DEPTH_BUFFER_BIT);
328 	glMatrixMode(GL_MODELVIEW);
329 	glLoadIdentity();
330 
331    // Zoom
332    glTranslatef(0.0f, -50.0 + currentZoom * 3 , -150.0 + currentZoom * 5);
333    
334    // Rotation
335    glRotatef(cameraRotationX, 1.0, 0.0, 0.0);
336  	glRotatef(cameraRotationY, 0.0, 1.0, 0.0);
337         
338    if (autoRotate)
339    {
340       cameraRotationY = (cameraRotationY + currentSpeed);
341 
342       // Accelerate / Decelerate
343       if (abs(currentSpeed) > 1) 
344       {
345          currentSpeed *= 0.97;
346          if (abs(currentSpeed) < 1) currentSpeed  = 1* lastSign;
347       }
348       else if(abs(currentSpeed) < 1)
349       {
350          currentSpeed *= 1.05;
351          if (abs(currentSpeed) > 1) currentSpeed  = 1 * lastSign;
352       }
353    }
354    if (cameraRotationY > 360) cameraRotationY -= 360;
355    if (cameraRotationY < 0) cameraRotationY += 360;
356 
357    
358    // Gummy material
359    float[] no_mat = [0.0f, 0.0f, 0.0f, 1.0f];
360    float[] mat_ambient = [0.05f, 0.05f, 0.05f, 1.0f];
361    float[] mat_ambient_color = [0.8f, 0.8f, 0.2f, 1.0f];
362    float[] mat_diffuse = [0.8f, 0.7f, 0.1f, 1.0f];
363    float[] mat_specular = [1.0f, 1.0f, 1.0f, 1.0f];
364    float high_shininess = 80.0f;
365    
366    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat.ptr);
367    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse.ptr);
368    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular.ptr);
369    glMaterialf(GL_FRONT, GL_SHININESS, high_shininess);
370    glMaterialfv(GL_FRONT, GL_EMISSION, mat_ambient.ptr);
371 
372    // Push vertices and triangles
373 	glEnableClientState(GL_VERTEX_ARRAY);
374 	glBindBuffer(GL_ARRAY_BUFFER, model[currentModel].vertexVbo);
375 	glVertexPointer(3, GL_FLOAT, 0, cast(void*)(0));
376 
377 	glEnableClientState(GL_NORMAL_ARRAY);
378 	glBindBuffer(GL_ARRAY_BUFFER, model[currentModel].vertexNormalsVbo);
379 	glNormalPointer(GL_FLOAT, 0, cast(void*)(0));
380 
381 	glBindBuffer(GL_ARRAY_BUFFER, 0); 
382 	glDrawArrays(GL_TRIANGLES, 0, cast(int)(model[currentModel].vertex.length/3));
383 	glDisableClientState(GL_VERTEX_ARRAY);   
384 }
385