1 /* ScummVM - Graphic Adventure Engine
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
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.
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.
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.
26 // Sprite management module
28 #include "saga/saga.h"
31 #include "saga/scene.h"
32 #include "saga/resource.h"
33 #include "saga/font.h"
35 #include "saga/sprite.h"
36 #include "saga/render.h"
40 #define RID_IHNM_ARROW_SPRITES 13
41 #define RID_IHNM_SAVEREMINDER_SPRITES 14
42 #define RID_IHNMDEMO_ARROW_SPRITES 8
43 #define RID_IHNMDEMO_SAVEREMINDER_SPRITES 9
45 Sprite::Sprite(SagaEngine
*vm
) : _vm(vm
) {
46 debug(8, "Initializing sprite subsystem...");
48 // Load sprite module resource context
49 _spriteContext
= _vm
->_resource
->getContext(GAME_RESOURCEFILE
);
50 if (_spriteContext
== NULL
) {
51 error("Sprite::Sprite resource context not found");
54 _decodeBufLen
= DECODE_BUF_LEN
;
56 _decodeBuf
= (byte
*)malloc(_decodeBufLen
);
57 if (_decodeBuf
== NULL
) {
58 memoryError("Sprite::Sprite");
61 if (_vm
->getGameId() == GID_ITE
) {
62 loadList(_vm
->getResourceDescription()->mainSpritesResourceId
, _mainSprites
);
63 _arrowSprites
= _saveReminderSprites
= _inventorySprites
= _mainSprites
;
65 } else if (_vm
->getGameId() == GID_IHNM
) {
66 if (_vm
->getFeatures() & GF_IHNM_DEMO
) {
67 loadList(RID_IHNMDEMO_ARROW_SPRITES
, _arrowSprites
);
68 loadList(RID_IHNMDEMO_SAVEREMINDER_SPRITES
, _saveReminderSprites
);
70 loadList(RID_IHNM_ARROW_SPRITES
, _arrowSprites
);
71 loadList(RID_IHNM_SAVEREMINDER_SPRITES
, _saveReminderSprites
);
75 error("Sprite: unknown game type");
79 Sprite::~Sprite(void) {
80 debug(8, "Shutting down sprite subsystem...");
81 _mainSprites
.freeMem();
82 if (_vm
->getGameId() == GID_IHNM
) {
83 _inventorySprites
.freeMem();
84 _arrowSprites
.freeMem();
85 _saveReminderSprites
.freeMem();
90 void Sprite::loadList(int resourceId
, SpriteList
&spriteList
) {
91 SpriteInfo
*spriteInfo
;
92 byte
*spriteListData
= 0;
93 size_t spriteListLength
= 0;
94 uint16 oldSpriteCount
;
95 uint16 newSpriteCount
;
98 int outputLength
, inputLength
;
100 const byte
*spritePointer
;
101 const byte
*spriteDataPointer
;
103 _vm
->_resource
->loadResource(_spriteContext
, resourceId
, spriteListData
, spriteListLength
);
105 if (spriteListLength
== 0) {
109 MemoryReadStreamEndian
readS(spriteListData
, spriteListLength
, _spriteContext
->isBigEndian
);
111 spriteCount
= readS
.readUint16();
113 debug(9, "Sprites: %d", spriteCount
);
115 oldSpriteCount
= spriteList
.spriteCount
;
116 newSpriteCount
= spriteList
.spriteCount
+ spriteCount
;
118 spriteList
.infoList
= (SpriteInfo
*)realloc(spriteList
.infoList
, newSpriteCount
* sizeof(*spriteList
.infoList
));
119 if (spriteList
.infoList
== NULL
) {
120 memoryError("Sprite::loadList");
123 spriteList
.spriteCount
= newSpriteCount
;
125 bool bigHeader
= _vm
->getGameId() == GID_IHNM
|| _vm
->isMacResources();
127 for (i
= oldSpriteCount
; i
< spriteList
.spriteCount
; i
++) {
128 spriteInfo
= &spriteList
.infoList
[i
];
130 offset
= readS
.readUint32();
132 offset
= readS
.readUint16();
134 if (offset
>= spriteListLength
) {
135 // ITE Mac demos throw this warning
136 warning("Sprite::loadList offset exceeded");
137 spriteList
.spriteCount
= i
;
141 spritePointer
= spriteListData
;
142 spritePointer
+= offset
;
145 MemoryReadStreamEndian
readS2(spritePointer
, 8, _spriteContext
->isBigEndian
);
147 spriteInfo
->xAlign
= readS2
.readSint16();
148 spriteInfo
->yAlign
= readS2
.readSint16();
150 spriteInfo
->width
= readS2
.readUint16();
151 spriteInfo
->height
= readS2
.readUint16();
153 spriteDataPointer
= spritePointer
+ readS2
.pos();
155 MemoryReadStreamEndian
readS2(spritePointer
, 4);
157 spriteInfo
->xAlign
= readS2
.readSByte();
158 spriteInfo
->yAlign
= readS2
.readSByte();
160 spriteInfo
->width
= readS2
.readByte();
161 spriteInfo
->height
= readS2
.readByte();
162 spriteDataPointer
= spritePointer
+ readS2
.pos();
165 outputLength
= spriteInfo
->width
* spriteInfo
->height
;
166 inputLength
= spriteListLength
- (spriteDataPointer
- spriteListData
);
167 decodeRLEBuffer(spriteDataPointer
, inputLength
, outputLength
);
168 spriteInfo
->decodedBuffer
= (byte
*) malloc(outputLength
);
169 if (spriteInfo
->decodedBuffer
== NULL
) {
170 memoryError("Sprite::loadList");
174 // IHNM sprites are upside-down, for reasons which i can only
175 // assume are perverse. To simplify things, flip them now. Not
178 if (_vm
->getGameId() == GID_IHNM
) {
179 byte
*src
= _decodeBuf
+ spriteInfo
->width
* (spriteInfo
->height
- 1);
180 byte
*dst
= spriteInfo
->decodedBuffer
;
182 for (int j
= 0; j
< spriteInfo
->height
; j
++) {
183 memcpy(dst
, src
, spriteInfo
->width
);
184 src
-= spriteInfo
->width
;
185 dst
+= spriteInfo
->width
;
189 memcpy(spriteInfo
->decodedBuffer
, _decodeBuf
, outputLength
);
192 free(spriteListData
);
195 void Sprite::getScaledSpriteBuffer(SpriteList
&spriteList
, int spriteNumber
, int scale
, int &width
, int &height
, int &xAlign
, int &yAlign
, const byte
*&buffer
) {
196 SpriteInfo
*spriteInfo
;
198 if (spriteList
.spriteCount
<= spriteNumber
) {
199 // this can occur in IHNM while loading a saved game from chapter 1-5 when being in the end chapter
200 warning("spriteList.spriteCount <= spriteNumber");
204 spriteInfo
= &spriteList
.infoList
[spriteNumber
];
207 xAlign
= (spriteInfo
->xAlign
* scale
) >> 8;
208 yAlign
= (spriteInfo
->yAlign
* scale
) >> 8;
209 height
= (spriteInfo
->height
* scale
+ 0x7f) >> 8;
210 width
= (spriteInfo
->width
* scale
+ 0x7f) >> 8;
211 scaleBuffer(spriteInfo
->decodedBuffer
, spriteInfo
->width
, spriteInfo
->height
, scale
);
214 xAlign
= spriteInfo
->xAlign
;
215 yAlign
= spriteInfo
->yAlign
;
216 height
= spriteInfo
->height
;
217 width
= spriteInfo
->width
;
218 buffer
= spriteInfo
->decodedBuffer
;
222 void Sprite::drawClip(const Point
&spritePointer
, int width
, int height
, const byte
*spriteBuffer
, bool clipToScene
) {
225 Common::Rect clipRect
= clipToScene
? _vm
->_scene
->getSceneClip() : _vm
->getDisplayClip();
229 const byte
*srcRowPointer
;
231 bufRowPointer
= _vm
->_gfx
->getBackBufferPixels() + _vm
->_gfx
->getBackBufferPitch() * spritePointer
.y
;
232 srcRowPointer
= spriteBuffer
;
234 clipWidth
= CLIP(width
, 0, clipRect
.right
- spritePointer
.x
);
235 clipHeight
= CLIP(height
, 0, clipRect
.bottom
- spritePointer
.y
);
239 if (spritePointer
.x
< clipRect
.left
) {
240 jo
= clipRect
.left
- spritePointer
.x
;
242 if (spritePointer
.y
< clipRect
.top
) {
243 io
= clipRect
.top
- spritePointer
.y
;
244 bufRowPointer
+= _vm
->_gfx
->getBackBufferPitch() * io
;
245 srcRowPointer
+= width
* io
;
248 for (i
= io
; i
< clipHeight
; i
++) {
249 for (j
= jo
; j
< clipWidth
; j
++) {
250 assert(_vm
->_gfx
->getBackBufferPixels() <= (byte
*)(bufRowPointer
+ j
+ spritePointer
.x
));
251 assert((_vm
->_gfx
->getBackBufferPixels() + (_vm
->getDisplayInfo().width
*
252 _vm
->getDisplayInfo().height
)) > (byte
*)(bufRowPointer
+ j
+ spritePointer
.x
));
253 assert((const byte
*)spriteBuffer
<= (const byte
*)(srcRowPointer
+ j
));
254 assert(((const byte
*)spriteBuffer
+ (width
* height
)) > (const byte
*)(srcRowPointer
+ j
));
256 if (*(srcRowPointer
+ j
) != 0) {
257 *(bufRowPointer
+ j
+ spritePointer
.x
) = *(srcRowPointer
+ j
);
260 bufRowPointer
+= _vm
->_gfx
->getBackBufferPitch();
261 srcRowPointer
+= width
;
264 int x1
= MAX
<int>(spritePointer
.x
, 0);
265 int y1
= MAX
<int>(spritePointer
.y
, 0);
266 int x2
= MIN
<int>(MAX
<int>(spritePointer
.x
+ clipWidth
, 0), clipRect
.right
);
267 int y2
= MIN
<int>(MAX
<int>(spritePointer
.y
+ clipHeight
, 0), clipRect
.bottom
);
269 if (x2
> x1
&& y2
> y1
)
270 _vm
->_render
->addDirtyRect(Common::Rect(x1
, y1
, x2
, y2
));
273 void Sprite::draw(SpriteList
&spriteList
, int32 spriteNumber
, const Point
&screenCoord
, int scale
, bool clipToScene
) {
274 const byte
*spriteBuffer
= NULL
;
281 getScaledSpriteBuffer(spriteList
, spriteNumber
, scale
, width
, height
, xAlign
, yAlign
, spriteBuffer
);
283 spritePointer
.x
= screenCoord
.x
+ xAlign
;
284 spritePointer
.y
= screenCoord
.y
+ yAlign
;
286 drawClip(spritePointer
, width
, height
, spriteBuffer
, clipToScene
);
289 void Sprite::draw(SpriteList
&spriteList
, int32 spriteNumber
, const Rect
&screenRect
, int scale
, bool clipToScene
) {
290 const byte
*spriteBuffer
= NULL
;
299 getScaledSpriteBuffer(spriteList
, spriteNumber
, scale
, width
, height
, xAlign
, yAlign
, spriteBuffer
);
300 spw
= (screenRect
.width() - width
) / 2;
301 sph
= (screenRect
.height() - height
) / 2;
308 spritePointer
.x
= screenRect
.left
+ xAlign
+ spw
;
309 spritePointer
.y
= screenRect
.top
+ yAlign
+ sph
;
310 drawClip(spritePointer
, width
, height
, spriteBuffer
, clipToScene
);
313 bool Sprite::hitTest(SpriteList
&spriteList
, int spriteNumber
, const Point
&screenCoord
, int scale
, const Point
&testPoint
) {
314 const byte
*spriteBuffer
= NULL
;
316 const byte
*srcRowPointer
;
323 getScaledSpriteBuffer(spriteList
, spriteNumber
, scale
, width
, height
, xAlign
, yAlign
, spriteBuffer
);
325 spritePointer
.x
= screenCoord
.x
+ xAlign
;
326 spritePointer
.y
= screenCoord
.y
+ yAlign
;
328 if ((testPoint
.y
< spritePointer
.y
) || (testPoint
.y
>= spritePointer
.y
+ height
)) {
331 if ((testPoint
.x
< spritePointer
.x
) || (testPoint
.x
>= spritePointer
.x
+ width
)) {
334 i
= testPoint
.y
- spritePointer
.y
;
335 j
= testPoint
.x
- spritePointer
.x
;
336 srcRowPointer
= spriteBuffer
+ j
+ i
* width
;
337 return *srcRowPointer
!= 0;
340 void Sprite::drawOccluded(SpriteList
&spriteList
, int spriteNumber
, const Point
&screenCoord
, int scale
, int depth
) {
341 const byte
*spriteBuffer
= NULL
;
343 byte
*destRowPointer
;
344 const byte
*sourceRowPointer
;
345 const byte
*sourcePointer
;
359 size_t maskBufferLength
;
360 byte
*maskRowPointer
;
363 if (!_vm
->_scene
->isBGMaskPresent()) {
364 draw(spriteList
, spriteNumber
, screenCoord
, scale
);
368 _vm
->_scene
->getBGMaskInfo(maskWidth
, maskHeight
, maskBuffer
, maskBufferLength
);
370 getScaledSpriteBuffer(spriteList
, spriteNumber
, scale
, width
, height
, xAlign
, yAlign
, spriteBuffer
);
372 clipData
.destPoint
.x
= screenCoord
.x
+ xAlign
;
373 clipData
.destPoint
.y
= screenCoord
.y
+ yAlign
;
375 clipData
.sourceRect
.left
= 0;
376 clipData
.sourceRect
.top
= 0;
377 clipData
.sourceRect
.right
= width
;
378 clipData
.sourceRect
.bottom
= height
;
380 clipData
.destRect
= _vm
->_scene
->getSceneClip();
382 if (!clipData
.calcClip()) {
386 // Finally, draw the occluded sprite
388 sourceRowPointer
= spriteBuffer
+ clipData
.drawSource
.x
+ (clipData
.drawSource
.y
* width
);
389 destRowPointer
= _vm
->_gfx
->getBackBufferPixels() + clipData
.drawDest
.x
+ (clipData
.drawDest
.y
* _vm
->_gfx
->getBackBufferPitch());
390 maskRowPointer
= maskBuffer
+ clipData
.drawDest
.x
+ (clipData
.drawDest
.y
* maskWidth
);
392 for (y
= 0; y
< clipData
.drawHeight
; y
++) {
393 sourcePointer
= sourceRowPointer
;
394 destPointer
= destRowPointer
;
395 maskPointer
= maskRowPointer
;
396 for (x
= 0; x
< clipData
.drawWidth
; x
++) {
397 if (*sourcePointer
!= 0) {
398 maskZ
= *maskPointer
& SPRITE_ZMASK
;
400 *destPointer
= *sourcePointer
;
407 destRowPointer
+= _vm
->_gfx
->getBackBufferPitch();
408 maskRowPointer
+= maskWidth
;
409 sourceRowPointer
+= width
;
412 _vm
->_render
->addDirtyRect(Common::Rect(clipData
.drawSource
.x
, clipData
.drawSource
.y
,
413 clipData
.drawSource
.x
+ width
, clipData
.drawSource
.y
+ height
));
416 void Sprite::decodeRLEBuffer(const byte
*inputBuffer
, size_t inLength
, size_t outLength
) {
423 if (outLength
> _decodeBufLen
) { // TODO: may we should make dynamic growing?
424 error("Sprite::decodeRLEBuffer outLength > _decodeBufLen");
427 outPointer
= _decodeBuf
;
428 outPointerEnd
= _decodeBuf
+ outLength
;
431 memset(outPointer
, 0, outLength
);
433 MemoryReadStream
readS(inputBuffer
, inLength
);
435 while (!readS
.eos() && (outPointer
< outPointerEnd
)) {
436 bg_runcount
= readS
.readByte();
439 fg_runcount
= readS
.readByte();
441 for (c
= 0; c
< bg_runcount
&& !readS
.eos(); c
++) {
442 *outPointer
= (byte
) 0;
443 if (outPointer
< outPointerEnd
)
449 for (c
= 0; c
< fg_runcount
&& !readS
.eos(); c
++) {
450 *outPointer
= readS
.readByte();
453 if (outPointer
< outPointerEnd
)
461 void Sprite::scaleBuffer(const byte
*src
, int width
, int height
, int scale
) {
462 byte skip
= 256 - scale
; // skip factor
463 byte vskip
= 0x80, hskip
;
464 byte
*dst
= _decodeBuf
;
466 for (int i
= 0; i
< height
; i
++) {
469 if (vskip
< skip
) { // We had an overflow
474 for (int j
= 0; j
< width
; j
++) {
478 if (hskip
< skip
) // overflow
486 } // End of namespace Saga