1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 /*
24  * This file is based on, or a modified version of code from TinyGL (C) 1997-1998 Fabrice Bellard,
25  * which is licensed under the zlib-license (see LICENSE).
26  * It also has modifications by the ResidualVM-team, which are covered under the GPLv2 (or later).
27  */
28 
29 #include "graphics/tinygl/zgl.h"
30 
31 namespace TinyGL {
32 
33 // fill triangle profile
34 // #define TINYGL_PROFILE
35 
36 #define CLIP_XMIN   (1 << 0)
37 #define CLIP_XMAX   (1 << 1)
38 #define CLIP_YMIN   (1 << 2)
39 #define CLIP_YMAX   (1 << 3)
40 #define CLIP_ZMIN   (1 << 4)
41 #define CLIP_ZMAX   (1 << 5)
42 
gl_transform_to_viewport(GLContext * c,GLVertex * v)43 void gl_transform_to_viewport(GLContext *c, GLVertex *v) {
44 	float winv;
45 
46 	// coordinates
47 	winv = (float)(1.0 / v->pc.W);
48 	v->zp.x = (int)(v->pc.X * winv * c->viewport.scale.X + c->viewport.trans.X);
49 	v->zp.y = (int)(v->pc.Y * winv * c->viewport.scale.Y + c->viewport.trans.Y);
50 	v->zp.z = (int)(v->pc.Z * winv * c->viewport.scale.Z + c->viewport.trans.Z);
51 	// color
52 	v->zp.r = (int)(v->color.X * ZB_POINT_RED_MAX);
53 	v->zp.g = (int)(v->color.Y * ZB_POINT_GREEN_MAX);
54 	v->zp.b = (int)(v->color.Z * ZB_POINT_BLUE_MAX);
55 	v->zp.a = (int)(v->color.W * ZB_POINT_ALPHA_MAX);
56 
57 	// texture
58 	if (c->texture_2d_enabled) {
59 		v->zp.s = (int)(v->tex_coord.X * ZB_POINT_ST_MAX);
60 		v->zp.t = (int)(v->tex_coord.Y * ZB_POINT_ST_MAX);
61 	}
62 }
63 
gl_add_select1(GLContext * c,int z1,int z2,int z3)64 static void gl_add_select1(GLContext *c, int z1, int z2, int z3) {
65 	int min, max;
66 
67 	min = max = z1;
68 	if (z2 < min)
69 		min = z2;
70 	if (z3 < min)
71 		min = z3;
72 	if (z2 > max)
73 		max = z2;
74 	if (z3 > max)
75 		max = z3;
76 
77 	gl_add_select(c, 0xffffffff - min, 0xffffffff - max);
78 }
79 
80 // point
81 
gl_draw_point(GLContext * c,GLVertex * p0)82 void gl_draw_point(GLContext *c, GLVertex *p0) {
83 	if (p0->clip_code == 0) {
84 		if (c->render_mode == TGL_SELECT) {
85 			gl_add_select(c, p0->zp.z, p0->zp.z);
86 		} else {
87 			c->fb->plot(&p0->zp);
88 		}
89 	}
90 }
91 
92 // line
93 
interpolate_color(GLContext * c,GLVertex * q,GLVertex * p0,GLVertex * p1,float t)94 static inline void interpolate_color(GLContext *c, GLVertex *q, GLVertex *p0, GLVertex *p1, float t) {
95 	if (c->current_shade_model == TGL_SMOOTH)
96 		q->color = p0->color + (p1->color - p0->color) * t;
97 	else
98 		q->color = p0->color;
99 }
100 
interpolate(GLContext * c,GLVertex * q,GLVertex * p0,GLVertex * p1,float t)101 static inline void interpolate(GLContext *c, GLVertex *q, GLVertex *p0, GLVertex *p1, float t) {
102 	q->pc = p0->pc + (p1->pc - p0->pc) * t;
103 	interpolate_color(c, q, p0, p1, t);
104 }
105 
106 // Line Clipping
107 
108 // Line Clipping algorithm from 'Computer Graphics', Principles and
109 // Practice
ClipLine1(float denom,float num,float * tmin,float * tmax)110 static inline int ClipLine1(float denom, float num, float *tmin, float *tmax) {
111 	float t;
112 
113 	if (denom > 0) {
114 		t = num / denom;
115 		if (t > *tmax)
116 			return 0;
117 		if (t > *tmin)
118 			*tmin = t;
119 	} else if (denom < 0) {
120 		t = num / denom;
121 		if (t < *tmin)
122 			return 0;
123 		if (t < *tmax)
124 			*tmax = t;
125 	} else if (num > 0)
126 		return 0;
127 
128 	return 1;
129 }
130 
gl_draw_line(GLContext * c,GLVertex * p1,GLVertex * p2)131 void gl_draw_line(GLContext *c, GLVertex *p1, GLVertex *p2) {
132 	float dx, dy, dz, dw, x1, y1, z1, w1;
133 	float tmin, tmax;
134 	GLVertex q1, q2;
135 	int cc1, cc2;
136 
137 	cc1 = p1->clip_code;
138 	cc2 = p2->clip_code;
139 
140 	if ((cc1 | cc2) == 0) {
141 		if (c->render_mode == TGL_SELECT) {
142 			gl_add_select1(c, p1->zp.z, p2->zp.z, p2->zp.z);
143 		} else {
144 			if (c->depth_test)
145 				c->fb->fillLineZ(&p1->zp, &p2->zp);
146 			else
147 				c->fb->fillLine(&p1->zp, &p2->zp);
148 		}
149 	} else if ((cc1 & cc2) != 0) {
150 		return;
151 	} else {
152 		dx = p2->pc.X - p1->pc.X;
153 		dy = p2->pc.Y - p1->pc.Y;
154 		dz = p2->pc.Z - p1->pc.Z;
155 		dw = p2->pc.W - p1->pc.W;
156 		x1 = p1->pc.X;
157 		y1 = p1->pc.Y;
158 		z1 = p1->pc.Z;
159 		w1 = p1->pc.W;
160 
161 		tmin = 0;
162 		tmax = 1;
163 		if (ClipLine1(dx + dw, -x1 - w1, &tmin, &tmax) &&
164 				ClipLine1(-dx + dw, x1 - w1, &tmin, &tmax) &&
165 				ClipLine1(dy + dw, -y1 - w1, &tmin, &tmax) &&
166 				ClipLine1(-dy + dw, y1 - w1, &tmin, &tmax) &&
167 				ClipLine1(dz + dw, -z1 - w1, &tmin, &tmax) &&
168 				ClipLine1(-dz + dw, z1 - w1, &tmin, &tmax)) {
169 			interpolate(c, &q1, p1, p2, tmin);
170 			interpolate(c, &q2, p1, p2, tmax);
171 			gl_transform_to_viewport(c, &q1);
172 			gl_transform_to_viewport(c, &q2);
173 
174 			if (c->depth_test)
175 				c->fb->fillLineZ(&q1.zp, &q2.zp);
176 			else
177 				c->fb->fillLine(&q1.zp, &q2.zp);
178 		}
179 	}
180 }
181 
182 // triangle
183 
184 // Clipping
185 
186 // We clip the segment [a,b] against the 6 planes of the normal volume.
187 // We compute the point 'c' of intersection and the value of the parameter 't'
188 // of the intersection if x=a+t(b-a).
189 
190 #define clip_func(name, sign, dir, dir1, dir2) \
191 static float name(Vector4 *c, Vector4 *a, Vector4 *b) { \
192 	float t, dX, dY, dZ, dW, den;\
193 	dX = (b->X - a->X); \
194 	dY = (b->Y - a->Y); \
195 	dZ = (b->Z - a->Z); \
196 	dW = (b->W - a->W); \
197 	den = -(sign d ## dir) + dW; \
198 	if (den == 0) \
199 		t = 0; \
200 	else \
201 		t = (sign a->dir - a->W) / den; \
202 	c-> dir1 = (a->dir1 + t * d ## dir1); \
203 	c-> dir2 = (a->dir2 + t * d ## dir2); \
204 	c->W = (a->W + t * dW); \
205 	c-> dir = (sign c->W); \
206 	return t; \
207 }
208 
209 clip_func(clip_xmin, -, X, Y, Z)
210 clip_func(clip_xmax, +, X, Y, Z)
211 clip_func(clip_ymin, -, Y, X, Z)
212 clip_func(clip_ymax, +, Y, X, Z)
213 clip_func(clip_zmin, -, Z, X, Y)
214 clip_func(clip_zmax, +, Z, X, Y)
215 
216 float(*clip_proc[6])(Vector4 *, Vector4 *, Vector4 *) =  {
217 	clip_xmin, clip_xmax,
218 	clip_ymin, clip_ymax,
219 	clip_zmin, clip_zmax
220 };
221 
updateTmp(GLContext * c,GLVertex * q,GLVertex * p0,GLVertex * p1,float t)222 static inline void updateTmp(GLContext *c, GLVertex *q, GLVertex *p0, GLVertex *p1, float t) {
223 	interpolate_color(c, q, p0, p1, t);
224 
225 	if (c->texture_2d_enabled) {
226 		// NOTE: This could be implemented with operator overloading,
227 		// but i'm not 100% sure that we can completely disregard Z and W components so I'm leaving it like this for now.
228 		q->tex_coord.X = (p0->tex_coord.X + (p1->tex_coord.X - p0->tex_coord.X) * t);
229 		q->tex_coord.Y = (p0->tex_coord.Y + (p1->tex_coord.Y - p0->tex_coord.Y) * t);
230 	}
231 
232 	q->clip_code = gl_clipcode(q->pc.X, q->pc.Y, q->pc.Z, q->pc.W);
233 	if (q->clip_code == 0)
234 		gl_transform_to_viewport(c, q);
235 }
236 
237 static void gl_draw_triangle_clip(GLContext *c, GLVertex *p0, GLVertex *p1, GLVertex *p2, int clip_bit);
238 
gl_draw_triangle(GLContext * c,GLVertex * p0,GLVertex * p1,GLVertex * p2)239 void gl_draw_triangle(GLContext *c, GLVertex *p0, GLVertex *p1, GLVertex *p2) {
240 	int co, c_and, cc[3], front;
241 	float norm;
242 
243 	cc[0] = p0->clip_code;
244 	cc[1] = p1->clip_code;
245 	cc[2] = p2->clip_code;
246 
247 	co = cc[0] | cc[1] | cc[2];
248 
249 	// we handle the non clipped case here to go faster
250 	if (co == 0) {
251 		norm = (float)(p1->zp.x - p0->zp.x) * (float)(p2->zp.y - p0->zp.y) -
252 			   (float)(p2->zp.x - p0->zp.x) * (float)(p1->zp.y - p0->zp.y);
253 		if (norm == 0)
254 			return;
255 
256 		front = norm < 0.0;
257 		front = front ^ c->current_front_face;
258 
259 		// back face culling
260 		if (c->cull_face_enabled) {
261 			// most used case first */
262 			if (c->current_cull_face == TGL_BACK) {
263 				if (front == 0)
264 					return;
265 				c->draw_triangle_front(c, p0, p1, p2);
266 			} else if (c->current_cull_face == TGL_FRONT) {
267 				if (front != 0)
268 					return;
269 				c->draw_triangle_back(c, p0, p1, p2);
270 			} else {
271 				return;
272 			}
273 		} else {
274 			// no culling
275 			if (front) {
276 				c->draw_triangle_front(c, p0, p1, p2);
277 			} else {
278 				c->draw_triangle_back(c, p0, p1, p2);
279 			}
280 		}
281 	} else {
282 		c_and = cc[0] & cc[1] & cc[2];
283 		if (c_and == 0) {
284 			gl_draw_triangle_clip(c, p0, p1, p2, 0);
285 		}
286 	}
287 }
288 
gl_draw_triangle_clip(GLContext * c,GLVertex * p0,GLVertex * p1,GLVertex * p2,int clip_bit)289 static void gl_draw_triangle_clip(GLContext *c, GLVertex *p0, GLVertex *p1, GLVertex *p2, int clip_bit) {
290 	int co, c_and, co1, cc[3], edge_flag_tmp, clip_mask;
291 	GLVertex tmp1, tmp2, *q[3];
292 	float tt;
293 
294 	cc[0] = p0->clip_code;
295 	cc[1] = p1->clip_code;
296 	cc[2] = p2->clip_code;
297 
298 	co = cc[0] | cc[1] | cc[2];
299 	if (co == 0) {
300 		gl_draw_triangle(c, p0, p1, p2);
301 	} else {
302 		c_and = cc[0] & cc[1] & cc[2];
303 		// the triangle is completely outside
304 		if (c_and != 0)
305 			return;
306 
307 		// find the next direction to clip
308 		while (clip_bit < 6 && (co & (1 << clip_bit)) == 0) {
309 			clip_bit++;
310 		}
311 
312 		// this test can be true only in case of rounding errors
313 		if (clip_bit == 6) {
314 #if 0
315 			printf("Error:\n");
316 			printf("%f %f %f %f\n", p0->pc.X, p0->pc.Y, p0->pc.Z, p0->pc.W);
317 			printf("%f %f %f %f\n", p1->pc.X, p1->pc.Y, p1->pc.Z, p1->pc.W);
318 			printf("%f %f %f %f\n", p2->pc.X, p2->pc.Y, p2->pc.Z, p2->pc.W);
319 #endif
320 			return;
321 		}
322 
323 		clip_mask = 1 << clip_bit;
324 		co1 = (cc[0] ^ cc[1] ^ cc[2]) & clip_mask;
325 
326 		if (co1)  {
327 			// one point outside
328 			if (cc[0] & clip_mask) {
329 				q[0] = p0; q[1] = p1; q[2] = p2;
330 			} else if (cc[1] & clip_mask) {
331 				q[0] = p1; q[1] = p2; q[2] = p0;
332 			} else {
333 				q[0] = p2; q[1] = p0; q[2] = p1;
334 			}
335 
336 			tt = clip_proc[clip_bit](&tmp1.pc, &q[0]->pc, &q[1]->pc);
337 			updateTmp(c, &tmp1, q[0], q[1], tt);
338 
339 			tt = clip_proc[clip_bit](&tmp2.pc, &q[0]->pc, &q[2]->pc);
340 			updateTmp(c, &tmp2, q[0], q[2], tt);
341 
342 			tmp1.edge_flag = q[0]->edge_flag;
343 			edge_flag_tmp = q[2]->edge_flag;
344 			q[2]->edge_flag = 0;
345 			gl_draw_triangle_clip(c, &tmp1, q[1], q[2], clip_bit + 1);
346 
347 			tmp2.edge_flag = 1;
348 			tmp1.edge_flag = 0;
349 			q[2]->edge_flag = edge_flag_tmp;
350 			gl_draw_triangle_clip(c, &tmp2, &tmp1, q[2], clip_bit + 1);
351 		} else {
352 			// two points outside
353 			if ((cc[0] & clip_mask) == 0) {
354 				q[0] = p0; q[1] = p1; q[2] = p2;
355 			} else if ((cc[1] & clip_mask) == 0) {
356 				q[0] = p1; q[1] = p2; q[2] = p0;
357 			} else {
358 				q[0] = p2; q[1] = p0; q[2] = p1;
359 			}
360 
361 			tt = clip_proc[clip_bit](&tmp1.pc, &q[0]->pc, &q[1]->pc);
362 			updateTmp(c, &tmp1, q[0], q[1], tt);
363 
364 			tt = clip_proc[clip_bit](&tmp2.pc, &q[0]->pc, &q[2]->pc);
365 			updateTmp(c, &tmp2, q[0], q[2], tt);
366 
367 			tmp1.edge_flag = 1;
368 			tmp2.edge_flag = q[2]->edge_flag;
369 			gl_draw_triangle_clip(c, q[0], &tmp1, &tmp2, clip_bit + 1);
370 		}
371 	}
372 }
373 
gl_draw_triangle_select(GLContext * c,GLVertex * p0,GLVertex * p1,GLVertex * p2)374 void gl_draw_triangle_select(GLContext *c, GLVertex *p0, GLVertex *p1, GLVertex *p2) {
375 	gl_add_select1(c, p0->zp.z, p1->zp.z, p2->zp.z);
376 }
377 
378 #ifdef TINYGL_PROFILE
379 int count_triangles, count_triangles_textured, count_pixels;
380 #endif
381 
gl_draw_triangle_fill(GLContext * c,GLVertex * p0,GLVertex * p1,GLVertex * p2)382 void gl_draw_triangle_fill(GLContext *c, GLVertex *p0, GLVertex *p1, GLVertex *p2) {
383 #ifdef TINYGL_PROFILE
384 	{
385 		int norm;
386 		assert(p0->zp.x >= 0 && p0->zp.x < c->fb->xsize);
387 		assert(p0->zp.y >= 0 && p0->zp.y < c->fb->ysize);
388 		assert(p1->zp.x >= 0 && p1->zp.x < c->fb->xsize);
389 		assert(p1->zp.y >= 0 && p1->zp.y < c->fb->ysize);
390 		assert(p2->zp.x >= 0 && p2->zp.x < c->fb->xsize);
391 		assert(p2->zp.y >= 0 && p2->zp.y < c->fb->ysize);
392 
393 		norm = (p1->zp.x - p0->zp.x) * (p2->zp.y - p0->zp.y) -
394 				(p2->zp.x - p0->zp.x) * (p1->zp.y - p0->zp.y);
395 		count_pixels += abs(norm) / 2;
396 		count_triangles++;
397 	}
398 #endif
399 
400 	if (c->color_mask == 0) {
401 		// FIXME: Accept more than just 0 or 1.
402 		c->fb->fillTriangleDepthOnly(&p0->zp, &p1->zp, &p2->zp);
403 	}
404 	if (c->shadow_mode & 1) {
405 		assert(c->fb->shadow_mask_buf);
406 		c->fb->fillTriangleFlatShadowMask(&p0->zp, &p1->zp, &p2->zp);
407 	} else if (c->shadow_mode & 2) {
408 		assert(c->fb->shadow_mask_buf);
409 		c->fb->fillTriangleFlatShadow(&p0->zp, &p1->zp, &p2->zp);
410 	} else if (c->texture_2d_enabled) {
411 #ifdef TINYGL_PROFILE
412 		count_triangles_textured++;
413 #endif
414 		c->fb->setTexture(c->current_texture->images[0].pixmap);
415 		if (c->current_shade_model == TGL_SMOOTH) {
416 			c->fb->fillTriangleTextureMappingPerspectiveSmooth(&p0->zp, &p1->zp, &p2->zp);
417 		} else {
418 			c->fb->fillTriangleTextureMappingPerspectiveFlat(&p0->zp, &p1->zp, &p2->zp);
419 		}
420 	} else if (c->current_shade_model == TGL_SMOOTH) {
421 		c->fb->fillTriangleSmooth(&p0->zp, &p1->zp, &p2->zp);
422 	} else {
423 		c->fb->fillTriangleFlat(&p0->zp, &p1->zp, &p2->zp);
424 	}
425 }
426 
427 // Render a clipped triangle in line mode
428 
gl_draw_triangle_line(GLContext * c,GLVertex * p0,GLVertex * p1,GLVertex * p2)429 void gl_draw_triangle_line(GLContext *c, GLVertex *p0, GLVertex *p1, GLVertex *p2) {
430 	if (c->depth_test) {
431 		if (p0->edge_flag)
432 			c->fb->fillLineZ(&p0->zp, &p1->zp);
433 		if (p1->edge_flag)
434 			c->fb->fillLineZ(&p1->zp, &p2->zp);
435 		if (p2->edge_flag)
436 			c->fb->fillLineZ(&p2->zp, &p0->zp);
437 	} else {
438 		if (p0->edge_flag)
439 			c->fb->fillLine(&p0->zp, &p1->zp);
440 		if (p1->edge_flag)
441 			c->fb->fillLine(&p1->zp, &p2->zp);
442 		if (p2->edge_flag)
443 			c->fb->fillLine(&p2->zp, &p0->zp);
444 	}
445 }
446 
447 // Render a clipped triangle in point mode
gl_draw_triangle_point(GLContext * c,GLVertex * p0,GLVertex * p1,GLVertex * p2)448 void gl_draw_triangle_point(GLContext *c, GLVertex *p0, GLVertex *p1, GLVertex *p2) {
449 	if (p0->edge_flag)
450 		c->fb->plot(&p0->zp);
451 	if (p1->edge_flag)
452 		c->fb->plot(&p1->zp);
453 	if (p2->edge_flag)
454 		c->fb->plot(&p2->zp);
455 }
456 
457 } // end of namespace TinyGL
458