/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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 3 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, see . * */ #include "trecision/actor.h" #include "trecision/animtype.h" #include "trecision/pathfinding3d.h" #include "trecision/sound.h" #include "trecision/trecision.h" #include "trecision/video.h" namespace Trecision { PathFinding3D::PathFinding3D(TrecisionEngine *vm) : _vm(vm) { _lookX = 0.0f; _lookZ = 0.0f; _curStep = 0; _lastStep = 0; _characterInMovement = false; _characterGoToPosition = -1; _actorPos = 0; _forcedActorPos = 0; _panelNum = 0; _curPanel = -1; _oldPanel = -1; for (int i = 0; i < MAXPATHNODES; ++i) _pathNode[i].clear(); _numPathNodes = 0; _numSortPanel = 0; _x3d = 0.0f; _y3d = 0.0f; _z3d = 0.0f; _curX = 0.0f; _curZ = 0.0f; for (int i = 0; i < 3; ++i) { _invP[i][0] = 0.0f; _invP[i][1] = 0.0f; _invP[i][2] = 0.0f; } for (int i = 0; i < MAXPANELSINROOM; ++i) _panel[i].clear(); for (int i = 0; i < 32; ++i) { _sortPan[i]._num = 0; _sortPan[i]._min = 0.0f; } for (int i = 0; i < MAXSTEP; ++i) _step[i].clear(); } PathFinding3D::~PathFinding3D() { } void PathFinding3D::findPath() { Actor *actor = _vm->_actor; actor->_px += actor->_dx; actor->_pz += actor->_dz; int inters = 0; _numPathNodes = 0; // if you have clicked behind the starting panel or the corner it's not possible to walk if (_curPanel < 0 && _oldPanel >= 0) { int16 b; // behind the starting panel if (pointInside(b = _oldPanel, _curX, _curZ) || // behind the panel corner1 ((_vm->dist2D(_panel[_oldPanel]._x1, _panel[_oldPanel]._z1, actor->_px, actor->_pz) < EPSILON) && (pointInside(b = _panel[_oldPanel]._nearPanel1, _curX, _curZ) || pointInside(b = _panel[_oldPanel]._nearPanel2, _curX, _curZ))) || // behind the panel corner2 ((_vm->dist2D(_panel[_oldPanel]._x2, _panel[_oldPanel]._z2, actor->_px, actor->_pz) < EPSILON) && (pointInside(b = _panel[_oldPanel]._nearPanel2, _curX, _curZ) || pointInside(b = _panel[_oldPanel]._nearPanel1, _curX, _curZ)))) { _curX = actor->_px; _curZ = actor->_pz; actor->_px -= actor->_dx; actor->_pz -= actor->_dz; _curPanel = b; _numPathNodes = 0; lookAt(_lookX, _lookZ); return; } } float dist = _vm->dist2D(actor->_px, actor->_pz, _curX, _curZ); for (int i = 0; i < _panelNum; ++i) { if (_panel[i]._flags & 0x80000000) { // it must be a wide panel if (intersectLineLine(_panel[i]._x1, _panel[i]._z1, _panel[i]._x2, _panel[i]._z2, actor->_px, actor->_pz, _curX, _curZ)) { ++inters; _pathNode[_numPathNodes]._x = _x3d; _pathNode[_numPathNodes]._z = _z3d; _pathNode[_numPathNodes]._dist = _vm->dist2D(actor->_px, actor->_pz, _x3d, _z3d); _pathNode[_numPathNodes]._oldPanel = i; _pathNode[_numPathNodes]._curPanel = i; ++_numPathNodes; // CORNERS - lever intersections in corners if (_oldPanel >= 0 && (i == _panel[_oldPanel]._nearPanel1 || i == _panel[_oldPanel]._nearPanel2)) { // otherwise if it's near the starting panel if ((_pathNode[_numPathNodes - 1]._dist < EPSILON) && (i != _oldPanel) && (i != _curPanel)) { // and the distance is very small to the intersection --inters; --_numPathNodes; // If the click is inside the nearby panel if (_curPanel < 0 && pointInside(i, _curX, _curZ)) { _curX = actor->_px; _curZ = actor->_pz; actor->_px -= actor->_dx; actor->_pz -= actor->_dz; _curPanel = i; lookAt(_lookX, _lookZ); return; } } } else if (_curPanel >= 0 && (i == _panel[_curPanel]._nearPanel1 || i == _panel[_curPanel]._nearPanel2)) { // otherwise if it is near the finish panel if (ABS(_pathNode[_numPathNodes - 1]._dist - dist) < EPSILON && i != _oldPanel && i != _curPanel) { // and the distance is very small to the intersection --inters; --_numPathNodes; } } } else if (i == _oldPanel) { // always adds start and finish node only in on a panel ++inters; _pathNode[_numPathNodes]._x = actor->_px; _pathNode[_numPathNodes]._z = actor->_pz; _pathNode[_numPathNodes]._dist = 0.0f; _pathNode[_numPathNodes]._oldPanel = _oldPanel; _pathNode[_numPathNodes]._curPanel = _oldPanel; ++_numPathNodes; } else if (i == _curPanel) { ++inters; _pathNode[_numPathNodes]._x = _curX; _pathNode[_numPathNodes]._z = _curZ; _pathNode[_numPathNodes]._dist = dist; _pathNode[_numPathNodes]._oldPanel = _curPanel; _pathNode[_numPathNodes]._curPanel = _curPanel; ++_numPathNodes; } } } // the path is defined by: // start _actor._px, _actor._pz // _numPathNodes _pathNode // end _curX, _curZ // if it collides with any panel if (inters) { sortPath(); // if odd and I go to the floor but I did not start from the panel // if it arrives on the floor and the last two nodes are not on the same block // if outside the last panel it moves the last node if (((inters & 1) && (_curPanel < 0) && (_oldPanel < 0)) || (((inters - 1) & 1) && (_curPanel < 0) && (!findAttachedPanel(_pathNode[_numPathNodes - 2]._curPanel, _pathNode[_numPathNodes - 1]._curPanel) || pointInside(_pathNode[_numPathNodes - 1]._curPanel, _curX, _curZ)))) { _curPanel = _pathNode[_numPathNodes - 1]._curPanel; pointOut(); // remove the point found _pathNode[_numPathNodes]._x = _curX; _pathNode[_numPathNodes]._z = _curZ; _pathNode[_numPathNodes]._oldPanel = _curPanel; _pathNode[_numPathNodes]._curPanel = _curPanel; ++_numPathNodes; } // if it arrives on the floor inters = 0; // Count the intersections with narrow panels // and with the union of large panels and small panels for (int i = 0; i < _panelNum; ++i) { if (!(_panel[i]._flags & 0x80000000)) { if (intersectLineLine(_panel[i]._x1, _panel[i]._z1, _panel[i]._x2, _panel[i]._z2, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z, _curX, _curZ)) ++inters; } else { if (_panel[i]._col1 & 0x80) { if (intersectLineLine(_panel[i]._x1, _panel[i]._z1, _panel[_panel[i]._col1 & 0x7F]._x2, _panel[_panel[i]._col1 & 0x7F]._z2, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z, _curX, _curZ)) { if (_vm->dist2D(_x3d, _z3d, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z) > EPSILON && _vm->dist2D(_x3d, _z3d, _curX, _curZ) > EPSILON) ++inters; } } else if (intersectLineLine(_panel[i]._x1, _panel[i]._z1, _panel[_panel[i]._col1 & 0x7F]._x1, _panel[_panel[i]._col1 & 0x7F]._z1, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z, _curX, _curZ)) { if (_vm->dist2D(_x3d, _z3d, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z) > EPSILON && _vm->dist2D(_x3d, _z3d, _curX, _curZ) > EPSILON) ++inters; } if (_panel[i]._col2 & 0x80) { if (intersectLineLine(_panel[i]._x2, _panel[i]._z2, _panel[_panel[i]._col2 & 0x7F]._x2, _panel[_panel[i]._col2 & 0x7F]._z2, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z, _curX, _curZ)) { if (_vm->dist2D(_x3d, _z3d, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z) > EPSILON && _vm->dist2D(_x3d, _z3d, _curX, _curZ) > EPSILON) ++inters; } } else if (intersectLineLine(_panel[i]._x2, _panel[i]._z2, _panel[_panel[i]._col2 & 0x7F]._x1, _panel[_panel[i]._col2 & 0x7F]._z1, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z, _curX, _curZ)) { if (_vm->dist2D(_x3d, _z3d, _pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z) > EPSILON && _vm->dist2D(_x3d, _z3d, _curX, _curZ) > EPSILON) ++inters; } } if (inters) break; } // If in the last line there's an obstacle, remove the first node if (inters) { _curPanel = _pathNode[_numPathNodes - 1]._curPanel; pointOut(); // take out the point found _pathNode[_numPathNodes]._x = _curX; _pathNode[_numPathNodes]._z = _curZ; _pathNode[_numPathNodes]._oldPanel = _curPanel; _pathNode[_numPathNodes]._curPanel = _curPanel; ++_numPathNodes; } _pathNode[_numPathNodes]._x = _curX; _pathNode[_numPathNodes]._z = _curZ; _pathNode[_numPathNodes]._dist = _vm->dist2D(actor->_px, actor->_pz, _curX, _curZ); _pathNode[_numPathNodes]._oldPanel = _curPanel; _pathNode[_numPathNodes]._curPanel = _curPanel; ++_numPathNodes; findShortPath(); displayPath(); } else { // otherwise if it's direct _pathNode[_numPathNodes]._x = actor->_px; _pathNode[_numPathNodes]._z = actor->_pz; _pathNode[_numPathNodes]._dist = 0.0f; _pathNode[_numPathNodes]._oldPanel = _oldPanel; _pathNode[_numPathNodes]._curPanel = _oldPanel; ++_numPathNodes; _pathNode[_numPathNodes]._x = _curX; _pathNode[_numPathNodes]._z = _curZ; _pathNode[_numPathNodes]._dist = _vm->dist2D(actor->_px, actor->_pz, _curX, _curZ); _pathNode[_numPathNodes]._oldPanel = _curPanel; _pathNode[_numPathNodes]._curPanel = _curPanel; ++_numPathNodes; displayPath(); } actor->_px -= actor->_dx; actor->_pz -= actor->_dz; } /** * Look for the shorter route avoiding obstacle */ void PathFinding3D::findShortPath() { SPathNode tempPath[MAXPATHNODES]; float len1, len2; int curPanel, nearPanel, oldPanel; float destX, destZ; bool fail = false; int count = 0; // Add departure tempPath[count]._x = _vm->_actor->_px; tempPath[count]._z = _vm->_actor->_pz; tempPath[count]._dist = 0.0f; tempPath[count]._oldPanel = _oldPanel; tempPath[count]._curPanel = _oldPanel; ++count; // for every obstacle, try to go around it by the right and the left // then take the sorter path for (int i = 0; i < _numPathNodes - 1; ++i) { memcpy(&tempPath[count], &_pathNode[i], sizeof(SPathNode)); ++count; if (count >= MAXPATHNODES - 2) count = MAXPATHNODES - 2; curPanel = _pathNode[i]._curPanel; // if source and destination panel are on the same block if (!findAttachedPanel(curPanel, _pathNode[i + 1]._curPanel)) continue; // go around obstacle starting with _nearPanel1 len1 = evalPath(i, _panel[curPanel]._x1, _panel[curPanel]._z1, _panel[curPanel]._nearPanel1) + _vm->dist2D(_pathNode[i]._x, _pathNode[i]._z, _panel[curPanel]._x1, _panel[curPanel]._z1); // go around obstacle starting with _nearPanel2 len2 = evalPath(i, _panel[curPanel]._x2, _panel[curPanel]._z2, _panel[curPanel]._nearPanel2) + _vm->dist2D(_pathNode[i]._x, _pathNode[i]._z, _panel[curPanel]._x2, _panel[curPanel]._z2); // Check which route was shorter if ((len1 < 32000.0f) && (len2 < 32000.0f)) { if (len1 < len2) { destX = _panel[curPanel]._x1; destZ = _panel[curPanel]._z1; nearPanel = _panel[curPanel]._nearPanel1; } else { destX = _panel[curPanel]._x2; destZ = _panel[curPanel]._z2; nearPanel = _panel[curPanel]._nearPanel2; } float curX = _pathNode[i]._x; float curZ = _pathNode[i]._z; oldPanel = curPanel; int index = 0; // Save the shorter path for (;;) { tempPath[count]._x = curX; tempPath[count]._z = curZ; tempPath[count]._oldPanel = oldPanel; tempPath[count]._curPanel = curPanel; ++count; if (count >= MAXPATHNODES - 2) count = MAXPATHNODES - 2; // if it reaches the point, exit the loop if (curPanel == _pathNode[i + 1]._curPanel) { memcpy(&tempPath[count], &_pathNode[i + 1], sizeof(SPathNode)); ++count; if (count >= MAXPATHNODES - 2) count = MAXPATHNODES - 2; break; } // If it's back to the starting panel, it didn't find a route if (((curPanel == _pathNode[i]._curPanel) && index) || (index > _panelNum)) { fail = true; // stop at the edge first break; // and stop walking } // otherwise go to the next panel if (_panel[nearPanel]._nearPanel1 == curPanel) { // go to summit 2 next time curX = destX; curZ = destZ; destX = _panel[nearPanel]._x2; destZ = _panel[nearPanel]._z2; oldPanel = curPanel; curPanel = nearPanel; nearPanel = _panel[curPanel]._nearPanel2; } else { // go to summit 1 next time curX = destX; curZ = destZ; destX = _panel[nearPanel]._x1; destZ = _panel[nearPanel]._z1; oldPanel = curPanel; curPanel = nearPanel; nearPanel = _panel[curPanel]._nearPanel1; } ++index; } } else { fail = true; } if (fail) // if it failed to go around the obstacle, stop break; } // adds arrival tempPath[count]._x = _curX; tempPath[count]._z = _curZ; tempPath[count]._dist = 0.0f; tempPath[count]._oldPanel = _curPanel; tempPath[count]._curPanel = _curPanel; ++count; // after walking around all obstacles, optimize _numPathNodes = 0; for (int i = 0; i < count; ++i) { if (_numPathNodes > MAXPATHNODES - 2) _numPathNodes = MAXPATHNODES - 2; int j; // remove all the attached nodes for (j = count - 1; j >= i; --j) { if (_vm->dist2D(tempPath[j]._x, tempPath[j]._z, tempPath[i]._x, tempPath[i]._z) < EPSILON) break; } i = j; memcpy(&_pathNode[_numPathNodes], &tempPath[i], sizeof(SPathNode)); ++_numPathNodes; for (j = count - 1; j > i + 1; --j) { int inters = 0; for (int k = 0; k < _panelNum; ++k) { // it must never intersect the small panel if (!(_panel[k]._flags & 0x80000000)) { if (intersectLineLine(_panel[k]._x1, _panel[k]._z1, _panel[k]._x2, _panel[k]._z2, tempPath[i]._x, tempPath[i]._z, tempPath[j]._x, tempPath[j]._z)) ++inters; if (_panel[k]._col1 & 0x80) { if (intersectLineLine(_panel[k]._x1, _panel[k]._z1, _panel[_panel[k]._col1 & 0x7F]._x2, _panel[_panel[k]._col1 & 0x7F]._z2, tempPath[i]._x, tempPath[i]._z, tempPath[j]._x, tempPath[j]._z)) { len2 = _vm->dist2D(_x3d, _z3d, tempPath[i]._x, tempPath[i]._z); len1 = _vm->dist2D(_x3d, _z3d, tempPath[j]._x, tempPath[j]._z); // intersect at a point distant from the start and the finish if ((len1 > EPSILON) && (len2 > EPSILON)) ++inters; } } else if (intersectLineLine(_panel[k]._x1, _panel[k]._z1, _panel[_panel[k]._col1 & 0x7F]._x1, _panel[_panel[k]._col1 & 0x7F]._z1, tempPath[i]._x, tempPath[i]._z, tempPath[j]._x, tempPath[j]._z)) { len2 = _vm->dist2D(_x3d, _z3d, tempPath[i]._x, tempPath[i]._z); len1 = _vm->dist2D(_x3d, _z3d, tempPath[j]._x, tempPath[j]._z); // intersect at a point distant from the start and the finish if ((len1 > EPSILON) && (len2 > EPSILON)) ++inters; } if (_panel[k]._col2 & 0x80) { if (intersectLineLine(_panel[k]._x2, _panel[k]._z2, _panel[_panel[k]._col2 & 0x7F]._x2, _panel[_panel[k]._col2 & 0x7F]._z2, tempPath[i]._x, tempPath[i]._z, tempPath[j]._x, tempPath[j]._z)) { len2 = _vm->dist2D(_x3d, _z3d, tempPath[i]._x, tempPath[i]._z); len1 = _vm->dist2D(_x3d, _z3d, tempPath[j]._x, tempPath[j]._z); // intersect at a point distant from the start and the finish if ((len1 > EPSILON) && (len2 > EPSILON)) ++inters; } } else if (intersectLineLine(_panel[k]._x2, _panel[k]._z2, _panel[_panel[k]._col2 & 0x7F]._x1, _panel[_panel[k]._col2 & 0x7F]._z1, tempPath[i]._x, tempPath[i]._z, tempPath[j]._x, tempPath[j]._z)) { len2 = _vm->dist2D(_x3d, _z3d, tempPath[i]._x, tempPath[i]._z); len1 = _vm->dist2D(_x3d, _z3d, tempPath[j]._x, tempPath[j]._z); // intersect at a point distant from the start and the finish if ((len1 > EPSILON) && (len2 > EPSILON)) ++inters; } if (inters) break; } } // if from A it's possible to reach B directly if (!inters) { curPanel = _pathNode[_numPathNodes - 1]._curPanel; oldPanel = tempPath[j]._oldPanel; int c; for (c = i; c <= j; ++c) { if ((tempPath[c]._oldPanel == curPanel) && (tempPath[c]._curPanel == oldPanel)) break; } // if they weren't connected it means it went through the floor if (c > j) { _pathNode[_numPathNodes - 1]._curPanel = -1; // start tempPath[j]._oldPanel = -1; // destination } i = j - 1; break; } } } } /** * Evaluate path length */ float PathFinding3D::evalPath(int a, float destX, float destZ, int nearP) { int index = 0; float len = 0.0f; int curPanel = _pathNode[a]._curPanel; float curX = _pathNode[a]._x; float curZ = _pathNode[a]._z; for (;;) { // if the point is reached, stop if (curPanel == _pathNode[a + 1]._curPanel) { len += _vm->dist2D(curX, curZ, _pathNode[a + 1]._x, _pathNode[a + 1]._z); break; } // if it's back to the starting plane, there's no route if (((curPanel == _pathNode[a]._curPanel) && index) || (index > _panelNum)) { len += 32000.0f; // Absurd length break; } // Otherwise it goes to the next plane // if nearP is attached to curp via vertex1 if (_panel[nearP]._nearPanel1 == curPanel) { // go to vertex 2 next time len += _vm->dist2D(curX, curZ, destX, destZ); curX = destX; curZ = destZ; destX = _panel[nearP]._x2; destZ = _panel[nearP]._z2; curPanel = nearP; nearP = _panel[curPanel]._nearPanel2; } else { // go to vertex 1 newt time len += _vm->dist2D(curX, curZ, destX, destZ); curX = destX; curZ = destZ; destX = _panel[nearP]._x1; destZ = _panel[nearP]._z1; curPanel = nearP; nearP = _panel[curPanel]._nearPanel1; } ++index; } return len; } /** * Check if a point is inside a panel */ bool PathFinding3D::pointInside(int pan, float x, float z) const { if (pan < 0) return false; if (!(_panel[pan]._flags & 0x80000000)) return true; double pgon[4][2]; pgon[0][0] = (double)_panel[pan]._x1; pgon[0][1] = (double)_panel[pan]._z1; pgon[3][0] = (double)_panel[pan]._x2; pgon[3][1] = (double)_panel[pan]._z2; uint8 idx = _panel[pan]._col1 & 0x7F; if (_panel[pan]._col1 & 0x80) { pgon[1][0] = (double)_panel[idx]._x2; pgon[1][1] = (double)_panel[idx]._z2; } else { pgon[1][0] = (double)_panel[idx]._x1; pgon[1][1] = (double)_panel[idx]._z1; } idx = _panel[pan]._col2 & 0x7F; if (_panel[pan]._col2 & 0x80) { pgon[2][0] = (double)_panel[idx]._x2; pgon[2][1] = (double)_panel[idx]._z2; } else { pgon[2][0] = (double)_panel[idx]._x1; pgon[2][1] = (double)_panel[idx]._z1; } double ox = pgon[3][0] - pgon[0][0]; double oz = pgon[3][1] - pgon[0][1]; double s = sqrt(ox * ox + oz * oz); ox /= s; oz /= s; pgon[0][0] -= EPSILON * ox; pgon[0][1] -= EPSILON * oz; pgon[3][0] += EPSILON * ox; pgon[3][1] += EPSILON * oz; ox = pgon[2][0] - pgon[1][0]; oz = pgon[2][1] - pgon[1][1]; s = sqrt(ox * ox + oz * oz); ox /= s; oz /= s; pgon[1][0] -= EPSILON * ox; pgon[1][1] -= EPSILON * oz; pgon[2][0] += EPSILON * ox; pgon[2][1] += EPSILON * oz; // Crossing-Multiply algorithm double *vtx0 = pgon[3]; // get test bit for above/below X axis bool yFlag0 = (vtx0[1] >= z); double *vtx1 = pgon[0]; int counter = 0; for (int j = 5; --j;) { const bool yFlag1 = (vtx1[1] >= z); if (yFlag0 != yFlag1) { const bool xFlag0 = (vtx0[0] >= x); if ((xFlag0 == (vtx1[0] >= x)) && (xFlag0)) counter += (yFlag0 ? -1 : 1); else if ((vtx1[0] - (vtx1[1] - z) * (vtx0[0] - vtx1[0]) / (vtx0[1] - vtx1[1])) >= x) counter += (yFlag0 ? -1 : 1); } // Move to the next pair of vertices, retaining info as possible. yFlag0 = yFlag1; vtx0 = vtx1; vtx1 += 2; } return (counter != 0); } void PathFinding3D::setPosition(int num) { SLight *curLight = _vm->_actor->_light; for (uint32 i = 0; i < _vm->_actor->_lightNum; ++i, ++curLight) { if (curLight->_inten != 0 || curLight->_position != num) continue; // If it's off and If it's the required position _vm->_actor->_px = curLight->_x; _vm->_actor->_pz = curLight->_z; _vm->_actor->_dx = 0.0f; _vm->_actor->_dz = 0.0f; float ox = curLight->_dx; float oz = curLight->_dz; // If it's a null light if (_vm->floatComp(ox, 0.0f) == 0 && _vm->floatComp(oz, 0.0f) == 0) // ox == 0.0f && oz == 0.0f warning("setPosition: Unknown error : null light"); float t = sqrt(ox * ox + oz * oz); ox /= t; oz /= t; float theta = _vm->sinCosAngle(ox, oz) * 180.0f / M_PI; if (_vm->floatComp(theta, 360.0f) >= 0) // theta >= 360.0f theta -= 360.0f; if (_vm->floatComp(theta, 0.0f) == -1) // theta < 0.0f theta += 360.0f; _vm->_actor->_theta = theta; _curStep = 0; _lastStep = 0; _curPanel = -1; _oldPanel = -1; reset(0, _vm->_actor->_px + _vm->_actor->_dx, _vm->_actor->_pz + _vm->_actor->_dz, _vm->_actor->_theta); _characterGoToPosition = num; return; } } void PathFinding3D::goToPosition(int num) { SLight *curLight = _vm->_actor->_light; for (uint32 i = 0; i < _vm->_actor->_lightNum; ++i, ++curLight) { if (curLight->_inten != 0 || curLight->_position != num) continue; // If it's off and if it's the right position _curX = curLight->_x; _curZ = curLight->_z; _lookX = _curX - curLight->_dx; _lookZ = _curZ - curLight->_dz; _curStep = 0; _lastStep = 0; reset(0, _vm->_actor->_px + _vm->_actor->_dx, _vm->_actor->_pz + _vm->_actor->_dz, _vm->_actor->_theta); _oldPanel = _curPanel; _curPanel = -1; findPath(); _characterGoToPosition = num; break; } } void PathFinding3D::lookAt(float x, float z) { float ox = _step[_lastStep]._px - x; float oz = _step[_lastStep]._pz - z; // If the light is null if (_vm->floatComp(ox, 0.0f) == 0 && _vm->floatComp(oz, 0.0f) == 0) { memcpy(&_step[_lastStep + 1], &_step[_lastStep], sizeof(SStep)); memcpy(&_step[_lastStep + 2], &_step[_lastStep + 1], sizeof(SStep)); _lastStep += 2; return; } float t = sqrt(ox * ox + oz * oz); ox /= t; oz /= t; float theta = _vm->sinCosAngle(ox, oz) * 180.0f / M_PI; if (_vm->floatComp(theta, 360.0f) >= 0) //theta >= 360.0f theta -= 360.0f; if (_vm->floatComp(theta, 0.0f) == -1) // theta < 0.0f theta += 360.0f; float approx = theta - _step[_lastStep]._theta; if (_vm->floatComp(approx, 30.0f) == -1 && _vm->floatComp(approx, -30.0f) == 1) // approx < 30.0f && approx > -30.0f approx = 0.0f; else if (_vm->floatComp(approx, 180.0f) == 1) // approx > 180.0f approx = -360.0f + approx; else if (_vm->floatComp(approx, -180.0f) == -1) // approx < -180.0f approx = 360.0f + approx; approx /= 3.0f; // Antepenultimate 1/3 _step[_lastStep]._theta += approx; _step[_lastStep]._theta = _vm->floatComp(_step[_lastStep]._theta, 360.0f) == 1 ? _step[_lastStep]._theta - 360.0f : _vm->floatComp(_step[_lastStep]._theta, 0.0f) == -1 ? _step[_lastStep]._theta + 360.0f : _step[_lastStep]._theta; // Penultimate 2/3 memcpy(&_step[_lastStep + 1], &_step[_lastStep], sizeof(SStep)); ++_lastStep; _step[_lastStep]._theta += approx; _step[_lastStep]._theta = _vm->floatComp(_step[_lastStep]._theta, 360.0f) == 1 ? _step[_lastStep]._theta - 360.0f : _vm->floatComp(_step[_lastStep]._theta, 0.0f) == -1 ? _step[_lastStep]._theta + 360.0f : _step[_lastStep]._theta; // Last right step memcpy(&_step[_lastStep + 1], &_step[_lastStep], sizeof(SStep)); ++_lastStep; _step[_lastStep]._theta = theta; // ???? memcpy(&_step[_lastStep + 1], &_step[_lastStep], sizeof(SStep)); ++_lastStep; _step[_lastStep]._theta = theta; } /** * Build list containing all the frames */ void PathFinding3D::buildFramelist() { // check that it never crosses or touches a narrow panel for (int i = 1; i < _numPathNodes; ++i) { for (int c = 0; c < _panelNum; ++c) { // it must never intersect narrow panel if (!(_panel[c]._flags & 0x80000000) && intersectLineLine(_panel[c]._x1, _panel[c]._z1, _panel[c]._x2, _panel[c]._z2, _pathNode[i - 1]._x, _pathNode[i - 1]._z, _pathNode[i]._x, _pathNode[i]._z)) { _numPathNodes = i; break; } } } float len = 0.0f; float curLen = 0.0f; float ox = _pathNode[0]._x; float oz = _pathNode[0]._z; for (int i = 1; i < _numPathNodes; ++i) { len += _vm->dist3D(_pathNode[i]._x, 0.0f, _pathNode[i]._z, ox, 0.0f, oz); ox = _pathNode[i]._x; oz = _pathNode[i]._z; } // total route length calculated - if too small, returns if (_vm->floatComp(len, 2.0f) == -1) { lookAt(_lookX, _lookZ); return; } int i = 0; // compute offset SVertex *v = _vm->_actor->_characterArea; float firstFrame = _vm->_actor->frameCenter(v); // if he was already walking int curAction, curFrame, cfp; if (_vm->_actor->_curAction == hWALK) { // compute current frame cfp = defActionLen[hSTART] + 1 + _vm->_actor->_curFrame; v += cfp * _vm->_actor->_vertexNum; curAction = hWALK; curFrame = _vm->_actor->_curFrame; // if it wasn't the last frame, take the next step if (_vm->_actor->_curFrame < defActionLen[hWALK] - 1) { ++cfp; ++curFrame; v += _vm->_actor->_vertexNum; } } else if ((_vm->_actor->_curAction >= hSTOP0) && (_vm->_actor->_curAction <= hSTOP9)) { // if he was stopped, starts moving again // compute current frame curAction = hWALK; curFrame = _vm->_actor->_curAction - hSTOP0; cfp = defActionLen[hSTART] + 1 + curFrame; v += cfp * _vm->_actor->_vertexNum; } else { // if he was standing, start working or turn oz = 0.0f; cfp = 1; curAction = hSTART; curFrame = 0; // start from the first frame v += _vm->_actor->_vertexNum; } oz = -_vm->_actor->frameCenter(v) + firstFrame; // at this point, CurA / _curAction is either hSTART or hWALK // until it arrives at the destination curLen = oz + _vm->_actor->frameCenter(v) - firstFrame; while (_vm->floatComp(curLen, len) == -1 || !i) { _step[i]._pz = oz - firstFrame; // where to render _step[i]._dz = curLen; // where it is _step[i]._curAction = curAction; _step[i]._curFrame = curFrame; ++i; v += _vm->_actor->_vertexNum; ++curFrame; ++cfp; if (curFrame >= defActionLen[curAction]) { if (curAction == hSTART) { curAction = hWALK; curFrame = 0; cfp = defActionLen[hSTART] + 1; ox = 0.0f; } else if (curAction == hWALK) { curAction = hWALK; curFrame = 0; cfp = defActionLen[hSTART] + 1; // end walk frame ox = _vm->_actor->frameCenter(v) - firstFrame; v = &_vm->_actor->_characterArea[cfp * _vm->_actor->_vertexNum]; ox -= _vm->_actor->frameCenter(v); } v = &_vm->_actor->_characterArea[cfp * _vm->_actor->_vertexNum]; // only if it doesn't end if (_vm->floatComp(oz + ox + _vm->_actor->frameCenter(v) - firstFrame, len) == -1) // oz + ox + _vm->_actor->frameCenter(v) - firstFrame < len oz += ox; else break; } curLen = oz + _vm->_actor->frameCenter(v) - firstFrame; } // After the destination, add the stop frame // if he was walking if (_step[i - 1]._curAction == hWALK) curAction = _step[i - 1]._curFrame + hSTOP0; // stop previous step. else curAction = hSTOP0; // stop step 01 assert(curAction <= hLAST); // _defActionLen below has a size of hLAST + 1 curFrame = 0; int index = 0; cfp = 0; while (index != curAction) cfp += defActionLen[index++]; v = &_vm->_actor->_characterArea[cfp * _vm->_actor->_vertexNum]; for (index = 0; index < defActionLen[curAction]; ++index) { curLen = oz + _vm->_actor->frameCenter(v) - firstFrame; _step[i]._pz = oz - firstFrame; // where to render _step[i]._dz = curLen; // where it is _step[i]._curAction = curAction; _step[i]._curFrame = curFrame; ++i; ++curFrame; v += _vm->_actor->_vertexNum; } // how far is it from the destination? int divider = i - 2; if (divider == 0) // Safeguard, should never be useful... but if it is, it'll avoid a divide by 0 error. divider = 1; float approx = (len - curLen - EPSILON) / (float)divider; float theta = 0.0f; // Adjust all the steps so it arrives exactly where clicked for (index = 1; index < i; ++index) { // verify there's no reverse step if (_vm->floatComp(_step[index - 1]._dz, _step[index]._dz + approx * index) == 1 || _vm->floatComp(_step[index]._dz + approx * index + EPSILON, len) >= 0) { theta = _step[index]._dz - _step[index]._pz; _step[index]._dz = _step[index - 1]._dz; _step[index]._pz = _step[index]._dz - theta; } else { _step[index]._pz += (approx * index); _step[index]._dz += (approx * index); } } float cx = _step[index - 1]._dz; _lastStep = index; // last step _curStep = 0; // current step // now insert exact directions and start and destination points index = 0; len = 0.0f; float startPos = 0.0f; for (i = 0; i < _numPathNodes - 1; ++i) { curLen = 0.0f; len += _vm->dist3D(_pathNode[i]._x, 0.0f, _pathNode[i]._z, _pathNode[i + 1]._x, 0.0f, _pathNode[i + 1]._z); // determine the direction ox = _pathNode[i + 1]._x - _pathNode[i]._x; oz = _pathNode[i + 1]._z - _pathNode[i]._z; // if it's a useless node, remove it if (_vm->floatComp(ox, 0.0f) == 0 && _vm->floatComp(oz, 0.0f) == 0) continue; approx = sqrt(ox * ox + oz * oz); ox /= approx; oz /= approx; theta = _vm->sinCosAngle(ox, oz) * 180.0f / M_PI + 180.0f; if (_vm->floatComp(theta, 360.0f) >= 0) theta -= 360.0f; if (_vm->floatComp(theta, 0.0f) == -1) theta += 360.0f; while (index < _lastStep && _vm->floatComp(_step[index]._dz, len) <= 0) { curLen = _step[index]._dz - _step[index]._pz; _step[index]._px = _pathNode[i]._x + (_step[index]._pz - startPos) * ox; _step[index]._pz = _pathNode[i]._z + (_step[index]._pz - startPos) * oz; _step[index]._dx = curLen * ox; _step[index]._dz = curLen * oz; _step[index]._theta = theta; _step[index]._curPanel = _pathNode[i]._curPanel; ++index; } startPos = len; } reset(index, _curX, _curZ, theta); _lastStep = index; // last step _curStep = 0; // current step // starting angle float oldTheta = _vm->_actor->_theta; // first angle walk theta = _step[0]._theta; // if he starts from standstill position if ((_step[0]._curAction == hSTART) && (_step[0]._curFrame == 0) && (_lastStep > 4) && _vm->floatComp(_step[0]._theta, _step[1]._theta) == 0) { approx = theta - oldTheta; if (_vm->floatComp(approx, 180.0f) == 1) approx = -360.0f + approx; else if (_vm->floatComp(approx, -180.0f) == -1) approx = 360.0f + approx; approx /= 3.0f; for (index = 0; index < 2; ++index) { _step[index]._theta = oldTheta + (float)(index + 1) * approx; _step[index]._theta = _vm->floatComp(_step[index]._theta, 360.0f) == 1 ? _step[index]._theta - 360.0f : _vm->floatComp(_step[index]._theta, 0.0f) == -1 ? _step[index]._theta + 360.0f : _step[index]._theta; theta = _step[index]._theta; curLen = sqrt(_step[index]._dx * _step[index]._dx + _step[index]._dz * _step[index]._dz); theta = ((270.0f - theta) * M_PI) / 180.0f; ox = cos(theta) * curLen; oz = sin(theta) * curLen; cx = _step[index]._px + _step[index]._dx; float cz = _step[index]._pz + _step[index]._dz; _step[index]._px += _step[index]._dx - ox; _step[index]._pz += _step[index]._dz - oz; _step[index]._dx = cx - _step[index]._px; _step[index]._dz = cz - _step[index]._pz; } } // makes the curve oldTheta = _step[2]._theta; for (index = 3; index <= _lastStep; ++index) { theta = _step[index]._theta; // if it made a curve if (_vm->floatComp(oldTheta, theta) != 0) { approx = theta - oldTheta; if (_vm->floatComp(approx, 180.0f) == 1) approx = -360.0f + approx; else if (_vm->floatComp(approx, -180.0f) == -1) approx = 360.0f + approx; approx /= 3.0f; // for the previous one _step[index - 1]._theta += approx; _step[index - 1]._theta = _vm->floatComp(_step[index - 1]._theta, 360.0f) == 1 ? _step[index - 1]._theta - 360.0f : _vm->floatComp(_step[index - 1]._theta, 0.0f) == -1 ? _step[index - 1]._theta + 360.0f : _step[index - 1]._theta; oldTheta = _step[index - 1]._theta; curLen = sqrt(_step[index - 1]._dx * _step[index - 1]._dx + _step[index - 1]._dz * _step[index - 1]._dz); oldTheta = ((270.0f - oldTheta) * M_PI) / 180.0f; ox = cos(oldTheta) * curLen; oz = sin(oldTheta) * curLen; cx = _step[index - 1]._px + _step[index - 1]._dx; float cz = _step[index - 1]._pz + _step[index - 1]._dz; _step[index - 1]._px += _step[index - 1]._dx - ox; _step[index - 1]._pz += _step[index - 1]._dz - oz; _step[index - 1]._dx = cx - _step[index - 1]._px; _step[index - 1]._dz = cz - _step[index - 1]._pz; // for the next one _step[index]._theta -= approx; _step[index]._theta = _vm->floatComp(_step[index]._theta, 360.0f) == 1 ? _step[index]._theta - 360.0f : _vm->floatComp(_step[index]._theta, 0.0f) == -1 ? _step[index]._theta + 360.0f : _step[index]._theta; oldTheta = theta; theta = _step[index]._theta; curLen = sqrt(_step[index]._dx * _step[index]._dx + _step[index]._dz * _step[index]._dz); theta = ((270.0f - theta) * M_PI) / 180.0f; ox = cos(theta) * curLen; oz = sin(theta) * curLen; cx = _step[index]._px + _step[index]._dx; cz = _step[index]._pz + _step[index]._dz; _step[index]._px += _step[index]._dx - ox; _step[index]._pz += _step[index]._dz - oz; _step[index]._dx = cx - _step[index]._px; _step[index]._dz = cz - _step[index]._pz; } else oldTheta = theta; } lookAt(_lookX, _lookZ); } /** * Take the next frame walk */ int PathFinding3D::nextStep() { Actor *actor = _vm->_actor; actor->_px = _step[_curStep]._px; actor->_pz = _step[_curStep]._pz; actor->_dx = _step[_curStep]._dx; actor->_dz = _step[_curStep]._dz; actor->_theta = _step[_curStep]._theta; actor->_curAction = _step[_curStep]._curAction; actor->_curFrame = _step[_curStep]._curFrame; _curPanel = _step[_curStep]._curPanel; // increase the current step if it's not the last frame if (_curStep < _lastStep) { ++_curStep; return false; } if (_characterGoToPosition != -1) setPosition(_characterGoToPosition); return true; } /** * View route */ void PathFinding3D::displayPath() { buildFramelist(); } /** * Check if two panels are in the same block */ bool PathFinding3D::findAttachedPanel(int16 srcPanel, int16 destPanel) { // if at least one is on the floor, return false if (srcPanel < 0 || destPanel < 0) return false; // if they are equal, return true if (srcPanel == destPanel) return true; int16 curPanel = srcPanel; int16 nearPanel = _panel[srcPanel]._nearPanel1; for (int i = 0;; ++i) { // if they are attached, return true if (curPanel == destPanel) return true; // if it has returned to the starting panel, return false if (srcPanel == curPanel && i) return false; if (i > _panelNum) return false; // if they are attached to vertex 1, take 2 if (_panel[nearPanel]._nearPanel1 == curPanel) { curPanel = nearPanel; nearPanel = _panel[curPanel]._nearPanel2; } else { curPanel = nearPanel; nearPanel = _panel[curPanel]._nearPanel1; } } } /** * Compare route distance (qsort) */ int pathCompare(const void *arg1, const void *arg2) { const SPathNode *p1 = (const SPathNode *)arg1; const SPathNode *p2 = (const SPathNode *)arg2; if (p1->_dist < p2->_dist) return -1; if (p1->_dist > p2->_dist) return 1; return 0; } /** * sort the nodes of the path found */ void PathFinding3D::sortPath() { qsort(&_pathNode[0], _numPathNodes, sizeof(SPathNode), pathCompare); } /** * Initializes sort panel */ void PathFinding3D::initSortPan() { _numSortPanel = 31; for (int i = 1; i < _numSortPanel - 1; ++i) { _sortPan[i]._min = 32000.0f; _sortPan[i]._num = i; } // First panel is behind everything and is not sorted _sortPan[0]._min = 30000.0f; _sortPan[0]._num = BOX_BACKGROUND; // Last panel is in front of everything and is not sorted _sortPan[30]._min = 0.0f; _sortPan[30]._num = BOX_FOREGROUND; Actor *actor = _vm->_actor; // Sort panel blocks by increasing distance from the camera for (int i = 0; i < _panelNum; ++i) { if (!(_panel[i]._flags & 0x80000000)) { float dist1 = _vm->dist3D(actor->_camera->_ex, 0.0, actor->_camera->_ez, _panel[i]._x1, 0.0, _panel[i]._z1); float dist2 = _vm->dist3D(actor->_camera->_ex, 0.0, actor->_camera->_ez, _panel[i]._x2, 0.0, _panel[i]._z2); float min = MIN(dist1, dist2); for (int j = 0; j < _numSortPanel; ++j) { if (_panel[i]._flags & (1 << j)) { if (_sortPan[j + 1]._min > min) _sortPan[j + 1]._min = min; } } } } sortPanel(); for (int i = 0; i < _numSortPanel; ++i) { if (_sortPan[i]._num == BOX_BACKGROUND) { // now the panels go from 0 (foreground) to _numSortPanel (background) _numSortPanel = i; break; } } } void PathFinding3D::read3D(Common::SeekableReadStreamEndian *ff) { // read panels _panelNum = ff->readSint32(); if (_panelNum > MAXPANELSINROOM) error("read3D(): Too many panels"); for (int i = 0; i < _panelNum; ++i) { _panel[i]._x1 = ff->readFloat(); _panel[i]._z1 = ff->readFloat(); _panel[i]._x2 = ff->readFloat(); _panel[i]._z2 = ff->readFloat(); _panel[i]._h = ff->readFloat(); _panel[i]._flags = ff->readUint32(); // Note : Despite the panels are stored in an int16 with a MAXPANELSINROOM set to 400, // _panelNum is stored in a int32 and nearPanel1 and 2 were stored in an int8 // in the data files. It's weird, but that's how the original game works so please // don't change that. _panel[i]._nearPanel1 = ff->readSByte(); _panel[i]._nearPanel2 = ff->readSByte(); _panel[i]._col1 = ff->readSByte(); _panel[i]._col2 = ff->readSByte(); } // projection matrix float _proj[3][3]; SCamera *cam = _vm->_actor->_camera; _proj[0][0] = cam->_e1[0]; _proj[0][1] = cam->_e1[1]; _proj[0][2] = cam->_e1[2]; _proj[1][0] = cam->_e2[0]; _proj[1][1] = cam->_e2[1]; _proj[1][2] = cam->_e2[2]; _proj[2][0] = cam->_e3[0]; _proj[2][1] = cam->_e3[1]; _proj[2][2] = cam->_e3[2]; // Compute 3x3 inverse matrix for 2D points on 3D float det = _proj[0][0] * _proj[1][1] * _proj[2][2] + _proj[0][1] * _proj[1][2] * _proj[2][0] + _proj[0][2] * _proj[1][0] * _proj[2][1] - _proj[2][0] * _proj[1][1] * _proj[0][2] - _proj[2][1] * _proj[1][2] * _proj[2][0] - _proj[2][2] * _proj[1][0] * _proj[2][1]; if (_vm->floatComp(det, 0.0f) == 0) error("read3D : Unexpected data error while computing inverse matrix"); _invP[0][0] = (_proj[1][1] * _proj[2][2] - _proj[1][2] * _proj[2][1]) / det; _invP[0][1] = (_proj[0][1] * _proj[2][2] - _proj[0][2] * _proj[2][1]) / (-det); _invP[0][2] = (_proj[0][1] * _proj[1][2] - _proj[0][2] * _proj[1][1]) / det; _invP[1][0] = (_proj[1][0] * _proj[2][2] - _proj[1][2] * _proj[2][0]) / (-det); _invP[1][1] = (_proj[0][0] * _proj[2][2] - _proj[0][2] * _proj[2][0]) / det; _invP[1][2] = (_proj[0][0] * _proj[1][2] - _proj[0][2] * _proj[1][0]) / (-det); _invP[2][0] = (_proj[1][0] * _proj[2][1] - _proj[1][1] * _proj[2][0]) / det; _invP[2][1] = (_proj[0][0] * _proj[2][1] - _proj[0][1] * _proj[2][0]) / (-det); _invP[2][2] = (_proj[0][0] * _proj[1][1] - _proj[0][1] * _proj[1][0]) / det; } void PathFinding3D::reset(uint16 idx,float px, float pz, float theta) { _step[idx]._px = px; _step[idx]._pz = pz; _step[idx]._dx = 0.0f; _step[idx]._dz = 0.0f; _step[idx]._theta = theta; _step[idx]._curAction = hSTAND; _step[idx]._curFrame = 0; _step[idx]._curPanel = _curPanel; } /** * Compare panel distance (qsort) */ int panelCompare(const void *arg1, const void *arg2) { const SSortPan *p1 = (const SSortPan *)arg1; const SSortPan *p2 = (const SSortPan *)arg2; if (p1->_min > p2->_min) return 1; if (p1->_min < p2->_min) return -1; return 0; } /** * Sort the panels */ void PathFinding3D::sortPanel() { qsort(&_sortPan[0], _numSortPanel, sizeof(SSortPan), panelCompare); } /** * Find the 3D point corresponding to the 2D point */ void PathFinding3D::whereIs(int px, int py) { float inters = 32000.0f; _vm->_actor->_px += _vm->_actor->_dx; _vm->_actor->_pz += _vm->_actor->_dz; _oldPanel = _curPanel; _curPanel = -2; invPointProject(px, py); float x = _x3d; float y = _y3d; float z = _z3d; // Try to intersect with the floor if (intersectLineFloor(x, y, z)) { _curPanel = -1; _curX = _x3d; _curZ = _z3d; } // try all the panels and choose the closest one for (int i = 0; i < _panelNum; ++i) { if (intersectLinePanel(&_panel[i], x, y, z)) { float temp = _vm->dist3D(_vm->_actor->_camera->_ex, _vm->_actor->_camera->_ey, _vm->_actor->_camera->_ez, _x3d, _y3d, _z3d); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; _curX = _x3d; _curZ = _z3d; } } } _lookX = _curX; _lookZ = _curZ; pointOut(); _vm->_actor->_px -= _vm->_actor->_dx; _vm->_actor->_pz -= _vm->_actor->_dz; } /** * Brings out point from inner panel */ void PathFinding3D::pointOut() { const float largeValue = 60.0f; // 30 cm = 15 enlarge * 2 float x = 0.0f, z = 0.0f; float inters = 32000.0f; // If I hit the floor, I have to count how many times // the straight line intersects with the wide panels if (_curPanel < 0) return; SPan *panel = &_panel[_curPanel]; float nx = panel->_z1 - panel->_z2; float nz = panel->_x2 - panel->_x1; float temp = sqrt(nx * nx + nz * nz); nx /= temp; nz /= temp; // move the point on the wide panel for (int i = 0; i < _panelNum; ++i) { panel = &_panel[i]; // Only check the external panels with the same flag if ((panel->_flags & 0x80000000) && (panel->_flags & (_panel[_curPanel]._flags & 0x7FFFFFFF))) { // check point 1 temp = _vm->dist2D(_curX, _curZ, panel->_x1, panel->_z1); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; x = panel->_x1; z = panel->_z1; } // check point 2 temp = _vm->dist2D(_curX, _curZ, panel->_x2, panel->_z2); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; x = panel->_x2; z = panel->_z2; } // check point a 1/3 temp = _vm->dist2D(_curX, _curZ, (panel->_x1 * 2.0f + panel->_x2) / 3.0f, (panel->_z1 * 2.0f + panel->_z2) / 3.0f); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; x = (panel->_x1 * 2.0f + panel->_x2) / 3.0f; z = (panel->_z1 * 2.0f + panel->_z2) / 3.0f; } // check point a 2/3 temp = _vm->dist2D(_curX, _curZ, (panel->_x1 + panel->_x2 * 2.0f) / 3.0f, (panel->_z1 + panel->_z2 * 2.0f) / 3.0f); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; x = (panel->_x1 + panel->_x2 * 2.0f) / 3.0f; z = (panel->_z1 + panel->_z2 * 2.0f) / 3.0f; } // check intersection with camera if (intersectLineLine(panel->_x1, panel->_z1, panel->_x2, panel->_z2, _vm->_actor->_camera->_ex, _vm->_actor->_camera->_ez, _curX, _curZ)) { temp = _vm->dist2D(_curX, _curZ, _x3d, _z3d); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; x = _x3d; z = _z3d; } } // check intersection with character if (intersectLineLine(panel->_x1, panel->_z1, panel->_x2, panel->_z2, _vm->_actor->_px, _vm->_actor->_pz, _curX, _curZ)) { temp = _vm->dist2D(_curX, _curZ, _x3d, _z3d); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; x = _x3d; z = _z3d; } } // check intersection with normal panel if (intersectLineLine(panel->_x1, panel->_z1, panel->_x2, panel->_z2, _curX + nx * largeValue, _curZ + nz * largeValue, _curX - nx * largeValue, _curZ - nz * largeValue)) { temp = _vm->dist2D(_curX, _curZ, _x3d, _z3d); if (_vm->floatComp(temp, inters) == -1) { inters = temp; _curPanel = i; x = _x3d; z = _z3d; } } } } _curX = x; _curZ = z; } /** * Projects 2D point in a 3D world */ void PathFinding3D::invPointProject(int x, int y) { float px = (float)(x - _vm->_cx) / _vm->_actor->_camera->_fovX; float py = (float)(y - _vm->_cy) / _vm->_actor->_camera->_fovY; _x3d = px * _invP[0][0] + py * _invP[0][1] + _invP[0][2]; _y3d = px * _invP[1][0] + py * _invP[1][1] + _invP[1][2]; _z3d = px * _invP[2][0] + py * _invP[2][1] + _invP[2][2]; _x3d += _vm->_actor->_camera->_ex; _y3d += _vm->_actor->_camera->_ey; _z3d += _vm->_actor->_camera->_ez; } /** * Intersects a 3D line with the panel */ bool PathFinding3D::intersectLinePanel(SPan *p, float x, float y, float z) { // If it's an enlarged panel if (p->_flags & 0x80000000) return false; float x1 = _vm->_actor->_camera->_ex; float y1 = _vm->_actor->_camera->_ey; float z1 = _vm->_actor->_camera->_ez; float dx = (x - x1); float dy = (y - y1); float dz = (z - z1); float t = sqrt(dx * dx + dy * dy + dz * dz); dx /= t; dy /= t; dz /= t; float nx = p->_z1 - p->_z2; float nz = p->_x2 - p->_x1; t = sqrt(nx * nx + nz * nz); nx /= t; nz /= t; // ny is always equal to zero for panels float n = nx * p->_x1 + nz * p->_z1 - nx * x - nz * z; float d = dx * nx + dz * nz; if (_vm->floatComp(d, 0.0f) != 0) { t = n / d; if (_vm->floatComp(t, 0.0f) <= 0) return false; _x3d = x1 + dx * t; _y3d = y1 + dy * t; _z3d = z1 + dz * t; float minX = MIN(p->_x1, p->_x2) - 1.5f; float maxX = MAX(p->_x1, p->_x2) + 1.5f; float minZ = MIN(p->_z1, p->_z2) - 1.5f; float maxZ = MAX(p->_z1, p->_z2) + 1.5f; // check if it fits inside the panel if (_vm->floatComp(_x3d, minX) >= 0 && _vm->floatComp(_x3d, maxX) <= 0 && _vm->floatComp(_y3d, 0.0) >= 0 && _vm->floatComp(_y3d, p->_h) <= 0 && _vm->floatComp(_z3d, minZ) >= 0 && _vm->floatComp(_z3d, maxZ) <= 0) return true; return false; } return false; } /** * Intersects 3D line with the floor */ bool PathFinding3D::intersectLineFloor(float x, float y, float z) { float x1 = _vm->_actor->_camera->_ex; float y1 = _vm->_actor->_camera->_ey; float z1 = _vm->_actor->_camera->_ez; float dx = (x - x1); float dy = (y - y1); float dz = (z - z1); float t = sqrt(dx * dx + dy * dy + dz * dz); dx /= t; dy /= t; dz /= t; // ny is always equal to 1 for the floor if (_vm->floatComp(dy, 0.0f) != 0) { t = -y / dy; if (_vm->floatComp(t, 0.0f) <= 0) return false; _x3d = x1 + dx * t; _y3d = y1 + dy * t; _z3d = z1 + dz * t; return true; } return false; } /** * Intersects a 2D line with a 2D line */ bool PathFinding3D::intersectLineLine(float xa, float ya, float xb, float yb, float xc, float yc, float xd, float yd) { float divisor = (float)((xb - xa) * (yd - yc) - (yb - ya) * (xd - xc)); if (_vm->floatComp(divisor, 0.0f) == 0) return false; float r = (float)((ya - yc) * (xd - xc) - (xa - xc) * (yd - yc)) / divisor; float s = (float)((ya - yc) * (xb - xa) - (xa - xc) * (yb - ya)) / divisor; if (_vm->floatComp(r, 0.0f) == -1 || _vm->floatComp(r, 1.0f) == 1 || _vm->floatComp(s, 0.0f) == -1 || _vm->floatComp(s, 1.0f) == 1) return false; _x3d = xa + r * (xb - xa); _y3d = 0.0f; _z3d = ya + r * (yb - ya); return true; } /** * Tells after which panel the character stands */ void PathFinding3D::actorOrder() { const float largeValue = 15.0f; // 30 cm (max) Actor *actor = _vm->_actor; if (_forcedActorPos != BOX_NORMAL) { _actorPos = _forcedActorPos; return; } float ox = actor->_px + actor->_dx - actor->_camera->_ex; float oz = actor->_pz + actor->_dz - actor->_camera->_ez; float dist = sqrt(ox * ox + oz * oz); float lx = (-oz / dist) * largeValue; float lz = (ox / dist) * largeValue; ox = actor->_px + actor->_dx; oz = actor->_pz + actor->_dz; // It must be copied in front of the nearest box _actorPos = _sortPan[1]._num; // from closest to farthest for (int i = 1; i < _numSortPanel; ++i) { for (int j = 0; j < _panelNum; ++j) { // If it's not wide and belongs to this level if (!(_panel[j]._flags & 0x80000000) && (_panel[j]._flags & (1 << (_sortPan[i]._num - 1)))) { // If it intersects the center of the character camera if (intersectLineLine(_panel[j]._x1, _panel[j]._z1, _panel[j]._x2, _panel[j]._z2, actor->_camera->_ex, actor->_camera->_ez, ox, oz) || intersectLineLine(_panel[j]._x1, _panel[j]._z1, _panel[j]._x2, _panel[j]._z2, actor->_camera->_ex, actor->_camera->_ez, ox + lx, oz + lz) || intersectLineLine(_panel[j]._x1, _panel[j]._z1, _panel[j]._x2, _panel[j]._z2, actor->_camera->_ex, actor->_camera->_ez, ox - lx, oz - lz)) { // If it intersects it must be copied after the next box _actorPos = _sortPan[i + 1]._num; } } } } } void PathFinding3D::syncGameStream(Common::Serializer &ser) { ser.syncAsSint32LE(_curPanel); ser.syncAsSint32LE(_oldPanel); } } // End of namespace Trecision