1 /*
2 ** r_sprites.cpp
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2011 Braden Obrzut
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 **    notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 **    notice, this list of conditions and the following disclaimer in the
16 **    documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 **    derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 **
33 */
34 
35 #include "textures/textures.h"
36 #include "c_cvars.h"
37 #include "r_sprites.h"
38 #include "linkedlist.h"
39 #include "tarray.h"
40 #include "templates.h"
41 #include "actor.h"
42 #include "thingdef/thingdef.h"
43 #include "v_palette.h"
44 #include "wl_agent.h"
45 #include "wl_draw.h"
46 #include "wl_main.h"
47 #include "wl_play.h"
48 #include "wl_shade.h"
49 #include "zstring.h"
50 #include "r_data/colormaps.h"
51 #include "a_inventory.h"
52 #include "id_us.h"
53 #include "id_vh.h"
54 
55 struct SpriteInfo
56 {
57 	union
58 	{
59 		char 		name[5];
60 		uint32_t	iname;
61 	};
62 	unsigned int	frames;
63 	unsigned int	numFrames;
64 };
65 
66 struct Sprite
67 {
68 	static const uint8_t NO_FRAMES = 255; // If rotations == NO_FRAMES
69 
70 	FTextureID	texture[8];
71 	uint8_t		rotations;
72 	uint16_t	mirror; // Mirroring bitfield
73 };
74 
75 static TArray<Sprite> spriteFrames;
76 static TArray<SpriteInfo> loadedSprites;
77 
R_CheckSpriteValid(unsigned int spr)78 bool R_CheckSpriteValid(unsigned int spr)
79 {
80 	if(spr < NUM_SPECIAL_SPRITES)
81 		return true;
82 
83 	SpriteInfo &sprite = loadedSprites[spr];
84 	if(sprite.numFrames == 0)
85 		return false;
86 	return true;
87 }
88 
R_GetNameForSprite(unsigned int index)89 uint32_t R_GetNameForSprite(unsigned int index)
90 {
91 	return loadedSprites[index].iname;
92 }
93 
94 // Cache sprite name lookups
R_GetSprite(const char * spr)95 unsigned int R_GetSprite(const char* spr)
96 {
97 	static unsigned int mid = 0;
98 
99 	union
100 	{
101 		char 		name[4];
102 		uint32_t	iname;
103 	} tmp;
104 	memcpy(tmp.name, spr, 4);
105 
106 	if(tmp.iname == loadedSprites[mid].iname)
107 		return mid;
108 
109 	for(mid = 0;mid < NUM_SPECIAL_SPRITES;++mid)
110 	{
111 		if(tmp.iname == loadedSprites[mid].iname)
112 			return mid;
113 	}
114 
115 	unsigned int max = loadedSprites.Size()-1;
116 	unsigned int min = NUM_SPECIAL_SPRITES;
117 	mid = (min+max)/2;
118 	do
119 	{
120 		if(tmp.iname == loadedSprites[mid].iname)
121 			return mid;
122 
123 		if(tmp.iname < loadedSprites[mid].iname)
124 			max = mid-1;
125 		else if(tmp.iname > loadedSprites[mid].iname)
126 			min = mid+1;
127 		mid = (min+max)/2;
128 	}
129 	while(max >= min);
130 
131 	// I don't think this should ever happen, but if it does return no sprite.
132 	return 0;
133 }
134 
R_GetAMSprite(AActor * actor,angle_t rotangle,bool & flip)135 FTexture *R_GetAMSprite(AActor *actor, angle_t rotangle, bool &flip)
136 {
137 	if(actor->sprite == SPR_NONE || loadedSprites[actor->sprite].numFrames == 0)
138 		return NULL;
139 
140 	const Sprite &spr = spriteFrames[loadedSprites[actor->sprite].frames+actor->state->frame];
141 	FTexture *tex;
142 	if(spr.rotations == 0)
143 	{
144 		tex = TexMan[spr.texture[0]];
145 		flip = false;
146 	}
147 	else
148 	{
149 		int rot = (rotangle-actor->angle-(ANGLE_90-ANGLE_45/2))/ANGLE_45;
150 		tex = TexMan[spr.texture[rot]];
151 		flip = (spr.mirror>>rot)&1;
152 	}
153 	return tex;
154 }
155 
R_InstallSprite(Sprite & frame,FTexture * tex,int dir,bool mirror)156 void R_InstallSprite(Sprite &frame, FTexture *tex, int dir, bool mirror)
157 {
158 	if(dir < -1 || dir >= 8)
159 	{
160 		printf("Invalid frame data for '%s'.\n", tex->Name);
161 		return;
162 	}
163 
164 	if(dir == -1)
165 	{
166 		frame.rotations = 0;
167 		dir = 0;
168 	}
169 	else
170 		frame.rotations = 8;
171 
172 	frame.texture[dir] = tex->GetID();
173 	if(mirror)
174 		frame.mirror |= 1<<dir;
175 }
176 
R_GetNumLoadedSprites()177 unsigned int R_GetNumLoadedSprites()
178 {
179 	return loadedSprites.Size();
180 }
181 
R_GetSpriteHitlist(BYTE * hitlist)182 void R_GetSpriteHitlist(BYTE* hitlist)
183 {
184 	// Start by getting a list of currently in use sprites and then tell the
185 	// precacher to load them.
186 
187 	BYTE* sprites = new BYTE[loadedSprites.Size()];
188 	memset(sprites, 0, loadedSprites.Size());
189 
190 	for(AActor::Iterator iter = AActor::GetIterator();iter.Next();)
191 	{
192 		sprites[iter->state->spriteInf] = 1;
193 	}
194 
195 	for(unsigned int i = loadedSprites.Size();i-- > NUM_SPECIAL_SPRITES;)
196 	{
197 		if(!sprites[i])
198 			continue;
199 
200 		SpriteInfo &sprInf = loadedSprites[i];
201 		Sprite *frame = &spriteFrames[sprInf.frames];
202 		for(unsigned int j = sprInf.numFrames;j-- > 0;++frame)
203 		{
204 			if(frame->rotations == Sprite::NO_FRAMES)
205 				continue;
206 
207 			for(unsigned int k = frame->rotations;k-- > 0;)
208 			{
209 				if(frame->texture[k].isValid())
210 					hitlist[frame->texture[k].GetIndex()] |= 1;
211 			}
212 		}
213 	}
214 
215 	delete[] sprites;
216 }
217 
SpriteCompare(const void * s1,const void * s2)218 int SpriteCompare(const void *s1, const void *s2)
219 {
220 	uint32_t n1 = static_cast<const SpriteInfo *>(s1)->iname;
221 	uint32_t n2 = static_cast<const SpriteInfo *>(s2)->iname;
222 	if(n1 < n2)
223 		return -1;
224 	else if(n1 > n2)
225 		return 1;
226 	return 0;
227 }
228 
R_InitSprites()229 void R_InitSprites()
230 {
231 	static const uint8_t MAX_SPRITE_FRAMES = 29; // A-Z, [, \, ]
232 
233 	// First sort the loaded sprites list
234 	qsort(&loadedSprites[NUM_SPECIAL_SPRITES], loadedSprites.Size()-NUM_SPECIAL_SPRITES, sizeof(loadedSprites[0]), SpriteCompare);
235 
236 	typedef LinkedList<FTexture*> SpritesList;
237 	typedef TMap<uint32_t, SpritesList> SpritesMap;
238 
239 	SpritesMap spritesMap;
240 
241 	// Collect potential sprite list (linked list of sprites by name)
242 	for(unsigned int i = TexMan.NumTextures();i-- > 0;)
243 	{
244 		FTexture *tex = TexMan.ByIndex(i);
245 		if(tex->UseType == FTexture::TEX_Sprite && strlen(tex->Name) >= 6)
246 		{
247 			SpritesList &list = spritesMap[tex->dwName];
248 			list.Push(tex);
249 		}
250 	}
251 
252 	// Now process the sprites if we need to load them
253 	for(unsigned int i = NUM_SPECIAL_SPRITES;i < loadedSprites.Size();++i)
254 	{
255 		SpritesList &list = spritesMap[loadedSprites[i].iname];
256 		if(list.Size() == 0)
257 			continue;
258 		loadedSprites[i].frames = spriteFrames.Size();
259 
260 		Sprite frames[MAX_SPRITE_FRAMES];
261 		uint8_t maxframes = 0;
262 		for(unsigned int j = 0;j < MAX_SPRITE_FRAMES;++j)
263 		{
264 			frames[j].rotations = Sprite::NO_FRAMES;
265 			frames[j].mirror = 0;
266 		}
267 
268 		for(SpritesList::Iterator iter = list.Head();iter;++iter)
269 		{
270 			FTexture *tex = iter;
271 			unsigned char frame = tex->Name[4] - 'A';
272 			if(frame < MAX_SPRITE_FRAMES)
273 			{
274 				if(frame > maxframes)
275 					maxframes = frame;
276 				R_InstallSprite(frames[frame], tex, tex->Name[5] - '1', false);
277 
278 				if(strlen(tex->Name) == 8)
279 				{
280 					frame = tex->Name[6] - 'A';
281 					if(frame < MAX_SPRITE_FRAMES)
282 					{
283 						if(frame > maxframes)
284 							maxframes = frame;
285 						R_InstallSprite(frames[frame], tex, tex->Name[7] - '1', true);
286 					}
287 				}
288 			}
289 		}
290 
291 		++maxframes;
292 		for(unsigned int j = 0;j < maxframes;++j)
293 		{
294 			// Check rotations
295 			if(frames[j].rotations == 8)
296 			{
297 				for(unsigned int r = 0;r < 8;++r)
298 				{
299 					if(!frames[j].texture[r].isValid())
300 					{
301 						printf("Sprite %s is missing rotations for frame %c.\n", loadedSprites[i].name, j);
302 						break;
303 					}
304 				}
305 			}
306 
307 			spriteFrames.Push(frames[j]);
308 		}
309 
310 		loadedSprites[i].numFrames = maxframes;
311 	}
312 }
313 
R_LoadSprite(const FString & name)314 void R_LoadSprite(const FString &name)
315 {
316 	if(loadedSprites.Size() == 0)
317 	{
318 		// Make sure the special sprites are loaded
319 		SpriteInfo sprInf;
320 		sprInf.frames = 0;
321 		strcpy(sprInf.name, "TNT1");
322 		loadedSprites.Push(sprInf);
323 	}
324 
325 	if(name.Len() != 4)
326 	{
327 		printf("Sprite name invalid.\n");
328 		return;
329 	}
330 
331 	static uint32_t lastSprite = 0;
332 	SpriteInfo sprInf;
333 	sprInf.frames = 0;
334 	sprInf.numFrames = 0;
335 
336 	strcpy(sprInf.name, name.GetChars());
337 	if(loadedSprites.Size() > 0)
338 	{
339 		if(sprInf.iname == lastSprite)
340 			return;
341 
342 		for(unsigned int i = 0;i < loadedSprites.Size();++i)
343 		{
344 			if(loadedSprites[i].iname == sprInf.iname)
345 			{
346 				sprInf = loadedSprites[i];
347 				lastSprite = sprInf.iname;
348 				return;
349 			}
350 		}
351 	}
352 	lastSprite = sprInf.iname;
353 
354 	loadedSprites.Push(sprInf);
355 }
356 
357 ////////////////////////////////////////////////////////////////////////////////
358 
359 // From wl_draw.cpp
360 int CalcRotate(AActor *ob);
361 extern byte* vbuf;
362 extern unsigned vbufPitch;
363 extern fixed viewshift;
364 extern fixed viewz;
365 
ScaleSprite(AActor * actor,int xcenter,const Frame * frame,unsigned height)366 void ScaleSprite(AActor *actor, int xcenter, const Frame *frame, unsigned height)
367 {
368 	if(actor->sprite == SPR_NONE || loadedSprites[actor->sprite].numFrames == 0)
369 		return;
370 
371 	bool flip = false;
372 	const Sprite &spr = spriteFrames[loadedSprites[actor->sprite].frames+frame->frame];
373 	FTexture *tex;
374 	if(spr.rotations == 0)
375 		tex = TexMan[spr.texture[0]];
376 	else
377 	{
378 		int rot = (CalcRotate(actor)+4)%8;
379 		tex = TexMan[spr.texture[rot]];
380 		flip = (spr.mirror>>rot)&1;
381 	}
382 	if(tex == NULL)
383 		return;
384 
385 	const int scale = height>>3; // Integer part of the height
386 	const int topoffset = (scale*(viewz-(32<<FRACBITS))/(32<<FRACBITS));
387 	if(scale == 0 || -(viewheight/2 - viewshift - topoffset) >= scale)
388 		return;
389 
390 	const double dyScale = (height/256.0)*(actor->scaleY/65536.);
391 	const int upperedge = static_cast<int>((viewheight/2 - viewshift - topoffset)+scale - tex->GetScaledTopOffsetDouble()*dyScale);
392 
393 	const double dxScale = (height/256.0)*(FixedDiv(actor->scaleX, yaspect)/65536.);
394 	const int actx = static_cast<int>(xcenter - tex->GetScaledLeftOffsetDouble()*dxScale);
395 
396 	const unsigned int texWidth = tex->GetWidth();
397 	const unsigned int startX = -MIN(actx, 0);
398 	const unsigned int startY = -MIN(upperedge, 0);
399 	const fixed xStep = static_cast<fixed>(tex->xScale/dxScale);
400 	const fixed yStep = static_cast<fixed>(tex->yScale/dyScale);
401 	const fixed xRun = MIN<fixed>(texWidth<<FRACBITS, xStep*(viewwidth-actx));
402 	const fixed yRun = MIN<fixed>(tex->GetHeight()<<FRACBITS, yStep*(viewheight-upperedge));
403 
404 	const BYTE *colormap;
405 	if((actor->flags & FL_BRIGHT) || frame->fullbright)
406 		colormap = NormalLight.Maps;
407 	else
408 	{
409 		const int shade = LIGHT2SHADE(gLevelLight + r_extralight);
410 		const int tz = FixedMul(r_depthvisibility<<8, height);
411 		colormap = &NormalLight.Maps[GETPALOOKUP(MAX(tz, MINZ), shade)<<8];
412 	}
413 	const BYTE *src;
414 	byte *destBase = vbuf + actx + startX + (upperedge > 0 ? vbufPitch*upperedge : 0);
415 	byte *dest = destBase;
416 	unsigned int i;
417 	fixed x, y;
418 	for(i = actx+startX, x = startX*xStep;x < xRun;x += xStep, ++i, dest = ++destBase)
419 	{
420 		if(wallheight[i] > (signed)height)
421 			continue;
422 
423 		src = tex->GetColumn(flip ? texWidth - (x>>FRACBITS) - 1 : (x>>FRACBITS), NULL);
424 
425 		for(y = startY*yStep;y < yRun;y += yStep)
426 		{
427 			if(src[y>>FRACBITS])
428 				*dest = colormap[src[y>>FRACBITS]];
429 			dest += vbufPitch;
430 		}
431 	}
432 }
433 
R_DrawPlayerSprite(AActor * actor,const Frame * frame,fixed offsetX,fixed offsetY)434 void R_DrawPlayerSprite(AActor *actor, const Frame *frame, fixed offsetX, fixed offsetY)
435 {
436 	if(frame->spriteInf == SPR_NONE || loadedSprites[frame->spriteInf].numFrames == 0)
437 		return;
438 
439 	const Sprite &spr = spriteFrames[loadedSprites[frame->spriteInf].frames+frame->frame];
440 	FTexture *tex;
441 	if(spr.rotations == 0)
442 		tex = TexMan[spr.texture[0]];
443 	else
444 		tex = TexMan[spr.texture[(CalcRotate(actor)+4)%8]];
445 	if(tex == NULL)
446 		return;
447 
448 	const BYTE *colormap;
449 	if(frame->fullbright)
450 		colormap = NormalLight.Maps;
451 	else
452 	{
453 		const int shade = LIGHT2SHADE(gLevelLight) - (gLevelMaxLightVis/LIGHTVISIBILITY_FACTOR);
454 		colormap = &NormalLight.Maps[GETPALOOKUP(0, shade)<<8];
455 	}
456 
457 	const fixed scale = viewheight<<(FRACBITS-1);
458 
459 	const fixed centeringOffset = (centerx - 2*centerxwide)<<FRACBITS;
460 	const fixed leftedge = FixedMul((160<<FRACBITS) - fixed(tex->GetScaledLeftOffsetDouble()*FRACUNIT) + offsetX, pspritexscale) + centeringOffset;
461 	fixed upperedge = ((100-32)<<FRACBITS) + fixed(tex->GetScaledTopOffsetDouble()*FRACUNIT) - offsetY - AspectCorrection[r_ratio].tallscreen;
462 	if(viewsize == 21 && players[0].ReadyWeapon)
463 	{
464 		upperedge -= players[0].ReadyWeapon->yadjust;
465 	}
466 	upperedge = scale - FixedMul(upperedge, pspriteyscale);
467 
468 	// startX and startY indicate where the sprite becomes visible, we only
469 	// need to calculate the start since the end will be determined when we hit
470 	// the view during drawing.
471 	const unsigned int startX = -MIN(leftedge>>FRACBITS, 0);
472 	const unsigned int startY = -MIN(upperedge>>FRACBITS, 0);
473 	const fixed xStep = FixedDiv(tex->xScale, pspritexscale);
474 	const fixed yStep = FixedDiv(tex->yScale, pspriteyscale);
475 
476 	const int x1 = leftedge>>FRACBITS;
477 	const int y1 = upperedge>>FRACBITS;
478 	const fixed xRun = MIN<fixed>(tex->GetWidth()<<FRACBITS, xStep*(viewwidth-x1-startX));
479 	const fixed yRun = MIN<fixed>(tex->GetHeight()<<FRACBITS, yStep*(viewheight-y1));
480 	const BYTE *src;
481 	byte *destBase = vbuf+x1+startX + (y1 > 0 ? vbufPitch*y1 : 0);
482 	byte *dest = destBase;
483 	fixed x, y;
484 	for(x = startX*xStep;x < xRun;x += xStep)
485 	{
486 		src = tex->GetColumn(x>>FRACBITS, NULL);
487 
488 		for(y = startY*yStep;y < yRun;y += yStep)
489 		{
490 			if(src[y>>FRACBITS] != 0)
491 				*dest = colormap[src[y>>FRACBITS]];
492 			dest += vbufPitch;
493 		}
494 
495 		dest = ++destBase;
496 	}
497 }
498 
499 ////////////////////////////////////////////////////////////////////////////////
500 //
501 // S3DNA Zoomer
502 //
503 
IMPLEMENT_INTERNAL_CLASS(SpriteZoomer)504 IMPLEMENT_INTERNAL_CLASS(SpriteZoomer)
505 
506 SpriteZoomer::SpriteZoomer(FTextureID texID, unsigned short zoomtime) :
507 	Thinker(ThinkerList::VICTORY), frame(NULL), texID(texID), count(0), zoomtime(zoomtime)
508 {
509 }
510 
SpriteZoomer(const Frame * frame,unsigned short zoomtime)511 SpriteZoomer::SpriteZoomer(const Frame *frame, unsigned short zoomtime) :
512 	Thinker(ThinkerList::VICTORY), frame(frame), count(0), zoomtime(zoomtime)
513 {
514 	frametics = frame->duration;
515 }
516 
Draw()517 void SpriteZoomer::Draw()
518 {
519 	FTexture *gmoverTex;
520 	if(frame)
521 	{
522 		const Sprite &spr = spriteFrames[loadedSprites[frame->spriteInf].frames+frame->frame];
523 		gmoverTex = TexMan[spr.texture[0]];
524 	}
525 	else
526 		gmoverTex = TexMan(texID);
527 
528 	// What we're trying to do is zoom in a 160x160 player sprite to
529 	// fill the viewheight.  S3DNA use the player sprite rendering
530 	// function and passed count as the height. We won't do it like that
531 	// since that method didn't account for the view window size
532 	// (vanilla could crash) and our player sprite renderer may take
533 	// into account things we would rather not have here.
534 	const double yscale = double(viewheight*count)/double(zoomtime*64);
535 	const double xscale = yscale/FIXED2FLOAT(yaspect);
536 
537 	screen->DrawTexture(gmoverTex, viewscreenx + (viewwidth>>1), viewscreeny + (viewheight>>1) + yscale*32,
538 		DTA_DestWidthF, gmoverTex->GetScaledWidthDouble()*xscale,
539 		DTA_DestHeightF, gmoverTex->GetScaledHeightDouble()*yscale,
540 		TAG_DONE);
541 }
542 
Tick()543 void SpriteZoomer::Tick()
544 {
545 	if(frame)
546 	{
547 		if(--frametics <= 0)
548 		{
549 			do
550 			{
551 				frame = frame->next;
552 				frametics = frame->duration;
553 			}
554 			while(frametics == 0);
555 		}
556 	}
557 
558 	assert(count <= zoomtime);
559 	if(++count > zoomtime)
560 		Destroy();
561 }
562 
R_DrawZoomer(FTextureID texID)563 void R_DrawZoomer(FTextureID texID)
564 {
565 	TObjPtr<SpriteZoomer> zoomer = new SpriteZoomer(texID, 192);
566 	do
567 	{
568 		for(unsigned int t = tics;zoomer && t-- > 0;)
569 			zoomer->Tick();
570 		if(!zoomer)
571 			break;
572 
573 		ThreeDRefresh();
574 		zoomer->Draw();
575 		VH_UpdateScreen();
576 		CalcTics();
577 	}
578 	while(true);
579 }
580