1/*
2
3	ChartRender.c
4
5	by Pierre Raynaud-Richard.
6
7	Copyright 1998 Be Incorporated, All Rights Reserved.
8
9*/
10
11/* This file has been designed to be easy to compile as a stand-alone
12   piece of code, allowing you to use advanced intel compiler, even
13   if they are compatible with the whole Be environment. To accomplish
14   that purpose, all declarations were concentrated in ChartRender.h
15   (see that header file for more infos). */
16#include "ChartRender.h"
17
18/* This table provide the horizontal and vertical offset of the matrix
19   of pixel used for drawing stars. This matrix is designed as follow:
20
21    --  [00] [01] [02] [03]  --
22   [04] [05] [06] [07] [08] [09]
23   [10] [11] [12] [13] [14] [15]
24   [16] [17] [18] [19] [20] [21]
25   [22] [23] [24] [25] [26] [27]
26    --  [28] [29] [30] [31]  --
27
28   The reference pixel is [12]. */
29int8	pattern_dh[32] = {
30		-1, 0, 1, 2,
31	-2, -1, 0, 1, 2, 3,
32	-2, -1, 0, 1, 2, 3,
33	-2, -1, 0, 1, 2, 3,
34	-2, -1, 0, 1, 2, 3,
35		-1, 0, 1, 2
36};
37
38int8	pattern_dv[32] = {
39		-2, -2, -2, -2,
40	-1, -1, -1, -1, -1, -1,
41	0,	0,	0,	0,	0,	0,
42	1,	1,	1,	1,	1,	1,
43	2,	2,	2,	2,	2,	2,
44		3,	3,	3,	3
45};
46
47/* Those table contains a preprocessed version of the 32 size of star,
48   represented in the 32 pixels matrix, by alpha-blending density [0 to 7].
49   Those matrix are stored in packed format, as a the list of all pixel
50   whose alpha-blending density is > 0. There is 4 cases because every
51   star can be aligned at half a pixel in both direction (we implement
52   sub-pixel precision and anti-aliasing to reduce the jittering). */
53static	uint8	pattern_list[4*LEVEL_COUNT][32];
54static	uint8	pattern_list_count[4*LEVEL_COUNT];
55static	uint8	pattern_color_offset[4*LEVEL_COUNT][32];
56/* this table store the alpha-blending level of the center pixel. This
57   is used for size so small that only the center pixel is lighted. */
58static	uint8	pixel_color_offset[LEVEL_COUNT];
59
60/* Those mask are use for fast clipping, to determine which of the 32
61   pixels of the standard star matrix are visible when coming closer
62   from a left, right, top or bottom clipping border. */
63static uint32	visible_mask_left[6] = {
64	0xffffffff,
65	0xffbefbef,
66	0xef3cf3ce,
67	0xce38e38c,
68	0x8c30c308,
69	0x08208200
70};
71
72static uint32	visible_mask_right[6] = {
73	0xffffffff,
74	0xf7df7dff,
75	0x73cf3cf7,
76	0x31c71c73,
77	0x10c30c31,
78	0x00410410,
79};
80
81static uint32	visible_mask_top[6] = {
82	0xffffffff,
83	0xfffffff0,
84	0xfffffc00,
85	0xffff0000,
86	0xffc00000,
87	0xf0000000
88};
89
90static uint32	visible_mask_bottom[6] = {
91	0xffffffff,
92	0x0fffffff,
93	0x003fffff,
94	0x0000ffff,
95	0x000003ff,
96	0x0000000f
97};
98
99/* Private functions used only internally. */
100bool		ProjectStar(star *s, geometry *geo);
101bool		CheckClipping(star *s, buffer *buf, bool reset_clipping);
102void		DrawStar(star *s, buffer *buf);
103void		EraseStar(star *s, buffer *buf);
104
105
106/* This function initialise the 32 sizes of anti-aliased star, each one
107   represented in 4 different half-pixel alignement :
108   x : -0.25, y : -0.25
109   x : +0.25, y : -0.25
110   x : -0.25, y : +0.25
111   x : +0.25, y : +0.25 */
112void InitPatterns()
113{
114	int32		i, j, k, count;
115	float		radius, x0, y0, x, y, dist, delta;
116	uint8		color;
117	uint8		*list, *color_offset;
118
119	/* do the 4 half-pixel alignement */
120	for (j=0; j<4; j++) {
121		if (j&1) x0 = 1.25;
122		else	 x0 = 0.75;
123		if (j&2) y0 = 1.25;
124		else	 y0 = 0.75;
125
126		/* do the 32 sizes */
127		for (i=0; i<LEVEL_COUNT; i++) {
128			radius = (float)(i+1) * (2.8/(float)LEVEL_COUNT);
129			count = 0;
130			list = pattern_list[j*LEVEL_COUNT + i];
131			color_offset = pattern_color_offset[j*LEVEL_COUNT + i];
132
133			/* scan the 32 pixels of the matrix */
134			for (k=0; k<32; k++) {
135				x = ((float)pattern_dh[k] + ROUNDING) - x0;
136				y = ((float)pattern_dv[k] + ROUNDING) - y0;
137
138				dist = sqrt(x*x + y*y);
139				/* process non source pixel */
140				if (dist > 0.5) {
141					delta = radius - dist + 0.5;
142					if (delta >= 1.0) {
143						*color_offset++ = 7;
144						*list++ = k;
145						count++;
146					}
147					else if (delta > 0.5) {
148						*color_offset++ = (uint8)(7.499 - 16.0 * (1.0 - delta) * (1.0 - delta) + ROUNDING);
149						*list++ = k;
150						count++;
151					}
152					else if (delta > 0) {
153						color = (uint8)(16.0 * delta * delta);
154						if (color > 0) {
155							*color_offset++ = color;
156							*list++ = k;
157							count++;
158						}
159					}
160				}
161				/* process source pixel (the one containing the center of the star) */
162				else {
163					if (radius < 0.25) {
164						color = (uint8)(32.0 * radius * radius + ROUNDING);
165						if (color == 0)
166							color++;
167					}
168					else if (radius < 0.75) {
169						delta = radius + 0.25;
170						color = (uint8)(7.499 - 22.0 * (1.0 - delta) * (1.0 - delta) + ROUNDING);
171					}
172					else
173						color = 7;
174					*color_offset++ = color;
175					*list++ = k;
176					count++;
177					pixel_color_offset[i] = color;
178				}
179			}
180			pattern_list_count[j*LEVEL_COUNT + i] = count;
181		}
182	}
183}
184
185/* Project a star (s) in the view space of the camera, as described by (geo).
186   Returns true if the star seems to be visible (in the pyramid of vision,
187   closer than the rear plan, farther than the front plan), or false if it's
188   clear that the star isnot visible. */
189bool ProjectStar(star *s, geometry *geo)
190{
191	int32		h_double, v_double, level;
192	float		x0, y0, z0, x, y, z, inv_z;
193
194	/* Calculate the coordinate of the star after doing the cycling operation
195	   that convert the cube of the starfield in a torus. This ensure that
196	   get the copy of the star that is the only one likely to be visible from
197	   the camera. */
198	x0 = s->x;
199	if (x0 < geo->cutx)
200		x0 += 1.0;
201	y0 = s->y;
202	if (y0 < geo->cuty)
203		y0 += 1.0;
204	z0 = s->z;
205	if (z0 < geo->cutz)
206		z0 += 1.0;
207	/* Translate the star relative to the position of the camera. */
208	x0 -= geo->x;
209	y0 -= geo->y;
210	z0 -= geo->z;
211
212	/* Calculate the z coordinate (depth) of the star in the camera referential. */
213	z = geo->m[0][2]*x0 + geo->m[1][2]*y0 + geo->m[2][2]*z0;
214
215	/* Do the rear and front plan clipping */
216	if ((z < geo->z_min) || (z > geo->z_max))
217		return false;
218
219	/* Calculate the x coordinate (horizontal) of the star in the camera referential. */
220	x = geo->m[0][0]*x0 + geo->m[1][0]*y0 + geo->m[2][0]*z0;
221
222	/* Do the left and right clipping based on the pyramid of vision. */
223	if ((x < geo->xz_min*z-BORDER_CLIPPING) || (x > geo->xz_max*z+BORDER_CLIPPING))
224		return false;
225
226	/* Calculate the y coordinate (vertical) of the star in the camera referential. */
227	y = geo->m[0][1]*x0 + geo->m[1][1]*y0 + geo->m[2][1]*z0;
228
229	/* Do the top and bottom clipping based on the pyramid of vision. */
230	if ((y < geo->yz_min*z-BORDER_CLIPPING) || (y > geo->yz_max*z+BORDER_CLIPPING))
231		return false;
232
233	/* Calculate the invert of z, used to project both H and V coordinate. Apply
234	   the zoom-factor at the same time. The zoom-factor was overscale by a factor
235	   of two in advance, for the half-pixel precision processing */
236	inv_z = geo->zoom_factor/z;
237
238	/* Calculate the double pixel coordinate in the buffer (in half-pixel). */
239	h_double = (int32)(x * inv_z + geo->offset_h);
240	v_double = (int32)(y * inv_z + geo->offset_v);
241
242	/* Calculate the light level of the star. We use that little weird function
243	   to a get faster gradient to black near the rear plan. */
244	level = (int32)(s->size * (inv_z * geo->z_max_square - z * geo->zoom_factor)) >> 8;
245	/* The light level can go higher that our max (saturation). */
246	if (level >= LEVEL_COUNT)
247		level = LEVEL_COUNT-1;
248
249	/* Get the real pixel coordinate in the buffer from the double coordinates */
250	s->h = h_double >> 1;
251	s->v = v_double >> 1;
252	/* Save the light level (used to recognize single pixel star) */
253	s->level = level;
254	/* switch between the 4 pattern table use for the 4 half-aligned. */
255	if ((h_double & 1) == 1) level += LEVEL_COUNT;
256	if ((v_double & 1) == 1) level += 2*LEVEL_COUNT;
257	s->pattern_level = level;
258	return true;
259}
260
261/* Once a star has been projected (using ProjectStar), we need to determine
262   which pixel of the star matrix are visible (if any). This depend of the
263   clipping of the specific buffer you're using. This function will do that
264   for the star (s), in the buffer (buf). It will return false if the star
265   is fully invisible, true if not. The flag reset_clipping is used to
266   reprocess the clipping from scratch, or to just cumulate the new clipping
267   to the last drawing clipping (this is needed when updating the clipping
268   of every stars after changing the clipping region of the buffer). */
269bool CheckClipping(star *s, buffer *buf, bool reset_clipping)
270{
271	int32			delta;
272	uint32			i, total_visible, tmp_visible;
273	clipping_rect	box;
274	clipping_rect	*r;
275
276	/* Simple case : the star is represented by only one pixel. */
277	if (pattern_list_count[s->pattern_level] == 1) {
278		/* if the pixel is not in the bounding box of the clipping region,
279		   the star is guarantee to be invisible. */
280		if ((s->h < buf->clip_bounds.left) ||
281			(s->h > buf->clip_bounds.right) ||
282			(s->v < buf->clip_bounds.top) ||
283			(s->v > buf->clip_bounds.bottom))
284			goto invisible;
285		/* if the clipping region contains only one rectangle, then it's
286		   equal to its bounding box, so no further test are needed. */
287		if (buf->clip_list_count == 1)
288			goto visible;
289		/* In the other case, we need to go through the list of rectangle
290		   of the clipping region and check if the pixel is in any of those */
291		r = buf->clip_list;
292		for (i=0; i<buf->clip_list_count; r++, i++)
293			if ((s->h >= r->left) &&
294				(s->h <= r->right) &&
295				(s->v >= r->top) &&
296				(s->v <= r->bottom))
297				goto visible;
298		/* The pixel is not visible. The star is marked as not drawn. */
299	invisible:
300		s->last_draw_offset = INVALID;
301		return false;
302	visible:
303		/* The pixel is visible. The offset at which the star should be draw is
304		   calculated and store for using by drawing (and erasing later). */
305		s->last_draw_offset = s->v * buf->bytes_per_row + s->h * buf->bytes_per_pixel;
306		return true;
307	}
308	/* Complex case : the star is represented by more than one pixel. */
309	else {
310		/* Calculate the box the bounding box of the matrix of 32 pixels used
311		   to represent the star, called box. */
312		box.left = s->h - 2;
313		box.right = s->h + 3;
314		box.top = s->v - 2;
315		box.bottom = s->v + 3;
316
317		/* Check if the box is fully outside of the bounding box of the clipping
318		   region. That woudl guarantee that the star is invisible. */
319		if ((box.right < buf->clip_bounds.left) ||
320			(box.left > buf->clip_bounds.right) ||
321			(box.bottom < buf->clip_bounds.top) ||
322			(box.top > buf->clip_bounds.bottom))
323			goto invisible_pat;
324
325		/* Now, we have to go through the list of rectangle of the clipping region
326		   and cumulate the mask of the star matrix pixels that are visible in any
327		   of those rectangle. At start time, the mask is empty. */
328		total_visible = 0;
329		r = buf->clip_list;
330		for (i=0; i<buf->clip_list_count; r++, i++) {
331			/* When reseting the clipping, all pixel of the matrix are tested. In
332			   the other mode, only the pixel previously visible are tested (as we
333			   want to know which one of the previously drawn pixel still need to
334			   be erased. */
335			if (reset_clipping)
336				tmp_visible = 0xffffffff;
337			else
338				tmp_visible = s->last_draw_pattern;
339
340			/* Calculate the clipping on the left side of the rectangle. */
341			delta = r->left-box.left;
342			if (delta > 5)
343				continue;
344			if (delta > 0)
345				tmp_visible &= visible_mask_left[delta];
346
347			/* Calculate the clipping on the right side of the rectangle. */
348			delta = box.right-r->right;
349			if (delta > 5)
350				continue;
351			if (delta > 0)
352				tmp_visible &= visible_mask_right[delta];
353
354			/* Calculate the clipping on the top side of the rectangle. */
355			delta = r->top-box.top;
356			if (delta > 5)
357				continue;
358			if (delta > 0)
359				tmp_visible &= visible_mask_top[delta];
360
361			/* Calculate the clipping on the bottom side of the rectangle. */
362			delta = box.bottom-r->bottom;
363			if (delta > 5)
364				continue;
365			if (delta > 0)
366				tmp_visible &= visible_mask_bottom[delta];
367
368			/* Pixel of the matrix not clipped out at that point are visible
369			   inside this rectangle of the clipping region. We need to add
370			   them to the mask of currently known visible pixel. */
371			total_visible |= tmp_visible;
372			/* If all pixel of the matrix are already visible, no need to continue
373			   further. */
374			if (total_visible == 0xffffffff)
375				goto visible_pat;
376		}
377		/* If no pixel are visible, then we know... */
378		if (total_visible != 0)
379			goto visible_pat;
380
381		/* The star is not visible. It's marked as not drawn. */
382	invisible_pat:
383		s->last_draw_offset = INVALID;
384		return false;
385	visible_pat:
386		/* The star is partially visible. The offset at which the star should be
387		   draw is calculated and store for using by drawing (and erasing later).
388		   The mask of which pixel of the matrix are visible is store for use
389		   at drawing and erasing time. */
390		s->last_draw_offset = s->v * buf->bytes_per_row + s->h * buf->bytes_per_pixel;
391		s->last_draw_pattern = total_visible;
392		return true;
393	}
394}
395
396/* After calling ProjectStar and CheckClipping, we're finally ready to
397   draw the star in its destination buffer. So let's do it... */
398void DrawStar(star *s, buffer *buf)
399{
400	int32		i, index, count;
401	uint8		*draw8;
402	uint16		*draw16;
403	uint32		*draw32;
404	uint32		*colors;
405	uint8		*pat_list;
406	uint8		*pat_color_offset;
407
408	/* Simple case : the star is represented by only one pixel. */
409	count = pattern_list_count[s->pattern_level];
410	if (count == 1) {
411		/* Depending the depth mode of the drawing buffer... */
412		switch (buf->depth_mode) {
413		case PIXEL_1_BYTE :
414			/* Get the pointer to the address we want to draw to... */
415			draw8 = (uint8*)((char*)buf->bits + s->last_draw_offset);
416			/* ... and write the color pattern we want to use depending of
417			   the lighting level and the color scheme of the star. */
418			*draw8 = buf->colors[s->color_type][pixel_color_offset[s->level]];
419			break;
420		case PIXEL_2_BYTES :
421			/* Same thing for 2 bytes mode */
422			draw16 = (uint16*)((char*)buf->bits + s->last_draw_offset);
423			*draw16 = buf->colors[s->color_type][pixel_color_offset[s->level]];
424			break;
425		case PIXEL_4_BYTES :
426			/* Same thing for 4 bytes mode */
427			draw32 = (uint32*)((char*)buf->bits + s->last_draw_offset);
428			*draw32 = buf->colors[s->color_type][pixel_color_offset[s->level]];
429			break;
430		}
431	}
432	/* Complex case : the star is represented by a multiple pixels. */
433	else {
434		/* Pointer to the color table used depending the color scheme of
435		   the star. */
436		colors = buf->colors[s->color_type];
437		pat_list = pattern_list[s->pattern_level];
438		pat_color_offset = pattern_color_offset[s->pattern_level];
439
440		/* Plot all pixel used to represent the star one after one... */
441		for (i=0; i<count; i++) {
442			/* This is the index of the pixel in the matrix */
443			index = pat_list[i];
444			/* Check if this pixel is visible (using the result of the clipping) */
445			if (s->last_draw_pattern & (1<<index)) {
446				switch (buf->depth_mode) {
447				case PIXEL_1_BYTE :
448					/* Get the pointer to the address we want to draw to... */
449					draw8 = (uint8*)((char*)buf->pattern_bits[index] + s->last_draw_offset);
450					/* ... and write the color pattern we want to use depending of
451					   the lighting level and the color scheme of the star. */
452					*draw8 = colors[pat_color_offset[i]];
453					break;
454				case PIXEL_2_BYTES :
455					/* Same thing for 2 bytes mode */
456					draw16 = (uint16*)((char*)buf->pattern_bits[index] + s->last_draw_offset);
457					*draw16 = colors[pat_color_offset[i]];
458					break;
459				case PIXEL_4_BYTES :
460					/* Same thing for 4 bytes mode */
461					draw32 = (uint32*)((char*)buf->pattern_bits[index] + s->last_draw_offset);
462					*draw32 = colors[pat_color_offset[i]];
463					break;
464				}
465			}
466		}
467	}
468}
469
470/* Before redrawing a star at its new position, we need to erase what we draw
471   at the previous frame... */
472void EraseStar(star *s, buffer *buf)
473{
474	int32		i, index, count;
475	uint8		*draw8;
476	uint16		*draw16;
477	uint32		*draw32;
478	uint32		back_color;
479	uint8		*pat_list;
480
481	/* Color pattern we use to erase the buffer. */
482	back_color = buf->back_color;
483
484	/* Simple case : the star is represented by only one pixel. */
485	count = pattern_list_count[s->pattern_level];
486	if (count == 1) {
487		/* Depending the depth mode of the drawing buffer... */
488		switch (buf->depth_mode) {
489		case PIXEL_1_BYTE :
490			/* Get the pointer to the address we want to erase... */
491			draw8 = (uint8*)((char*)buf->bits + s->last_draw_offset);
492			/* ... and write the background color pattern. */
493			*draw8 = back_color;
494			break;
495		case PIXEL_2_BYTES :
496			/* Same thing for 2 bytes mode */
497			draw16 = (uint16*)((char*)buf->bits + s->last_draw_offset);
498			*draw16 = back_color;
499			break;
500		case PIXEL_4_BYTES :
501			/* Same thing for 4 bytes mode */
502			draw32 = (uint32*)((char*)buf->bits + s->last_draw_offset);
503			*draw32 = back_color;
504			break;
505		}
506	}
507	/* Complex case : the star is represented by a multiple pixels. */
508	else {
509		pat_list = pattern_list[s->pattern_level];
510
511		/* Erase all pixel used to represent the star one after one... */
512		for (i=0; i<count; i++) {
513			index = pat_list[i];
514			/* Check if this pixel is visible (using the result of the clipping) */
515			if (s->last_draw_pattern & (1<<index)) {
516				switch (buf->depth_mode) {
517				case PIXEL_1_BYTE :
518					/* Get the pointer to the address we want to draw to... */
519					draw8 = (uint8*)((char*)buf->pattern_bits[index] + s->last_draw_offset);
520					/* ... and write the background color pattern. */
521					*draw8 = back_color;
522					break;
523				case PIXEL_2_BYTES :
524					/* Same thing for 2 bytes mode */
525					draw16 = (uint16*)((char*)buf->pattern_bits[index] + s->last_draw_offset);
526					*draw16 = back_color;
527					break;
528				case PIXEL_4_BYTES :
529					/* Same thing for 4 bytes mode */
530					draw32 = (uint32*)((char*)buf->pattern_bits[index] + s->last_draw_offset);
531					*draw32 = back_color;
532					break;
533				}
534			}
535		}
536	}
537}
538
539/* This function do the transition from previous state to the new state
540   as described in (geo), in the buffer (buf), for the list of star (sp) */
541void RefreshStarPacket(buffer *buf, star_packet *sp, geometry *geo)
542{
543	int32			i, min_count;
544	star			*s;
545
546	// TODO: For some reason, when selecting the "2 threads" option under vmware,
547	// some weird timing calculations finish with setting the star packet count to
548	// a negative number. This screws all the next calculations, and the animation
549	// then comes to a stop.
550	sp->count = max_c(sp->count, 0);
551
552	/* Calculate the number of stars that were process during the
553	   previous frame and still need to be process for that frame. */
554	min_count = sp->erase_count;
555	if (sp->count < min_count)
556		min_count = sp->count;
557
558	s = sp->list;
559
560	/* For all those star... */
561	for (i=0; i<min_count; s++, i++) {
562		/* ... erase them if necessary, ... */
563		if (s->last_draw_offset != INVALID)
564			EraseStar(s, buf);
565		/* ... project them at their new position, ... */
566		if (ProjectStar(s, geo)) {
567			/* ... check the clipping of the buffer if the star are in
568			   the pyramid of vision, ... */
569			if (CheckClipping(s, buf, true))
570				/* ... and draw them if they're really visible. */
571				DrawStar(s, buf);
572		}
573		/* ... or mark them as invisible if they're not in the pyramid
574		   of vision. */
575		else
576			s->last_draw_offset = INVALID;
577	}
578
579	/* For star that were process at the previous frame but that we don't
580	   want to process anymore, we just need to erase them. */
581	for (; i<sp->erase_count; s++, i++) {
582		if (s->last_draw_offset != INVALID)
583			EraseStar(s, buf);
584	}
585
586	/* For star that were not process before, but are now, we just need to
587	   go through the projection, clipping and drawing steps. */
588	for (; i<sp->count; s++, i++) {
589		if (ProjectStar(s, geo)) {
590			if (CheckClipping(s, buf, true))
591				DrawStar(s, buf);
592		}
593		else
594			s->last_draw_offset = INVALID;
595	}
596}
597
598/* Update the clipping visibility of all star of the list (sp) to
599  respect the new clipping defined for the buffer (buf). */
600void RefreshClipping(buffer *buf, star_packet *sp)
601{
602	star		*s;
603	int32		i;
604
605	s = sp->list;
606	for (i=0; i<sp->erase_count; s++, i++) {
607		if (s->last_draw_offset != INVALID)
608			CheckClipping(s, buf, false);
609	}
610}
611
612
613