scummvm/scumm/boxes.cpp
Eugene Sandulenko 154c4af8dc Mass-renamed all variables of Actor object according to our current code
formatting standards. Earlier we had half of vars named with leading
underscore and half without it.

Now code in actor.cpp is considerably more readable.

svn-id: r17068
2005-03-11 01:10:06 +00:00

1270 lines
33 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2001 Ludvig Strigeus
* Copyright (C) 2001-2005 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*
*/
#include "stdafx.h"
#include "scumm/scumm.h"
#include "scumm/actor.h"
#include "scumm/boxes.h"
#include "common/util.h"
namespace Scumm {
#if !defined(__GNUC__)
#pragma START_PACK_STRUCTS
#endif
struct Box { /* Internal walkbox file format */
union {
struct {
byte uy;
byte ly;
byte ulx;
byte urx;
byte llx;
byte lrx;
byte mask;
byte flags;
} GCC_PACK v2;
struct {
int16 ulx, uly;
int16 urx, ury;
int16 lrx, lry;
int16 llx, lly;
byte mask;
byte flags;
uint16 scale;
} GCC_PACK old;
struct {
int32 ulx, uly;
int32 urx, ury;
int32 lrx, lry;
int32 llx, lly;
uint32 mask;
uint32 flags;
uint32 scaleSlot;
uint32 scale;
uint32 unk2;
uint32 unk3;
} GCC_PACK v8;
} GCC_PACK;
} GCC_PACK;
#if !defined(__GNUC__)
#pragma END_PACK_STRUCTS
#endif
#define BOX_MATRIX_SIZE 2000
#define BOX_DEBUG 0
static bool compareSlope(int X1, int Y1, int X2, int Y2, int X3, int Y3);
static Common::Point closestPtOnLine(int ulx, int uly, int llx, int lly, int x, int y);
byte ScummEngine::getMaskFromBox(int box) {
// Fix for bug #740244 and #755863. This appears to have been a
// long standing bug in the original engine?
if (_version <= 3 && box == 255)
return 1;
Box *ptr = getBoxBaseAddr(box);
if (!ptr)
return 0;
// WORKAROUND for bug #847827: This is a bug in the data files, as it also
// occurs with the original engine. We work around it here anyway.
if (_gameId == GID_INDY4 && _currentRoom == 225 && _roomResource == 94 && box == 8)
return 0;
if (_version == 8)
return (byte) FROM_LE_32(ptr->v8.mask);
else if (_version <= 2)
return ptr->v2.mask;
else
return ptr->old.mask;
}
void ScummEngine::setBoxFlags(int box, int val) {
debug(2, "setBoxFlags(%d, 0x%02x)", box, val);
/* SCUMM7+ stuff */
if (val & 0xC000) {
assert(box >= 0 && box < 65);
_extraBoxFlags[box] = val;
} else {
Box *ptr = getBoxBaseAddr(box);
assert(ptr);
if (_version == 8)
ptr->v8.flags = TO_LE_32(val);
else if (_version <= 2)
ptr->v2.flags = val;
else
ptr->old.flags = val;
}
}
byte ScummEngine::getBoxFlags(int box) {
Box *ptr = getBoxBaseAddr(box);
if (!ptr)
return 0;
if (_version == 8)
return (byte) FROM_LE_32(ptr->v8.flags);
else if (_version <= 2)
return ptr->v2.flags;
else
return ptr->old.flags;
}
void ScummEngine::setBoxScale(int box, int scale) {
Box *ptr = getBoxBaseAddr(box);
assert(ptr);
if (_version == 8)
ptr->v8.scale = TO_LE_32(scale);
else if (_version <= 2)
error("This should not ever be called!");
else
ptr->old.scale = TO_LE_16(scale);
}
void ScummEngine::setBoxScaleSlot(int box, int slot) {
Box *ptr = getBoxBaseAddr(box);
assert(ptr);
ptr->v8.scaleSlot = TO_LE_32(slot);
}
int ScummEngine::getScale(int box, int x, int y) {
if (_features & GF_NO_SCALING)
return 255;
Box *ptr = getBoxBaseAddr(box);
if (!ptr)
return 255;
int slot , scale;
if (_version == 8) {
// COMI has a separate field for the scale slot...
slot = FROM_LE_32(ptr->v8.scaleSlot);
scale = FROM_LE_32(ptr->v8.scale);
} else {
scale = READ_LE_UINT16(&ptr->old.scale);
if (scale & 0x8000)
slot = (scale & 0x7FFF) + 1;
else
slot = 0;
}
// Was a scale slot specified? If so, we compute the effective scale
// from it, ignoring the box scale.
if (slot)
scale = getScaleFromSlot(slot, x, y);
return scale;
}
int ScummEngine::getScaleFromSlot(int slot, int x, int y) {
assert(1 <= slot && slot <= ARRAYSIZE(_scaleSlots));
int scale;
int scaleX = 0, scaleY = 0;
ScaleSlot &s = _scaleSlots[slot-1];
if (s.y1 == s.y2 && s.x1 == s.x2)
error("Invalid scale slot %d", slot);
if (s.y1 != s.y2) {
if (y < 0)
y = 0;
scaleY = (s.scale2 - s.scale1) * (y - s.y1) / (s.y2 - s.y1) + s.scale1;
}
if (s.x1 == s.x2) {
scale = scaleY;
} else {
scaleX = (s.scale2 - s.scale1) * (x - s.x1) / (s.x2 - s.x1) + s.scale1;
if (s.y1 == s.y2) {
scale = scaleX;
} else {
scale = (scaleX + scaleY) / 2;
}
}
// Clip the scale to range 1-255
if (scale < 1)
scale = 1;
else if (scale > 255)
scale = 255;
return scale;
}
int ScummEngine::getBoxScale(int box) {
if (_features & GF_NO_SCALING)
return 255;
Box *ptr = getBoxBaseAddr(box);
if (!ptr)
return 255;
if (_version == 8)
return FROM_LE_32(ptr->v8.scale);
else
return READ_LE_UINT16(&ptr->old.scale);
}
/**
* Convert a rtScaleTable resource to a corresponding scale slot entry.
*
* At some point, we discovered that the old scale items (stored in rtScaleTable
* resources) are in fact the same as (or rather, a predecessor of) the
* scale slots used in COMI. While not being precomputed (and thus slightly
* slower), scale slots are more flexible, and most importantly, can cope with
* rooms higher than 200 pixels. That's an essential feature for DIG and FT
* and in fact the lack of it caused various bugs in the past.
*
* Hence, we decided to switch all games to use the more powerful scale slots.
* To accomodate old savegames, we attempt here to convert rtScaleTable
* resources to scale slots.
*/
void ScummEngine::convertScaleTableToScaleSlot(int slot) {
assert(1 <= slot && slot <= ARRAYSIZE(_scaleSlots));
byte *resptr = getResourceAddress(rtScaleTable, slot);
int lowerIdx, upperIdx;
float m, oldM;
// Do nothing if the given scale table doesn't exist
if (resptr == 0)
return;
if (resptr[0] == resptr[199]) {
// The scale is constant. This usually means we encountered one of the
// "broken" cases. We set pseudo scale item values which lead to a
// constant scale of 255.
setScaleSlot(slot, 0, 0, 255, 0, 199, 255);
return;
}
/*
* Essentially, what we are doing here is some kind of "line fitting"
* algorithm. The data in the scale table represents a linear graph. What
* we want to find is the slope and (vertical) offset of this line. Things
* are complicated by the fact that the line is cut of vertically at 1 and
* 255. We have to be careful in handling this and some border cases.
*
* Some typical graphs look like these:
* --- --- ---
* / --- \
* ___/ --- \___
*
* The method used here is to compute the slope of secants fixed at the
* left and right end. For most cases this detects the cut-over points
* quite accurately.
*/
// Search for the bend on the left side
m = (resptr[199] - resptr[0]) / 199.0;
for (lowerIdx = 0; lowerIdx < 199 && (resptr[lowerIdx] == 1 || resptr[lowerIdx] == 255); lowerIdx++) {
oldM = m;
m = (resptr[199] - resptr[lowerIdx+1]) / (float)(199 - (lowerIdx+1));
if (m > 0) {
if (m <= oldM)
break;
} else {
if (m >= oldM)
break;
}
}
// Search for the bend on the right side
m = (resptr[199] - resptr[0]) / 199.0;
for (upperIdx = 199; upperIdx > 1 && (resptr[upperIdx] == 1 || resptr[upperIdx] == 255); upperIdx--) {
oldM = m;
m = (resptr[upperIdx-1] - resptr[0]) / (float)(upperIdx-1);
if (m > 0) {
if (m <= oldM)
break;
} else {
if (m >= oldM)
break;
}
}
// If lowerIdx and upperIdx are equal, we assume that there
// was no bend at all, and go for the maximum range.
if (lowerIdx == upperIdx) {
lowerIdx = 0;
upperIdx = 199;
}
// The values of y1 and y2, as well as the scale, can now easily be computed
setScaleSlot(slot, 0, lowerIdx, resptr[lowerIdx], 0, upperIdx, resptr[upperIdx]);
// Compute the variance, for debugging. It shouldn't exceed 1
ScaleSlot &s = _scaleSlots[slot-1];
int y;
int sum = 0;
int scale;
float variance;
for (y = 0; y < 200; y++) {
scale = (s.scale2 - s.scale1) * (y - s.y1) / (s.y2 - s.y1) + s.scale1;
if (scale < 1)
scale = 1;
else if (scale > 255)
scale = 255;
sum += (resptr[y] - scale) * (resptr[y] - scale);
}
variance = sum / (200.0 - 1.0);
if (variance > 1)
debug(1, "scale item %d, variance %f exceeds 1 (room %d)", slot, variance, _currentRoom);
}
void ScummEngine::setScaleSlot(int slot, int x1, int y1, int scale1, int x2, int y2, int scale2) {
assert(1 <= slot && slot <= ARRAYSIZE(_scaleSlots));
ScaleSlot &s = _scaleSlots[slot-1];
s.x2 = x2;
s.y2 = y2;
s.scale2 = scale2;
s.x1 = x1;
s.y1 = y1;
s.scale1 = scale1;
}
byte ScummEngine::getNumBoxes() {
byte *ptr = getResourceAddress(rtMatrix, 2);
if (!ptr)
return 0;
if (_version == 8)
return (byte) READ_LE_UINT32(ptr);
else
return ptr[0];
}
Box *ScummEngine::getBoxBaseAddr(int box) {
byte *ptr = getResourceAddress(rtMatrix, 2);
if (!ptr || box == 255)
return NULL;
// FIXME: In "pass to adventure", the loom demo, when bobbin enters
// the tent to the elders, box = 2, but ptr[0] = 2 -> errors out.
// Hence we disable the check for now. Maybe in PASS (and other old games)
// we shouldn't subtract 1 from ptr[0] when performing the check?
// this also seems to be incorrect for atari st demo of zak
// and assumingly other v2 games
// The same happens in Indy3EGA (see bug #770351)
// Also happens in ZakEGA (see bug #771803).
//
// This *might* mean that we have a bug in our box implementation
// OTOH, the original engine, unlike ScummVM, performed no bound
// checking at all. All the problems so far have been cases where
// the value was exactly one more than what we consider the maximum.
// So it's very well possible that all of these are script errors.
if (_version <= 4 && ptr[0] == box)
box--;
checkRange(ptr[0] - 1, 0, box, "Illegal box %d");
if (_version <= 2)
return (Box *)(ptr + box * SIZEOF_BOX_V2 + 1);
else if (_version == 3)
return (Box *)(ptr + box * SIZEOF_BOX_V3 + 1);
else if (_features & GF_SMALL_HEADER)
return (Box *)(ptr + box * SIZEOF_BOX + 1);
else if (_version == 8)
return (Box *)(ptr + box * SIZEOF_BOX_V8 + 4);
else
return (Box *)(ptr + box * SIZEOF_BOX + 2);
}
int ScummEngine::getSpecialBox(int x, int y) {
int i;
int numOfBoxes;
byte flag;
numOfBoxes = getNumBoxes() - 1;
for (i = numOfBoxes; i >= 0; i--) {
flag = getBoxFlags(i);
if (!(flag & kBoxInvisible) && (flag & kBoxPlayerOnly))
return (-1);
if (checkXYInBoxBounds(i, x, y))
return (i);
}
return (-1);
}
bool ScummEngine::checkXYInBoxBounds(int b, int x, int y) {
BoxCoords box;
if (b < 0 || b == Actor::kInvalidBox)
return false;
getBoxCoordinates(b, &box);
if (x < box.ul.x && x < box.ur.x && x < box.lr.x && x < box.ll.x)
return false;
if (x > box.ul.x && x > box.ur.x && x > box.lr.x && x > box.ll.x)
return false;
if (y < box.ul.y && y < box.ur.y && y < box.lr.y && y < box.ll.y)
return false;
if (y > box.ul.y && y > box.ur.y && y > box.lr.y && y > box.ll.y)
return false;
if (box.ul.x == box.ur.x && box.ul.y == box.ur.y && box.lr.x == box.ll.x && box.lr.y == box.ll.y ||
box.ul.x == box.ll.x && box.ul.y == box.ll.y && box.ur.x == box.lr.x && box.ur.y == box.lr.y) {
Common::Point pt;
pt = closestPtOnLine(box.ul.x, box.ul.y, box.lr.x, box.lr.y, x, y);
if (distanceFromPt(x, y, pt.x, pt.y) <= 4)
return true;
}
if (!compareSlope(box.ul.x, box.ul.y, box.ur.x, box.ur.y, x, y))
return false;
if (!compareSlope(box.ur.x, box.ur.y, box.lr.x, box.lr.y, x, y))
return false;
if (!compareSlope(box.ll.x, box.ll.y, x, y, box.lr.x, box.lr.y))
return false;
if (!compareSlope(box.ul.x, box.ul.y, x, y, box.ll.x, box.ll.y))
return false;
return true;
}
void ScummEngine::getBoxCoordinates(int boxnum, BoxCoords *box) {
Box *bp = getBoxBaseAddr(boxnum);
assert(bp);
if (_version == 8) {
box->ul.x = (short)FROM_LE_32(bp->v8.ulx);
box->ul.y = (short)FROM_LE_32(bp->v8.uly);
box->ur.x = (short)FROM_LE_32(bp->v8.urx);
box->ur.y = (short)FROM_LE_32(bp->v8.ury);
box->ll.x = (short)FROM_LE_32(bp->v8.llx);
box->ll.y = (short)FROM_LE_32(bp->v8.lly);
box->lr.x = (short)FROM_LE_32(bp->v8.lrx);
box->lr.y = (short)FROM_LE_32(bp->v8.lry);
// FIXME: Some walkboxes in CMI appear to have been flipped,
// in the sense that for instance the lower boundary is above
// the upper one. Can that really be the case, or is there
// some more sinister problem afoot?
//
// Is this fix sufficient, or will we need something more
// elaborate?
if (box->ul.y > box->ll.y && box->ur.y > box->lr.y) {
SWAP(box->ul, box->ll);
SWAP(box->ur, box->lr);
}
if (box->ul.x > box->ur.x && box->ll.x > box->lr.x) {
SWAP(box->ul, box->ur);
SWAP(box->ll, box->lr);
}
} else if (_version <= 2) {
box->ul.x = bp->v2.ulx * 8;
box->ul.y = bp->v2.uy * 2;
box->ur.x = bp->v2.urx * 8;
box->ur.y = bp->v2.uy * 2;
box->ll.x = bp->v2.llx * 8;
box->ll.y = bp->v2.ly * 2;
box->lr.x = bp->v2.lrx * 8;
box->lr.y = bp->v2.ly * 2;
} else {
box->ul.x = (int16)READ_LE_UINT16(&bp->old.ulx);
box->ul.y = (int16)READ_LE_UINT16(&bp->old.uly);
box->ur.x = (int16)READ_LE_UINT16(&bp->old.urx);
box->ur.y = (int16)READ_LE_UINT16(&bp->old.ury);
box->ll.x = (int16)READ_LE_UINT16(&bp->old.llx);
box->ll.y = (int16)READ_LE_UINT16(&bp->old.lly);
box->lr.x = (int16)READ_LE_UINT16(&bp->old.lrx);
box->lr.y = (int16)READ_LE_UINT16(&bp->old.lry);
}
}
uint ScummEngine::distanceFromPt(int x, int y, int ptx, int pty) {
int diffx, diffy;
diffx = abs(ptx - x);
if (diffx >= 0x1000)
return 0xFFFFFF;
diffy = abs(pty - y);
if (diffy >= 0x1000)
return 0xFFFFFF;
diffx *= diffx;
diffy *= diffy;
return diffx + diffy;
}
bool compareSlope(int X1, int Y1, int X2, int Y2, int X3, int Y3) {
return (Y2 - Y1) * (X3 - X1) <= (Y3 - Y1) * (X2 - X1);
}
Common::Point closestPtOnLine(int ulx, int uly, int llx, int lly, int x, int y) {
int lydiff, lxdiff;
int32 dist, a, b, c;
int x2, y2;
Common::Point pt;
if (llx == ulx) { // Vertical line?
x2 = ulx;
y2 = y;
} else if (lly == uly) { // Horizontal line?
x2 = x;
y2 = uly;
} else {
lydiff = lly - uly;
lxdiff = llx - ulx;
if (abs(lxdiff) > abs(lydiff)) {
dist = lxdiff * lxdiff + lydiff * lydiff;
a = ulx * lydiff / lxdiff;
b = x * lxdiff / lydiff;
c = (a + b - uly + y) * lydiff * lxdiff / dist;
x2 = c;
y2 = c * lydiff / lxdiff - a + uly;
} else {
dist = lydiff * lydiff + lxdiff * lxdiff;
a = uly * lxdiff / lydiff;
b = y * lydiff / lxdiff;
c = (a + b - ulx + x) * lydiff * lxdiff / dist;
y2 = c;
x2 = c * lxdiff / lydiff - a + ulx;
}
}
lxdiff = llx - ulx;
lydiff = lly - uly;
if (abs(lydiff) < abs(lxdiff)) {
if (lxdiff > 0) {
if (x2 < ulx) {
type1:;
x2 = ulx;
y2 = uly;
} else if (x2 > llx) {
type2:;
x2 = llx;
y2 = lly;
}
} else {
if (x2 > ulx)
goto type1;
if (x2 < llx)
goto type2;
}
} else {
if (lydiff > 0) {
if (y2 < uly)
goto type1;
if (y2 > lly)
goto type2;
} else {
if (y2 > uly)
goto type1;
if (y2 < lly)
goto type2;
}
}
pt.x = x2;
pt.y = y2;
return pt;
}
bool ScummEngine::inBoxQuickReject(int b, int x, int y, int threshold) {
int t;
BoxCoords box;
getBoxCoordinates(b, &box);
t = x - threshold;
if (t > box.ul.x && t > box.ur.x && t > box.lr.x && t > box.ll.x)
return true;
t = x + threshold;
if (t < box.ul.x && t < box.ur.x && t < box.lr.x && t < box.ll.x)
return true;
t = y - threshold;
if (t > box.ul.y && t > box.ur.y && t > box.lr.y && t > box.ll.y)
return true;
t = y + threshold;
if (t < box.ul.y && t < box.ur.y && t < box.lr.y && t < box.ll.y)
return true;
return false;
}
int ScummEngine::getClosestPtOnBox(int b, int x, int y, int16& outX, int16& outY) {
Common::Point pt;
uint dist;
uint bestdist = 0xFFFFFF;
BoxCoords box;
getBoxCoordinates(b, &box);
pt = closestPtOnLine(box.ul.x, box.ul.y, box.ur.x, box.ur.y, x, y);
dist = distanceFromPt(x, y, pt.x, pt.y);
if (dist < bestdist) {
bestdist = dist;
outX = pt.x;
outY = pt.y;
}
pt = closestPtOnLine(box.ur.x, box.ur.y, box.lr.x, box.lr.y, x, y);
dist = distanceFromPt(x, y, pt.x, pt.y);
if (dist < bestdist) {
bestdist = dist;
outX = pt.x;
outY = pt.y;
}
pt = closestPtOnLine(box.lr.x, box.lr.y, box.ll.x, box.ll.y, x, y);
dist = distanceFromPt(x, y, pt.x, pt.y);
if (dist < bestdist) {
bestdist = dist;
outX = pt.x;
outY = pt.y;
}
pt = closestPtOnLine(box.ll.x, box.ll.y, box.ul.x, box.ul.y, x, y);
dist = distanceFromPt(x, y, pt.x, pt.y);
if (dist < bestdist) {
bestdist = dist;
outX = pt.x;
outY = pt.y;
}
return bestdist;
}
byte *ScummEngine::getBoxMatrixBaseAddr() {
byte *ptr = getResourceAddress(rtMatrix, 1);
assert(ptr);
if (*ptr == 0xFF)
ptr++;
return ptr;
}
/*
* Compute if there is a way that connects box 'from' with box 'to'.
* Returns the number of a box adjactant to 'from' that is the next on the
* way to 'to' (this can be 'to' itself or a third box).
* If there is no connection -1 is return.
*/
int ScummEngine::getPathToDestBox(byte from, byte to) {
const byte *boxm;
byte i;
const int numOfBoxes = getNumBoxes();
int dest = -1;
if (from == to)
return to;
if (to == Actor::kInvalidBox)
return -1;
if (from == Actor::kInvalidBox)
return to;
assert(from < numOfBoxes);
assert(to < numOfBoxes);
boxm = getBoxMatrixBaseAddr();
if (_version <= 2) {
// The v2 box matrix is a real matrix with numOfBoxes rows and columns.
// The first numOfBoxes bytes contain indices to the start of the corresponding
// row (although that seems unnecessary to me - the value is easily computable.
boxm += numOfBoxes + boxm[from];
return (int8)boxm[to];
}
// WORKAROUND #1: It seems that in some cases, the box matrix is corrupt
// (more precisely, is too short) in the datafiles already. In
// particular this seems to be the case in room 46 of Indy3 EGA (see
// also bug #770690). This didn't cause problems in the original
// engine, because there, the memory layout is different. After the
// walkbox would follow the rest of the room file, thus the program
// always behaved the same (and by chance, correct). Not so for us,
// since random data may follow after the resource in ScummVM.
//
// As a workaround, we add a check for the end of the box matrix
// resource, and abort the search once we reach the end.
const byte *end = boxm + getResourceSize(rtMatrix, 1);
// WORKAROUND #2: In addition to the above, we have to add this special
// case to fix the scene in Indy3 where Indy meets Hitler in Berlin.
// See bug #770690 and also bug #774783.
if ((_gameId == GID_INDY3) && _roomResource == 46 && from == 1 && to == 0)
return 0;
// Skip up to the matrix data for box 'from'
for (i = 0; i < from && boxm < end; i++) {
while (boxm < end && *boxm != 0xFF)
boxm += 3;
boxm++;
}
// Now search for the entry for box 'to'
while (boxm < end && boxm[0] != 0xFF) {
if (boxm[0] <= to && to <= boxm[1])
dest = (int8)boxm[2];
boxm += 3;
}
if (boxm >= end)
warning("The box matrix apparently is truncated (room %d)", _roomResource);
return dest;
}
/*
* Computes the next point actor a has to walk towards in a straight
* line in order to get from box1 to box3 via box2.
*/
bool Actor::findPathTowards(byte box1nr, byte box2nr, byte box3nr, Common::Point &foundPath) {
BoxCoords box1;
BoxCoords box2;
Common::Point tmp;
int i, j;
int flag;
int q, pos;
_vm->getBoxCoordinates(box1nr, &box1);
_vm->getBoxCoordinates(box2nr, &box2);
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
if (box1.ul.x == box1.ur.x && box1.ul.x == box2.ul.x && box1.ul.x == box2.ur.x) {
flag = 0;
if (box1.ul.y > box1.ur.y) {
SWAP(box1.ul.y, box1.ur.y);
flag |= 1;
}
if (box2.ul.y > box2.ur.y) {
SWAP(box2.ul.y, box2.ur.y);
flag |= 2;
}
if (box1.ul.y > box2.ur.y || box2.ul.y > box1.ur.y ||
(box1.ur.y == box2.ul.y || box2.ur.y == box1.ul.y) &&
box1.ul.y != box1.ur.y && box2.ul.y != box2.ur.y) {
if (flag & 1)
SWAP(box1.ul.y, box1.ur.y);
if (flag & 2)
SWAP(box2.ul.y, box2.ur.y);
} else {
pos = _pos.y;
if (box2nr == box3nr) {
int diffX = _walkdata.dest.x - _pos.x;
int diffY = _walkdata.dest.y - _pos.y;
int boxDiffX = box1.ul.x - _pos.x;
if (diffX != 0) {
int t;
diffY *= boxDiffX;
t = diffY / diffX;
if (t == 0 && (diffY <= 0 || diffX <= 0)
&& (diffY >= 0 || diffX >= 0))
t = -1;
pos = _pos.y + t;
}
}
q = pos;
if (q < box2.ul.y)
q = box2.ul.y;
if (q > box2.ur.y)
q = box2.ur.y;
if (q < box1.ul.y)
q = box1.ul.y;
if (q > box1.ur.y)
q = box1.ur.y;
if (q == pos && box2nr == box3nr)
return true;
foundPath.y = q;
foundPath.x = box1.ul.x;
return false;
}
}
if (box1.ul.y == box1.ur.y && box1.ul.y == box2.ul.y && box1.ul.y == box2.ur.y) {
flag = 0;
if (box1.ul.x > box1.ur.x) {
SWAP(box1.ul.x, box1.ur.x);
flag |= 1;
}
if (box2.ul.x > box2.ur.x) {
SWAP(box2.ul.x, box2.ur.x);
flag |= 2;
}
if (box1.ul.x > box2.ur.x || box2.ul.x > box1.ur.x ||
(box1.ur.x == box2.ul.x || box2.ur.x == box1.ul.x) &&
box1.ul.x != box1.ur.x && box2.ul.x != box2.ur.x) {
if (flag & 1)
SWAP(box1.ul.x, box1.ur.x);
if (flag & 2)
SWAP(box2.ul.x, box2.ur.x);
} else {
if (box2nr == box3nr) {
int diffX = _walkdata.dest.x - _pos.x;
int diffY = _walkdata.dest.y - _pos.y;
int boxDiffY = box1.ul.y - _pos.y;
pos = _pos.x;
if (diffY != 0) {
pos += diffX * boxDiffY / diffY;
}
} else {
pos = _pos.x;
}
q = pos;
if (q < box2.ul.x)
q = box2.ul.x;
if (q > box2.ur.x)
q = box2.ur.x;
if (q < box1.ul.x)
q = box1.ul.x;
if (q > box1.ur.x)
q = box1.ur.x;
if (q == pos && box2nr == box3nr)
return true;
foundPath.x = q;
foundPath.y = box1.ul.y;
return false;
}
}
tmp = box1.ul;
box1.ul = box1.ur;
box1.ur = box1.lr;
box1.lr = box1.ll;
box1.ll = tmp;
}
tmp = box2.ul;
box2.ul = box2.ur;
box2.ur = box2.lr;
box2.lr = box2.ll;
box2.ll = tmp;
}
return false;
}
#if BOX_DEBUG
static void printMatrix(byte *boxm, int num) {
int i;
for (i = 0; i < num; i++) {
printf("%d: ", i);
while (*boxm != 0xFF) {
printf("%d, ", *boxm);
boxm++;
}
boxm++;
printf("\n");
}
}
static void printMatrix2(byte *matrix, int num) {
int i, j;
printf(" ");
for (i = 0; i < num; i++)
printf("%2d ", i);
printf("\n");
for (i = 0; i < num; i++) {
printf("%2d: ", i);
for (j = 0; j < num; j++) {
int val = matrix[i * 64 + j];
if (val == Actor::kInvalidBox)
printf(" ? ");
else
printf("%2d ", val);
}
printf("\n");
}
}
#endif
void ScummEngine::createBoxMatrix() {
int num, i, j, k;
byte *adjacentMatrix, *itineraryMatrix;
// The total number of boxes
num = getNumBoxes();
assert(num <= 64);
// Allocate the adjacent & itinerary matrices
adjacentMatrix = (byte *)malloc(64 * 64);
itineraryMatrix = (byte *)malloc(64 * 64);
// Initialise the adjacent matrix: each box has distance 0 to itself,
// and distance 1 to its direct neighbors. Initially, it has distance
// 255 (= infinity) to all other boxes.
for (i = 0; i < num; i++) {
for (j = 0; j < num; j++) {
if (i == j) {
adjacentMatrix[i * 64 + j] = 0;
itineraryMatrix[i * 64 + j] = j;
} else if (areBoxesNeighbours(i, j)) {
adjacentMatrix[i * 64 + j] = 1;
itineraryMatrix[i * 64 + j] = j;
} else {
adjacentMatrix[i * 64 + j] = 255;
itineraryMatrix[i * 64 + j] = Actor::kInvalidBox;
}
}
}
// Compute the shortest routes between boxes via Kleene's algorithm.
// The original code used some kind of mangled Dijkstra's algorithm;
// while that might in theory be slightly faster, it was
// a) extremly obfuscated
// b) incorrect: it didn't always find the shortest paths
// c) not any faster in reality for our sparse & small adjacent matrices
for (k = 0; k < num; k++) {
for (i = 0; i < num; i++) {
for (j = 0; j < num; j++) {
if (i == j)
continue;
byte distIK = adjacentMatrix[64 * i + k];
byte distKJ = adjacentMatrix[64 * k + j];
if (adjacentMatrix[64 * i + j] > distIK + distKJ) {
adjacentMatrix[64 * i + j] = distIK + distKJ;
itineraryMatrix[64 * i + j] = itineraryMatrix[64 * i + k];
}
}
}
}
// "Compress" the distance matrix into the box matrix format used
// by the engine. The format is like this:
// For each box (from 0 to num) there is first a byte with value 0xFF,
// followed by an arbitrary number of byte triples; the end is marked
// again by the lead 0xFF for the next "row". The meaning of the
// byte triples is as follows: the first two bytes define a range
// of box numbers (e.g. 7-11), while the third byte defines an
// itineray box. Assuming we are in the 5th "row" and encounter
// the triplet 7,11,15: this means to get from box 5 to any of
// the boxes 7,8,9,10,11 the shortest way is to go via box 15.
// See also getPathToDestBox.
byte *matrixStart = createResource(rtMatrix, 1, BOX_MATRIX_SIZE);
const byte *matrixEnd = matrixStart + BOX_MATRIX_SIZE;
#define addToMatrix(b) do { *matrixStart++ = (b); assert(matrixStart < matrixEnd); } while (0)
for (i = 0; i < num; i++) {
addToMatrix(0xFF);
for (j = 0; j < num; j++) {
byte itinerary = itineraryMatrix[64 * i + j];
if (itinerary != Actor::kInvalidBox) {
addToMatrix(j);
while (j < num - 1 && itinerary == itineraryMatrix[64 * i + (j + 1)])
j++;
addToMatrix(j);
addToMatrix(itinerary);
}
}
}
addToMatrix(0xFF);
#if BOX_DEBUG
printf("Itinerary matrix:\n");
printMatrix2(itineraryMatrix, num);
printf("compressed matrix:\n");
printMatrix(getBoxMatrixBaseAddr(), num);
#endif
free(adjacentMatrix);
free(itineraryMatrix);
}
/** Check if two boxes are neighbours. */
bool ScummEngine::areBoxesNeighbours(int box1nr, int box2nr) {
Common::Point tmp;
BoxCoords box;
BoxCoords box2;
if (getBoxFlags(box1nr) & kBoxInvisible || getBoxFlags(box2nr) & kBoxInvisible)
return false;
getBoxCoordinates(box1nr, &box2);
getBoxCoordinates(box2nr, &box);
// Roughly, the idea of this algorithm is to check if we can find
// two sides of the two given boxes which touch.
// In order to keep te code simple, we only match the upper sides;
// then, we "rotate" the box coordinates four times each, for a total
// of 16 comparisions (four sides compared with four sides each).
for (int j = 0; j < 4; j++) {
for (int k = 0; k < 4; k++) {
// Are the "upper" sides of the boxes on a single vertical line
// (i.e. all share one x value) ?
if (box2.ur.x == box2.ul.x && box.ul.x == box2.ul.x && box.ur.x == box2.ul.x) {
bool swappedBox2 = false, swappedBox1 = false;
if (box2.ur.y < box2.ul.y) {
swappedBox2 = 1;
SWAP(box2.ur.y, box2.ul.y);
}
if (box.ur.y < box.ul.y) {
swappedBox1 = 1;
SWAP(box.ur.y, box.ul.y);
}
if (box.ur.y < box2.ul.y ||
box.ul.y > box2.ur.y ||
(box.ul.y == box2.ur.y ||
box.ur.y == box2.ul.y) && box2.ur.y != box2.ul.y && box.ul.y != box.ur.y) {
} else {
return true;
}
// Swap back if necessary
if (swappedBox2) {
SWAP(box2.ur.y, box2.ul.y);
}
if (swappedBox1) {
SWAP(box.ur.y, box.ul.y);
}
}
// Are the "upper" sides of the boxes on a single horizontal line
// (i.e. all share one y value) ?
if (box2.ur.y == box2.ul.y && box.ul.y == box2.ul.y && box.ur.y == box2.ul.y) {
bool swappedBox2 = false, swappedBox1 = false;
if (box2.ur.x < box2.ul.x) {
swappedBox2 = 1;
SWAP(box2.ur.x, box2.ul.x);
}
if (box.ur.x < box.ul.x) {
swappedBox1 = 1;
SWAP(box.ur.x, box.ul.x);
}
if (box.ur.x < box2.ul.x ||
box.ul.x > box2.ur.x ||
(box.ul.x == box2.ur.x ||
box.ur.x == box2.ul.x) && box2.ur.x != box2.ul.x && box.ul.x != box.ur.x) {
} else {
return true;
}
// Swap back if necessary
if (swappedBox2) {
SWAP(box2.ur.x, box2.ul.x);
}
if (swappedBox1) {
SWAP(box.ur.x, box.ul.x);
}
}
// "Rotate" the box coordinates
tmp = box2.ul;
box2.ul = box2.ur;
box2.ur = box2.lr;
box2.lr = box2.ll;
box2.ll = tmp;
}
// "Rotate" the box coordinates
tmp = box.ul;
box.ul = box.ur;
box.ur = box.lr;
box.lr = box.ll;
box.ll = tmp;
}
return false;
}
void Actor::findPathTowardsOld(byte box1, byte box2, byte finalBox, Common::Point &p2, Common::Point &p3) {
Common::Point pt;
Common::Point gateA[2];
Common::Point gateB[2];
_vm->getGates(box1, box2, gateA, gateB);
p2.x = 32000;
p3.x = 32000;
// next box (box2) = final box?
if (box2 == finalBox) {
// In Indy3, the masks (= z-level) have to match, too -- needed for the
// 'maze' in the zeppeling (see bug #1032964).
if (_vm->_gameId != GID_INDY3 || _vm->getMaskFromBox(box1) == _vm->getMaskFromBox(box2)) {
// Is the actor (x,y) between both gates?
if (compareSlope(_pos.x, _pos.y, _walkdata.dest.x, _walkdata.dest.y, gateA[0].x, gateA[0].y) !=
compareSlope(_pos.x, _pos.y, _walkdata.dest.x, _walkdata.dest.y, gateB[0].x, gateB[0].y) &&
compareSlope(_pos.x, _pos.y, _walkdata.dest.x, _walkdata.dest.y, gateA[1].x, gateA[1].y) !=
compareSlope(_pos.x, _pos.y, _walkdata.dest.x, _walkdata.dest.y, gateB[1].x, gateB[1].y)) {
return;
}
}
}
p3 = pt = closestPtOnLine(gateA[1].x, gateA[1].y, gateB[1].x, gateB[1].y, _pos.x, _pos.y);
if (compareSlope(_pos.x, _pos.y, p3.x, p3.y, gateA[0].x, gateA[0].y) ==
compareSlope(_pos.x, _pos.y, p3.x, p3.y, gateB[0].x, gateB[0].y)) {
closestPtOnLine(gateA[0].x, gateA[0].y, gateB[0].x, gateB[0].y, _pos.x, _pos.y);
p2 = pt; // if point 2 between gates, ignore!
}
}
/**
* Compute the "gate" between two boxes. The gate is a pair of two lines which
* both start on box 'box1' and end on 'box2'. For both lines, one of its
* end points is the corner point of one of the two boxes. The other end point
* is a point on the other point closest to first end point.
* This way the lines bound a 'corridor' between the two boxes, through which
* the actor has to walk to get from box1 to box2.
*/
void ScummEngine::getGates(int box1, int box2, Common::Point gateA[2], Common::Point gateB[2]) {
int i, j;
int dist[8];
int minDist[3];
int closest[3];
int box[3];
BoxCoords coords;
Common::Point closestPoint[8];
Common::Point boxCorner[8];
int line1, line2;
// For all corner coordinates of the first box, compute the point closest
// to them on the second box (and also compute the distance of these points).
getBoxCoordinates(box1, &coords);
boxCorner[0] = coords.ul;
boxCorner[1] = coords.ur;
boxCorner[2] = coords.lr;
boxCorner[3] = coords.ll;
for (i = 0; i < 4; i++) {
dist[i] = getClosestPtOnBox(box2, boxCorner[i].x, boxCorner[i].y, closestPoint[i].x, closestPoint[i].y);
}
// Now do the same but with the roles of the first and second box swapped.
getBoxCoordinates(box2, &coords);
boxCorner[4] = coords.ul;
boxCorner[5] = coords.ur;
boxCorner[6] = coords.lr;
boxCorner[7] = coords.ll;
for (i = 4; i < 8; i++) {
dist[i] = getClosestPtOnBox(box1, boxCorner[i].x, boxCorner[i].y, closestPoint[i].x, closestPoint[i].y);
}
// Find the three closest "close" points between the two boxes.
for (j = 0; j < 3; j++) {
minDist[j] = 0xFFFF;
for (i = 0; i < 8; i++) {
if (dist[i] < minDist[j]) {
minDist[j] = dist[i];
closest[j] = i;
}
}
dist[closest[j]] = 0xFFFF;
minDist[j] = (int)sqrt((double)minDist[j]);
box[j] = (closest[j] > 3); // Is the point on the first or on the second box?
}
// Finally, compute the actual "gate".
if (box[0] == box[1] && abs(minDist[0] - minDist[1]) < 4) {
line1 = closest[0];
line2 = closest[1];
} else if (box[0] == box[1] && minDist[0] == minDist[1]) { // parallel
line1 = closest[0];
line2 = closest[1];
} else if (box[0] == box[2] && minDist[0] == minDist[2]) { // parallel
line1 = closest[0];
line2 = closest[2];
} else if (box[1] == box[2] && minDist[1] == minDist[2]) { // parallel
line1 = closest[1];
line2 = closest[2];
} else if (box[0] == box[2] && abs(minDist[0] - minDist[2]) < 4) {
line1 = closest[0];
line2 = closest[2];
} else if (abs(minDist[0] - minDist[2]) < 4) {
line1 = closest[1];
line2 = closest[2];
} else if (abs(minDist[0] - minDist[1]) < 4) {
line1 = closest[0];
line2 = closest[1];
} else {
line1 = closest[0];
line2 = closest[0];
}
// Set the gate
if (line1 < 4) {
gateA[0] = boxCorner[line1];
gateA[1] = closestPoint[line1];
} else {
gateA[1] = boxCorner[line1];
gateA[0] = closestPoint[line1];
}
if (line2 < 4) {
gateB[0] = boxCorner[line2];
gateB[1] = closestPoint[line2];
} else {
gateB[1] = boxCorner[line2];
gateB[0] = closestPoint[line2];
}
}
} // End of namespace Scumm