1 /* ScummVM - Graphic Adventure Engine
2  *
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.
6  *
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.
11  *
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.
16  *
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.
20  *
21  * This file contains the clipping rectangle code.
22  */
23 
24 #include "tinsel/cliprect.h"	// object clip rect defs
25 #include "tinsel/graphics.h"	// normal object drawing
26 #include "tinsel/object.h"
27 #include "tinsel/palette.h"
28 #include "tinsel/tinsel.h"		// for _vm
29 
30 namespace Tinsel {
31 
32 /**
33  * Resets the clipping rectangle allocator.
34  */
ResetClipRect()35 void ResetClipRect() {
36 	_vm->_clipRects.clear();
37 }
38 
39 /**
40  * Allocate a clipping rectangle from the free list.
41  * @param pClip			clip rectangle dimensions to allocate
42  */
AddClipRect(const Common::Rect & pClip)43 void AddClipRect(const Common::Rect &pClip) {
44 	_vm->_clipRects.push_back(pClip);
45 }
46 
GetClipRects()47 const RectList &GetClipRects() {
48 	return _vm->_clipRects;
49 }
50 
51 /**
52  * Creates the intersection of two rectangles.
53  * Returns True if there is a intersection.
54  * @param pDest			Pointer to destination rectangle that is to receive the intersection
55  * @param pSrc1			Pointer to a source rectangle
56  * @param pSrc2			Pointer to a source rectangle
57  */
IntersectRectangle(Common::Rect & pDest,const Common::Rect & pSrc1,const Common::Rect & pSrc2)58 bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
59 	pDest.left   = MAX(pSrc1.left, pSrc2.left);
60 	pDest.top    = MAX(pSrc1.top, pSrc2.top);
61 	pDest.right  = MIN(pSrc1.right, pSrc2.right);
62 	pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);
63 
64 	return !pDest.isEmpty();
65 }
66 
67 /**
68  * Creates the union of two rectangles.
69  * Returns True if there is a union.
70  * @param pDest			destination rectangle that is to receive the new union
71  * @param pSrc1			a source rectangle
72  * @param pSrc2			a source rectangle
73  */
UnionRectangle(Common::Rect & pDest,const Common::Rect & pSrc1,const Common::Rect & pSrc2)74 bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
75 	pDest.left   = MIN(pSrc1.left, pSrc2.left);
76 	pDest.top    = MIN(pSrc1.top, pSrc2.top);
77 	pDest.right  = MAX(pSrc1.right, pSrc2.right);
78 	pDest.bottom = MAX(pSrc1.bottom, pSrc2.bottom);
79 
80 	return !pDest.isEmpty();
81 }
82 
83 /**
84  * Check if the two rectangles are next to each other.
85  * @param pSrc1			a source rectangle
86  * @param pSrc2			a source rectangle
87  */
LooseIntersectRectangle(const Common::Rect & pSrc1,const Common::Rect & pSrc2)88 static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
89 	Common::Rect pDest;
90 
91 	pDest.left   = MAX(pSrc1.left, pSrc2.left);
92 	pDest.top    = MAX(pSrc1.top, pSrc2.top);
93 	pDest.right  = MIN(pSrc1.right, pSrc2.right);
94 	pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);
95 
96 	return pDest.isValidRect();
97 }
98 
99 /**
100  * Adds velocities and creates clipping rectangles for all the
101  * objects that have moved on the specified object list.
102  * @param pObjList			Playfield display list to draw
103  * @param pWin				Playfield window top left position
104  * @param pClip				Playfield clipping rectangle
105  * @param bNoVelocity		When reset, objects pos is updated with velocity
106  * @param bScrolled)		When set, playfield has scrolled
107  */
FindMovingObjects(OBJECT ** pObjList,Common::Point * pWin,Common::Rect * pClip,bool bNoVelocity,bool bScrolled)108 void FindMovingObjects(OBJECT **pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) {
109 	OBJECT *pObj;			// object list traversal pointer
110 
111 	for (pObj = *pObjList; pObj != NULL; pObj = pObj->pNext) {
112 		if (!bNoVelocity) {
113 			// we want to add velocities to objects position
114 
115 			if (bScrolled) {
116 				// this playfield has scrolled
117 
118 				// indicate change
119 				pObj->flags |= DMA_CHANGED;
120 			}
121 		}
122 
123 		if ((pObj->flags & DMA_CHANGED) ||	// object changed
124 			HasPalMoved(pObj->pPal)) {	// or palette moved
125 			// object has changed in some way
126 
127 			Common::Rect rcClip;	// objects clipped bounding rectangle
128 			Common::Rect rcObj;	// objects bounding rectangle
129 
130 			// calc intersection of objects previous bounding rectangle
131 			// NOTE: previous position is in screen co-ords
132 			if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) {
133 				// previous position is within clipping rect
134 				AddClipRect(rcClip);
135 			}
136 
137 			// calc objects current bounding rectangle
138 			if (pObj->flags & DMA_ABS) {
139 				// object position is absolute
140 				rcObj.left = fracToInt(pObj->xPos);
141 				rcObj.top  = fracToInt(pObj->yPos);
142 			} else {
143 				// object position is relative to window
144 				rcObj.left = fracToInt(pObj->xPos) - pWin->x;
145 				rcObj.top  = fracToInt(pObj->yPos) - pWin->y;
146 			}
147 			rcObj.right  = rcObj.left + pObj->width;
148 			rcObj.bottom = rcObj.top  + pObj->height;
149 
150 			// calc intersection of object with clipping rect
151 			if (IntersectRectangle(rcClip, rcObj, *pClip)) {
152 				// current position is within clipping rect
153 				AddClipRect(rcClip);
154 
155 				// update previous position
156 				pObj->rcPrev = rcClip;
157 			} else {
158 				// clear previous position
159 				pObj->rcPrev = Common::Rect();
160 			}
161 
162 			// clear changed flag
163 			pObj->flags &= ~DMA_CHANGED;
164 		}
165 	}
166 }
167 
168 /**
169  * Merges any clipping rectangles that overlap to try and reduce
170  * the total number of clip rectangles.
171  */
MergeClipRect()172 void MergeClipRect() {
173 	RectList &s_rectList = _vm->_clipRects;
174 
175 	if (s_rectList.size() <= 1)
176 		return;
177 
178 	RectList::iterator rOuter, rInner;
179 
180 	for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) {
181 		rInner = rOuter;
182 		while (++rInner != s_rectList.end()) {
183 
184 			if (LooseIntersectRectangle(*rOuter, *rInner)) {
185 				// these two rectangles overlap or
186 				// are next to each other - merge them
187 
188 				UnionRectangle(*rOuter, *rOuter, *rInner);
189 
190 				// remove the inner rect from the list
191 				s_rectList.erase(rInner);
192 
193 				// move back to beginning of list
194 				rInner = rOuter;
195 			}
196 		}
197 	}
198 }
199 
200 /**
201  * Redraws all objects within this clipping rectangle.
202  * @param pObjList		Object list to draw
203  * @param pWin			Window top left position
204  * @param pClip			Pointer to clip rectangle
205  */
UpdateClipRect(OBJECT ** pObjList,Common::Point * pWin,Common::Rect * pClip)206 void UpdateClipRect(OBJECT **pObjList, Common::Point *pWin, Common::Rect *pClip) {
207 	int x, y, right, bottom;	// object corners
208 	int hclip, vclip;			// total size of object clipping
209 	DRAWOBJECT currentObj;		// filled in to draw the current object in list
210 	OBJECT *pObj;				// object list iterator
211 
212 	// Initialize the fields of the drawing object to empty
213 	memset(&currentObj, 0, sizeof(DRAWOBJECT));
214 
215 	for (pObj = *pObjList; pObj != NULL; pObj = pObj->pNext) {
216 		if (pObj->flags & DMA_ABS) {
217 			// object position is absolute
218 			x = fracToInt(pObj->xPos);
219 			y = fracToInt(pObj->yPos);
220 		} else {
221 			// object position is relative to window
222 			x = fracToInt(pObj->xPos) - pWin->x;
223 			y = fracToInt(pObj->yPos) - pWin->y;
224 		}
225 
226 		// calc object right
227 		right = x + pObj->width;
228 		if (right < 0)
229 			// totally clipped if negative
230 			continue;
231 
232 		// calc object bottom
233 		bottom = y + pObj->height;
234 		if (bottom < 0)
235 			// totally clipped if negative
236 			continue;
237 
238 		// bottom clip = low right y - clip low right y
239 		currentObj.botClip = bottom - pClip->bottom;
240 		if (currentObj.botClip < 0) {
241 			// negative - object is not clipped
242 			currentObj.botClip = 0;
243 		}
244 
245 		// right clip = low right x - clip low right x
246 		currentObj.rightClip = right - pClip->right;
247 		if (currentObj.rightClip < 0) {
248 			// negative - object is not clipped
249 			currentObj.rightClip = 0;
250 		}
251 
252 		// top clip = clip top left y - top left y
253 		currentObj.topClip = pClip->top - y;
254 		if (currentObj.topClip < 0) {
255 			// negative - object is not clipped
256 			currentObj.topClip = 0;
257 		} else {	// clipped - adjust start position to top of clip rect
258 			y = pClip->top;
259 		}
260 
261 		// left clip = clip top left x - top left x
262 		currentObj.leftClip = pClip->left - x;
263 		if (currentObj.leftClip < 0) {
264 			// negative - object is not clipped
265 			currentObj.leftClip = 0;
266 		} else {
267 			// NOTE: This else statement is disabled in tinsel v1
268 			// clipped - adjust start position to left of clip rect
269 			x = pClip->left;
270 		}
271 
272 		// calc object total horizontal clipping
273 		hclip = currentObj.leftClip + currentObj.rightClip;
274 
275 		// calc object total vertical clipping
276 		vclip = currentObj.topClip + currentObj.botClip;
277 
278 		if (hclip + vclip != 0) {
279 			// object is clipped in some way
280 
281 			if (pObj->width <= hclip)
282 				// object totally clipped horizontally - ignore
283 				continue;
284 
285 			if (pObj->height <= vclip)
286 				// object totally clipped vertically - ignore
287 				continue;
288 
289 			// set clip bit in objects flags
290 			currentObj.flags = pObj->flags | DMA_CLIP;
291 		} else {	// object is not clipped - copy flags
292 			currentObj.flags = pObj->flags;
293 		}
294 
295 		// copy objects properties to local object
296 		currentObj.width    = pObj->width;
297 		currentObj.height   = pObj->height;
298 		currentObj.xPos     = (short)x;
299 		currentObj.yPos     = (short)y;
300 		currentObj.pPal     = pObj->pPal;
301 		currentObj.constant = pObj->constant;
302 		currentObj.hBits    = pObj->hBits;
303 
304 		// draw the object
305 		DrawObject(&currentObj);
306 	}
307 }
308 
309 } // End of namespace Tinsel
310