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