1 //
2 // Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
3 //
4 // This software is provided 'as-is', without any express or implied
5 // warranty.  In no event will the authors be held liable for any damages
6 // arising from the use of this software.
7 // Permission is granted to anyone to use this software for any purpose,
8 // including commercial applications, and to alter it and redistribute it
9 // freely, subject to the following restrictions:
10 // 1. The origin of this software must not be misrepresented; you must not
11 //    claim that you wrote the original software. If you use this software
12 //    in a product, an acknowledgment in the product documentation would be
13 //    appreciated but is not required.
14 // 2. Altered source versions must be plainly marked as such, and must not be
15 //    misrepresented as being the original software.
16 // 3. This notice may not be removed or altered from any source distribution.
17 //
18 
19 #define _USE_MATH_DEFINES
20 #include <cmath>
21 #include <cstdio>
22 #include "imgui.h"
23 #include "SDL.h"
24 #include "SDL_opengl.h"
25 
26 // Some math headers don't have PI defined.
27 static const float PI = 3.14159265f;
28 
29 void imguifree(void* ptr, void* userptr);
30 void* imguimalloc(size_t size, void* userptr);
31 
32 #define STBTT_malloc(x,y)    imguimalloc(x,y)
33 #define STBTT_free(x,y)      imguifree(x,y)
34 #define STB_TRUETYPE_IMPLEMENTATION
35 #include "stb_truetype.h"
36 
imguifree(void * ptr,void *)37 void imguifree(void* ptr, void* /*userptr*/)
38 {
39 	free(ptr);
40 }
41 
imguimalloc(size_t size,void *)42 void* imguimalloc(size_t size, void* /*userptr*/)
43 {
44 	return malloc(size);
45 }
46 
47 static const unsigned TEMP_COORD_COUNT = 100;
48 static float g_tempCoords[TEMP_COORD_COUNT*2];
49 static float g_tempNormals[TEMP_COORD_COUNT*2];
50 
51 static const int CIRCLE_VERTS = 8*4;
52 static float g_circleVerts[CIRCLE_VERTS*2];
53 
54 static stbtt_bakedchar g_cdata[96]; // ASCII 32..126 is 95 glyphs
55 static GLuint g_ftex = 0;
56 
RGBA(unsigned char r,unsigned char g,unsigned char b,unsigned char a)57 inline unsigned int RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
58 {
59 	return (r) | (g << 8) | (b << 16) | (a << 24);
60 }
61 
drawPolygon(const float * coords,unsigned numCoords,float r,unsigned int col)62 static void drawPolygon(const float* coords, unsigned numCoords, float r, unsigned int col)
63 {
64 	if (numCoords > TEMP_COORD_COUNT) numCoords = TEMP_COORD_COUNT;
65 
66 	for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++)
67 	{
68 		const float* v0 = &coords[j*2];
69 		const float* v1 = &coords[i*2];
70 		float dx = v1[0] - v0[0];
71 		float dy = v1[1] - v0[1];
72 		float d = sqrtf(dx*dx+dy*dy);
73 		if (d > 0)
74 		{
75 			d = 1.0f/d;
76 			dx *= d;
77 			dy *= d;
78 		}
79 		g_tempNormals[j*2+0] = dy;
80 		g_tempNormals[j*2+1] = -dx;
81 	}
82 
83 	for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++)
84 	{
85 		float dlx0 = g_tempNormals[j*2+0];
86 		float dly0 = g_tempNormals[j*2+1];
87 		float dlx1 = g_tempNormals[i*2+0];
88 		float dly1 = g_tempNormals[i*2+1];
89 		float dmx = (dlx0 + dlx1) * 0.5f;
90 		float dmy = (dly0 + dly1) * 0.5f;
91 		float	dmr2 = dmx*dmx + dmy*dmy;
92 		if (dmr2 > 0.000001f)
93 		{
94 			float	scale = 1.0f / dmr2;
95 			if (scale > 10.0f) scale = 10.0f;
96 			dmx *= scale;
97 			dmy *= scale;
98 		}
99 		g_tempCoords[i*2+0] = coords[i*2+0]+dmx*r;
100 		g_tempCoords[i*2+1] = coords[i*2+1]+dmy*r;
101 	}
102 
103 	unsigned int colTrans = RGBA(col&0xff, (col>>8)&0xff, (col>>16)&0xff, 0);
104 
105 	glBegin(GL_TRIANGLES);
106 
107 	glColor4ubv((GLubyte*)&col);
108 
109 	for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++)
110 	{
111 		glVertex2fv(&coords[i*2]);
112 		glVertex2fv(&coords[j*2]);
113 		glColor4ubv((GLubyte*)&colTrans);
114 		glVertex2fv(&g_tempCoords[j*2]);
115 
116 		glVertex2fv(&g_tempCoords[j*2]);
117 		glVertex2fv(&g_tempCoords[i*2]);
118 
119 		glColor4ubv((GLubyte*)&col);
120 		glVertex2fv(&coords[i*2]);
121 	}
122 
123 	glColor4ubv((GLubyte*)&col);
124 	for (unsigned i = 2; i < numCoords; ++i)
125 	{
126 		glVertex2fv(&coords[0]);
127 		glVertex2fv(&coords[(i-1)*2]);
128 		glVertex2fv(&coords[i*2]);
129 	}
130 
131 	glEnd();
132 }
133 
drawRect(float x,float y,float w,float h,float fth,unsigned int col)134 static void drawRect(float x, float y, float w, float h, float fth, unsigned int col)
135 {
136 	float verts[4*2] =
137 	{
138 		x+0.5f, y+0.5f,
139 		x+w-0.5f, y+0.5f,
140 		x+w-0.5f, y+h-0.5f,
141 		x+0.5f, y+h-0.5f,
142 	};
143 	drawPolygon(verts, 4, fth, col);
144 }
145 
146 /*
147 static void drawEllipse(float x, float y, float w, float h, float fth, unsigned int col)
148 {
149 	float verts[CIRCLE_VERTS*2];
150 	const float* cverts = g_circleVerts;
151 	float* v = verts;
152 
153 	for (int i = 0; i < CIRCLE_VERTS; ++i)
154 	{
155 		*v++ = x + cverts[i*2]*w;
156 		*v++ = y + cverts[i*2+1]*h;
157 	}
158 
159 	drawPolygon(verts, CIRCLE_VERTS, fth, col);
160 }
161 */
162 
drawRoundedRect(float x,float y,float w,float h,float r,float fth,unsigned int col)163 static void drawRoundedRect(float x, float y, float w, float h, float r, float fth, unsigned int col)
164 {
165 	const unsigned n = CIRCLE_VERTS/4;
166 	float verts[(n+1)*4*2];
167 	const float* cverts = g_circleVerts;
168 	float* v = verts;
169 
170 	for (unsigned i = 0; i <= n; ++i)
171 	{
172 		*v++ = x+w-r + cverts[i*2]*r;
173 		*v++ = y+h-r + cverts[i*2+1]*r;
174 	}
175 
176 	for (unsigned i = n; i <= n*2; ++i)
177 	{
178 		*v++ = x+r + cverts[i*2]*r;
179 		*v++ = y+h-r + cverts[i*2+1]*r;
180 	}
181 
182 	for (unsigned i = n*2; i <= n*3; ++i)
183 	{
184 		*v++ = x+r + cverts[i*2]*r;
185 		*v++ = y+r + cverts[i*2+1]*r;
186 	}
187 
188 	for (unsigned i = n*3; i < n*4; ++i)
189 	{
190 		*v++ = x+w-r + cverts[i*2]*r;
191 		*v++ = y+r + cverts[i*2+1]*r;
192 	}
193 	*v++ = x+w-r + cverts[0]*r;
194 	*v++ = y+r + cverts[1]*r;
195 
196 	drawPolygon(verts, (n+1)*4, fth, col);
197 }
198 
199 
drawLine(float x0,float y0,float x1,float y1,float r,float fth,unsigned int col)200 static void drawLine(float x0, float y0, float x1, float y1, float r, float fth, unsigned int col)
201 {
202 	float dx = x1-x0;
203 	float dy = y1-y0;
204 	float d = sqrtf(dx*dx+dy*dy);
205 	if (d > 0.0001f)
206 	{
207 		d = 1.0f/d;
208 		dx *= d;
209 		dy *= d;
210 	}
211 	float nx = dy;
212 	float ny = -dx;
213 	float verts[4*2];
214 	r -= fth;
215 	r *= 0.5f;
216 	if (r < 0.01f) r = 0.01f;
217 	dx *= r;
218 	dy *= r;
219 	nx *= r;
220 	ny *= r;
221 
222 	verts[0] = x0-dx-nx;
223 	verts[1] = y0-dy-ny;
224 
225 	verts[2] = x0-dx+nx;
226 	verts[3] = y0-dy+ny;
227 
228 	verts[4] = x1+dx+nx;
229 	verts[5] = y1+dy+ny;
230 
231 	verts[6] = x1+dx-nx;
232 	verts[7] = y1+dy-ny;
233 
234 	drawPolygon(verts, 4, fth, col);
235 }
236 
237 
imguiRenderGLInit(const char * fontpath)238 bool imguiRenderGLInit(const char* fontpath)
239 {
240 	for (int i = 0; i < CIRCLE_VERTS; ++i)
241 	{
242 		float a = (float)i/(float)CIRCLE_VERTS * PI*2;
243 		g_circleVerts[i*2+0] = cosf(a);
244 		g_circleVerts[i*2+1] = sinf(a);
245 	}
246 
247 	// Load font.
248 	FILE* fp = fopen(fontpath, "rb");
249 	if (!fp) return false;
250 	if (fseek(fp, 0, SEEK_END) != 0)
251 	{
252 		fclose(fp);
253 		return false;
254 	}
255 	long size = ftell(fp);
256 	if (size < 0)
257 	{
258 		fclose(fp);
259 		return false;
260 	}
261 	if (fseek(fp, 0, SEEK_SET) != 0)
262 	{
263 		fclose(fp);
264 		return false;
265 	}
266 
267 	unsigned char* ttfBuffer = (unsigned char*)malloc(size);
268 	if (!ttfBuffer)
269 	{
270 		fclose(fp);
271 		return false;
272 	}
273 
274 	size_t readLen = fread(ttfBuffer, 1, size, fp);
275 	fclose(fp);
276 	if (readLen != static_cast<size_t>(size))
277 	{
278 		free(ttfBuffer);
279 		return false;
280 	}
281 
282 	fp = 0;
283 
284 	unsigned char* bmap = (unsigned char*)malloc(512*512);
285 	if (!bmap)
286 	{
287 		free(ttfBuffer);
288 		return false;
289 	}
290 
291 	stbtt_BakeFontBitmap(ttfBuffer,0, 15.0f, bmap,512,512, 32,96, g_cdata);
292 
293 	// can free ttf_buffer at this point
294 	glGenTextures(1, &g_ftex);
295 	glBindTexture(GL_TEXTURE_2D, g_ftex);
296 	glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bmap);
297 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
298 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
299 
300 	free(ttfBuffer);
301 	free(bmap);
302 
303 	return true;
304 }
305 
imguiRenderGLDestroy()306 void imguiRenderGLDestroy()
307 {
308 	if (g_ftex)
309 	{
310 		glDeleteTextures(1, &g_ftex);
311 		g_ftex = 0;
312 	}
313 }
314 
getBakedQuad(stbtt_bakedchar * chardata,int pw,int ph,int char_index,float * xpos,float * ypos,stbtt_aligned_quad * q)315 static void getBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index,
316 						 float *xpos, float *ypos, stbtt_aligned_quad *q)
317 {
318 	stbtt_bakedchar *b = chardata + char_index;
319 	int round_x = STBTT_ifloor(*xpos + b->xoff);
320 	int round_y = STBTT_ifloor(*ypos - b->yoff);
321 
322 	q->x0 = (float)round_x;
323 	q->y0 = (float)round_y;
324 	q->x1 = (float)round_x + b->x1 - b->x0;
325 	q->y1 = (float)round_y - b->y1 + b->y0;
326 
327 	q->s0 = b->x0 / (float)pw;
328 	q->t0 = b->y0 / (float)pw;
329 	q->s1 = b->x1 / (float)ph;
330 	q->t1 = b->y1 / (float)ph;
331 
332 	*xpos += b->xadvance;
333 }
334 
335 static const float g_tabStops[4] = {150, 210, 270, 330};
336 
getTextLength(stbtt_bakedchar * chardata,const char * text)337 static float getTextLength(stbtt_bakedchar *chardata, const char* text)
338 {
339 	float xpos = 0;
340 	float len = 0;
341 	while (*text)
342 	{
343 		int c = (unsigned char)*text;
344 		if (c == '\t')
345 		{
346 			for (int i = 0; i < 4; ++i)
347 			{
348 				if (xpos < g_tabStops[i])
349 				{
350 					xpos = g_tabStops[i];
351 					break;
352 				}
353 			}
354 		}
355 		else if (c >= 32 && c < 128)
356 		{
357 			stbtt_bakedchar *b = chardata + c-32;
358 			int round_x = STBTT_ifloor((xpos + b->xoff) + 0.5);
359 			len = round_x + b->x1 - b->x0 + 0.5f;
360 			xpos += b->xadvance;
361 		}
362 		++text;
363 	}
364 	return len;
365 }
366 
drawText(float x,float y,const char * text,int align,unsigned int col)367 static void drawText(float x, float y, const char *text, int align, unsigned int col)
368 {
369 	if (!g_ftex) return;
370 	if (!text) return;
371 
372 	if (align == IMGUI_ALIGN_CENTER)
373 		x -= getTextLength(g_cdata, text)/2;
374 	else if (align == IMGUI_ALIGN_RIGHT)
375 		x -= getTextLength(g_cdata, text);
376 
377 	glColor4ub(col&0xff, (col>>8)&0xff, (col>>16)&0xff, (col>>24)&0xff);
378 
379 	glEnable(GL_TEXTURE_2D);
380 
381 	// assume orthographic projection with units = screen pixels, origin at top left
382 	glBindTexture(GL_TEXTURE_2D, g_ftex);
383 
384 	glBegin(GL_TRIANGLES);
385 
386 	const float ox = x;
387 
388 	while (*text)
389 	{
390 		int c = (unsigned char)*text;
391 		if (c == '\t')
392 		{
393 			for (int i = 0; i < 4; ++i)
394 			{
395 				if (x < g_tabStops[i]+ox)
396 				{
397 					x = g_tabStops[i]+ox;
398 					break;
399 				}
400 			}
401 		}
402 		else if (c >= 32 && c < 128)
403 		{
404 			stbtt_aligned_quad q;
405 			getBakedQuad(g_cdata, 512,512, c-32, &x,&y,&q);
406 
407 			glTexCoord2f(q.s0, q.t0);
408 			glVertex2f(q.x0, q.y0);
409 			glTexCoord2f(q.s1, q.t1);
410 			glVertex2f(q.x1, q.y1);
411 			glTexCoord2f(q.s1, q.t0);
412 			glVertex2f(q.x1, q.y0);
413 
414 			glTexCoord2f(q.s0, q.t0);
415 			glVertex2f(q.x0, q.y0);
416 			glTexCoord2f(q.s0, q.t1);
417 			glVertex2f(q.x0, q.y1);
418 			glTexCoord2f(q.s1, q.t1);
419 			glVertex2f(q.x1, q.y1);
420 		}
421 		++text;
422 	}
423 
424 	glEnd();
425 	glDisable(GL_TEXTURE_2D);
426 }
427 
428 
imguiRenderGLDraw()429 void imguiRenderGLDraw()
430 {
431 	const imguiGfxCmd* q = imguiGetRenderQueue();
432 	int nq = imguiGetRenderQueueSize();
433 
434 	const float s = 1.0f/8.0f;
435 
436 	glDisable(GL_SCISSOR_TEST);
437 	for (int i = 0; i < nq; ++i)
438 	{
439 		const imguiGfxCmd& cmd = q[i];
440 		if (cmd.type == IMGUI_GFXCMD_RECT)
441 		{
442 			if (cmd.rect.r == 0)
443 			{
444 				drawRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f,
445 						 (float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1,
446 						 1.0f, cmd.col);
447 			}
448 			else
449 			{
450 				drawRoundedRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f,
451 								(float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1,
452 								(float)cmd.rect.r*s, 1.0f, cmd.col);
453 			}
454 		}
455 		else if (cmd.type == IMGUI_GFXCMD_LINE)
456 		{
457 			drawLine(cmd.line.x0*s, cmd.line.y0*s, cmd.line.x1*s, cmd.line.y1*s, cmd.line.r*s, 1.0f, cmd.col);
458 		}
459 		else if (cmd.type == IMGUI_GFXCMD_TRIANGLE)
460 		{
461 			if (cmd.flags == 1)
462 			{
463 				const float verts[3*2] =
464 				{
465 					(float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f,
466 					(float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s/2-0.5f,
467 					(float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1,
468 				};
469 				drawPolygon(verts, 3, 1.0f, cmd.col);
470 			}
471 			if (cmd.flags == 2)
472 			{
473 				const float verts[3*2] =
474 				{
475 					(float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1,
476 					(float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s/2-0.5f, (float)cmd.rect.y*s+0.5f,
477 					(float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1,
478 				};
479 				drawPolygon(verts, 3, 1.0f, cmd.col);
480 			}
481 		}
482 		else if (cmd.type == IMGUI_GFXCMD_TEXT)
483 		{
484 			drawText(cmd.text.x, cmd.text.y, cmd.text.text, cmd.text.align, cmd.col);
485 		}
486 		else if (cmd.type == IMGUI_GFXCMD_SCISSOR)
487 		{
488 			if (cmd.flags)
489 			{
490 				glEnable(GL_SCISSOR_TEST);
491 				glScissor(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h);
492 			}
493 			else
494 			{
495 				glDisable(GL_SCISSOR_TEST);
496 			}
497 		}
498 	}
499 	glDisable(GL_SCISSOR_TEST);
500 }
501