1 // Emacs style mode select	 -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id:$
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 //
8 // This source is available for distribution and/or modification
9 // only under the terms of the DOOM Source Code License as
10 // published by id Software. All rights reserved.
11 //
12 // The source is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15 // for more details.
16 //
17 // $Log:$
18 //
19 // DESCRIPTION:
20 //		Mission begin melt/wipe screen special effect.
21 //
22 //-----------------------------------------------------------------------------
23 
24 #include "i_video.h"
25 #include "v_video.h"
26 #include "m_random.h"
27 #include "doomdef.h"
28 #include "f_wipe.h"
29 #include "c_cvars.h"
30 #include "templates.h"
31 
32 //
33 //		SCREEN WIPE PACKAGE
34 //
35 
36 static int CurrentWipeType;
37 
38 static short *wipe_scr_start;
39 static short *wipe_scr_end;
40 static int *y;
41 
42 // [RH] Fire Wipe
43 #define FIREWIDTH	64
44 #define FIREHEIGHT	64
45 static BYTE *burnarray;
46 static int density;
47 static int burntime;
48 
49 // [RH] Crossfade
50 static int fade;
51 
52 
53 // Melt -------------------------------------------------------------
54 
55 // Match the strip sizes that oldschool Doom used on a 320x200 screen.
56 #define MELT_WIDTH		160
57 #define MELT_HEIGHT		200
58 
wipe_shittyColMajorXform(short * array)59 void wipe_shittyColMajorXform (short *array)
60 {
61 	int x, y;
62 	short *dest;
63 	int width = SCREENWIDTH / 2;
64 
65 	dest = new short[width*SCREENHEIGHT*2];
66 
67 	for(y = 0; y < SCREENHEIGHT; y++)
68 		for(x = 0; x < width; x++)
69 			dest[x*SCREENHEIGHT+y] = array[y*width+x];
70 
71 	memcpy(array, dest, SCREENWIDTH*SCREENHEIGHT);
72 
73 	delete[] dest;
74 }
75 
wipe_initMelt(int ticks)76 bool wipe_initMelt (int ticks)
77 {
78 	int i, r;
79 
80 	// copy start screen to main screen
81 	screen->DrawBlock (0, 0, SCREENWIDTH, SCREENHEIGHT, (BYTE *)wipe_scr_start);
82 
83 	// makes this wipe faster (in theory)
84 	// to have stuff in column-major format
85 	wipe_shittyColMajorXform (wipe_scr_start);
86 	wipe_shittyColMajorXform (wipe_scr_end);
87 
88 	// setup initial column positions
89 	// (y<0 => not ready to scroll yet)
90 	y = new int[MELT_WIDTH];
91 	y[0] = -(M_Random() & 15);
92 	for (i = 1; i < MELT_WIDTH; i++)
93 	{
94 		r = (M_Random()%3) - 1;
95 		y[i] = clamp(y[i-1] + r, -15, 0);
96 	}
97 
98 	return 0;
99 }
100 
wipe_doMelt(int ticks)101 bool wipe_doMelt (int ticks)
102 {
103 	int i, j, dy, x;
104 	const short *s;
105 	short *d;
106 	bool done = true;
107 
108 	while (ticks--)
109 	{
110 		done = true;
111 		for (i = 0; i < MELT_WIDTH; i++)
112 		{
113 			if (y[i] < 0)
114 			{
115 				y[i]++;
116 				done = false;
117 			}
118 			else if (y[i] < MELT_HEIGHT)
119 			{
120 				dy = (y[i] < 16) ? y[i]+1 : 8;
121 				y[i] = MIN(y[i] + dy, MELT_HEIGHT);
122 				done = false;
123 			}
124 			if (ticks == 0 && y[i] >= 0)
125 			{ // Only draw for the final tick.
126 				const int pitch = screen->GetPitch() / 2;
127 				int sy = y[i] * SCREENHEIGHT / MELT_HEIGHT;
128 
129 				for (x = i * (SCREENWIDTH/2) / MELT_WIDTH; x < (i + 1) * (SCREENWIDTH/2) / MELT_WIDTH; ++x)
130 				{
131 					s = &wipe_scr_end[x*SCREENHEIGHT];
132 					d = &((short *)screen->GetBuffer())[x];
133 
134 					for (j = sy; j != 0; --j)
135 					{
136 						*d = *(s++);
137 						d += pitch;
138 					}
139 
140 					s = &wipe_scr_start[x*SCREENHEIGHT];
141 
142 					for (j = SCREENHEIGHT - sy; j != 0; --j)
143 					{
144 						*d = *(s++);
145 						d += pitch;
146 					}
147 				}
148 			}
149 		}
150 	}
151 
152 	return done;
153 }
154 
wipe_exitMelt(int ticks)155 bool wipe_exitMelt (int ticks)
156 {
157 	delete[] y;
158 	return 0;
159 }
160 
161 // Burn -------------------------------------------------------------
162 
wipe_initBurn(int ticks)163 bool wipe_initBurn (int ticks)
164 {
165 	burnarray = new BYTE[FIREWIDTH * (FIREHEIGHT+5)];
166 	memset (burnarray, 0, FIREWIDTH * (FIREHEIGHT+5));
167 	density = 4;
168 	burntime = 0;
169 	return 0;
170 }
171 
wipe_CalcBurn(BYTE * burnarray,int width,int height,int density)172 int wipe_CalcBurn (BYTE *burnarray, int width, int height, int density)
173 {
174 	// This is a modified version of the fire that was once used
175 	// on the player setup menu.
176 	static int voop;
177 
178 	int a, b;
179 	BYTE *from;
180 
181 	// generator
182 	from = &burnarray[width * height];
183 	b = voop;
184 	voop += density / 3;
185 	for (a = 0; a < density/8; a++)
186 	{
187 		unsigned int offs = (a+b) & (width - 1);
188 		unsigned int v = M_Random();
189 		v = MIN(from[offs] + 4 + (v & 15) + (v >> 3) + (M_Random() & 31), 255u);
190 		from[offs] = from[width*2 + ((offs + width*3/2) & (width - 1))] = v;
191 	}
192 
193 	density = MIN(density + 10, width * 7);
194 
195 	from = burnarray;
196 	for (b = 0; b <= height; b += 2)
197 	{
198 		BYTE *pixel = from;
199 
200 		// special case: first pixel on line
201 		BYTE *p = pixel + (width << 1);
202 		unsigned int top = *p + *(p + width - 1) + *(p + 1);
203 		unsigned int bottom = *(pixel + (width << 2));
204 		unsigned int c1 = (top + bottom) >> 2;
205 		if (c1 > 1) c1--;
206 		*pixel = c1;
207 		*(pixel + width) = (c1 + bottom) >> 1;
208 		pixel++;
209 
210 		// main line loop
211 		for (a = 1; a < width-1; a++)
212 		{
213 			// sum top pixels
214 			p = pixel + (width << 1);
215 			top = *p + *(p - 1) + *(p + 1);
216 
217 			// bottom pixel
218 			bottom = *(pixel + (width << 2));
219 
220 			// combine pixels
221 			c1 = (top + bottom) >> 2;
222 			if (c1 > 1) c1--;
223 
224 			// store pixels
225 			*pixel = c1;
226 			*(pixel + width) = (c1 + bottom) >> 1;		// interpolate
227 
228 			// next pixel
229 			pixel++;
230 		}
231 
232 		// special case: last pixel on line
233 		p = pixel + (width << 1);
234 		top = *p + *(p - 1) + *(p - width + 1);
235 		bottom = *(pixel + (width << 2));
236 		c1 = (top + bottom) >> 2;
237 		if (c1 > 1) c1--;
238 		*pixel = c1;
239 		*(pixel + width) = (c1 + bottom) >> 1;
240 
241 		// next line
242 		from += width << 1;
243 	}
244 
245 	// Check for done-ness. (Every pixel with level 126 or higher counts as done.)
246 	for (a = width * height, from = burnarray; a != 0; --a, ++from)
247 	{
248 		if (*from < 126)
249 		{
250 			return density;
251 		}
252 	}
253 	return -1;
254 }
255 
wipe_doBurn(int ticks)256 bool wipe_doBurn (int ticks)
257 {
258 	bool done;
259 
260 	burntime += ticks;
261 	ticks *= 2;
262 
263 	// Make the fire burn
264 	done = false;
265 	while (!done && ticks--)
266 	{
267 		density = wipe_CalcBurn(burnarray, FIREWIDTH, FIREHEIGHT, density);
268 		done = (density < 0);
269 	}
270 
271 	// Draw the screen
272 	fixed_t xstep, ystep, firex, firey;
273 	int x, y;
274 	BYTE *to, *fromold, *fromnew;
275 
276 	xstep = (FIREWIDTH * FRACUNIT) / SCREENWIDTH;
277 	ystep = (FIREHEIGHT * FRACUNIT) / SCREENHEIGHT;
278 	to = screen->GetBuffer();
279 	fromold = (BYTE *)wipe_scr_start;
280 	fromnew = (BYTE *)wipe_scr_end;
281 
282 	for (y = 0, firey = 0; y < SCREENHEIGHT; y++, firey += ystep)
283 	{
284 		for (x = 0, firex = 0; x < SCREENWIDTH; x++, firex += xstep)
285 		{
286 			int fglevel;
287 
288 			fglevel = burnarray[(firex>>FRACBITS)+(firey>>FRACBITS)*FIREWIDTH] / 2;
289 			if (fglevel >= 63)
290 			{
291 				to[x] = fromnew[x];
292 			}
293 			else if (fglevel == 0)
294 			{
295 				to[x] = fromold[x];
296 				done = false;
297 			}
298 			else
299 			{
300 				int bglevel = 64-fglevel;
301 				DWORD *fg2rgb = Col2RGB8[fglevel];
302 				DWORD *bg2rgb = Col2RGB8[bglevel];
303 				DWORD fg = fg2rgb[fromnew[x]];
304 				DWORD bg = bg2rgb[fromold[x]];
305 				fg = (fg+bg) | 0x1f07c1f;
306 				to[x] = RGB32k.All[fg & (fg>>15)];
307 				done = false;
308 			}
309 		}
310 		fromold += SCREENWIDTH;
311 		fromnew += SCREENWIDTH;
312 		to += SCREENPITCH;
313 	}
314 
315 	return done || (burntime > 40);
316 }
317 
wipe_exitBurn(int ticks)318 bool wipe_exitBurn (int ticks)
319 {
320 	delete[] burnarray;
321 	return 0;
322 }
323 
324 // Crossfade --------------------------------------------------------
325 
wipe_initFade(int ticks)326 bool wipe_initFade (int ticks)
327 {
328 	fade = 0;
329 	return 0;
330 }
331 
wipe_doFade(int ticks)332 bool wipe_doFade (int ticks)
333 {
334 	fade += ticks * 2;
335 	if (fade > 64)
336 	{
337 		screen->DrawBlock (0, 0, SCREENWIDTH, SCREENHEIGHT, (BYTE *)wipe_scr_end);
338 		return true;
339 	}
340 	else
341 	{
342 		int x, y;
343 		fixed_t bglevel = 64 - fade;
344 		DWORD *fg2rgb = Col2RGB8[fade];
345 		DWORD *bg2rgb = Col2RGB8[bglevel];
346 		BYTE *fromnew = (BYTE *)wipe_scr_end;
347 		BYTE *fromold = (BYTE *)wipe_scr_start;
348 		BYTE *to = screen->GetBuffer();
349 
350 		for (y = 0; y < SCREENHEIGHT; y++)
351 		{
352 			for (x = 0; x < SCREENWIDTH; x++)
353 			{
354 				DWORD fg = fg2rgb[fromnew[x]];
355 				DWORD bg = bg2rgb[fromold[x]];
356 				fg = (fg+bg) | 0x1f07c1f;
357 				to[x] = RGB32k.All[fg & (fg>>15)];
358 			}
359 			fromnew += SCREENWIDTH;
360 			fromold += SCREENWIDTH;
361 			to += SCREENPITCH;
362 		}
363 	}
364 	return false;
365 }
366 
wipe_exitFade(int ticks)367 bool wipe_exitFade (int ticks)
368 {
369 	return 0;
370 }
371 
372 // General Wipe Functions -------------------------------------------
373 
374 static bool (*wipes[])(int) =
375 {
376 	wipe_initMelt, wipe_doMelt, wipe_exitMelt,
377 	wipe_initBurn, wipe_doBurn, wipe_exitBurn,
378 	wipe_initFade, wipe_doFade, wipe_exitFade
379 };
380 
381 // Returns true if the wipe should be performed.
wipe_StartScreen(int type)382 bool wipe_StartScreen (int type)
383 {
384 	CurrentWipeType = clamp(type, 0, wipe_NUMWIPES - 1);
385 
386 	if (CurrentWipeType)
387 	{
388 		wipe_scr_start = new short[SCREENWIDTH * SCREENHEIGHT / 2];
389 		screen->GetBlock (0, 0, SCREENWIDTH, SCREENHEIGHT, (BYTE *)wipe_scr_start);
390 		return true;
391 	}
392 	return false;
393 }
394 
wipe_EndScreen(void)395 void wipe_EndScreen (void)
396 {
397 	if (CurrentWipeType)
398 	{
399 		wipe_scr_end = new short[SCREENWIDTH * SCREENHEIGHT / 2];
400 		screen->GetBlock (0, 0, SCREENWIDTH, SCREENHEIGHT, (BYTE *)wipe_scr_end);
401 		screen->DrawBlock (0, 0, SCREENWIDTH, SCREENHEIGHT, (BYTE *)wipe_scr_start); // restore start scr.
402 		// Initialize the wipe
403 		(*wipes[(CurrentWipeType-1)*3])(0);
404 	}
405 }
406 
407 // Returns true if the wipe is done.
wipe_ScreenWipe(int ticks)408 bool wipe_ScreenWipe (int ticks)
409 {
410 	bool rc;
411 
412 	if (CurrentWipeType == wipe_None)
413 		return true;
414 
415 	// do a piece of wipe-in
416 	V_MarkRect(0, 0, SCREENWIDTH, SCREENHEIGHT);
417 	rc = (*wipes[(CurrentWipeType-1)*3+1])(ticks);
418 
419 	return rc;
420 }
421 
422 // Final things for the wipe
wipe_Cleanup()423 void wipe_Cleanup()
424 {
425 	if (wipe_scr_start != NULL)
426 	{
427 		delete[] wipe_scr_start;
428 		wipe_scr_start = NULL;
429 	}
430 	if (wipe_scr_end != NULL)
431 	{
432 		delete[] wipe_scr_end;
433 		wipe_scr_end = NULL;
434 	}
435 	if (CurrentWipeType > 0)
436 	{
437 		(*wipes[(CurrentWipeType-1)*3+2])(0);
438 	}
439 }
440