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