scummvm/engines/trecision/3d.cpp
2021-05-29 21:24:31 +01:00

2505 lines
67 KiB
C++

/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "trecision/3d.h"
#include "trecision/actor.h"
#include "trecision/graphics.h"
#include "trecision/trecision.h"
namespace Trecision {
#define SHADOWVERTSNUM 42
static int16 _shadowVerts[SHADOWVERTSNUM] = {
6, 15, 23,
24, 32, 78,
80, 81, 83,
86, 90, 99,
107, 108, 116,
155, 157, 158,
160, 164, 168,
169, 173, 174,
187, 188, 192,
193, 213, 215,
227, 229, 235,
238, 249, 250,
252, 253, 299,
306, 330, 336
};
#define SHADOWFACESNUM 48
int16 _shadowFaces[SHADOWFACESNUM][3] = {
22, 21, 5,
7, 5, 22,
7, 19, 5,
5, 2, 19,
27, 24, 16,
27, 16, 18,
18, 16, 9,
18, 13, 9,
13, 9, 2,
3, 19, 12,
25, 26, 17,
17, 15, 25,
17, 19, 15,
15, 12, 19,
20, 23, 8,
8, 6, 20,
6, 9, 3,
3, 8, 6,
12, 3, 4,
4, 11, 12,
35, 4, 11,
13, 2, 1,
1, 14, 13,
14, 37, 1,
1, 34, 37,
31, 36, 37,
37, 30, 31,
29, 34, 35,
35, 29, 28,
36, 11, 31,
30, 37, 14,
29, 1, 34,
28, 4, 35,
36, 10, 35,
35, 32, 10,
37, 0, 34,
37, 33, 0,
0, 33, 39,
39, 40, 0,
10, 38, 32,
32, 41, 38,
36, 35, 34,
36, 37, 35,
11, 36, 35,
38, 40, 41,
41, 38, 39,
2, 19, 13,
3, 9, 12
};
Renderer3D::Renderer3D(TrecisionEngine *vm) : _vm(vm) {
_zBuffer = new int16[ZBUFFERSIZE / 2];
_curPage = nullptr;
_minXClip = 0;
_minYClip = 0;
_maxXClip = 0;
_maxYClip = 0;
_zBufStartX = 0;
_zBufStartY = 0;
_zBufWid = 0;
_shadowLightNum = 0;
_totalShadowVerts = 0;
// data for the triangle routines
for (int i = 0; i < 480; ++i) {
_lEdge[i] = 0;
_rEdge[i] = 0;
_lColor[i] = 0;
_rColor[i] = 0;
_lZ[i] = 0;
_rZ[i] = 0;
_lTextX[i] = 0;
_rTextX[i] = 0;
_lTextY[i] = 0;
_rTextY[i] = 0;
}
for (int i = 0; i < 10; ++i)
_shadowIntens[i] = 0;
for (int i = 0; i < MAXVERTEX; ++i) {
_vVertex[i]._x = 0;
_vVertex[i]._y = 0;
_vVertex[i]._z = 0;
_vVertex[i]._angle = 0;
_shVertex[i]._x = 0;
_shVertex[i]._y = 0;
_shVertex[i]._z = 0;
_shVertex[i]._nx = 0;
_shVertex[i]._ny = 0;
_shVertex[i]._nz = 0;
}
}
Renderer3D::~Renderer3D() {
delete[] _zBuffer;
}
void Renderer3D::textureTriangle(int32 x1, int32 y1, int32 z1, int32 c1, int32 tx1, int32 ty1,
int32 x2, int32 y2, int32 z2, int32 c2, int32 tx2, int32 ty2,
int32 x3, int32 y3, int32 z3, int32 c3, int32 tx3, int32 ty3,
STexture *t) {
int32 cl; // color of left edge of horizontal scanline
int32 zl; // zbuffer of left edge of horizontal scanline
int32 olx; // texture x of left edge of horizontal scanline
int32 oly; // texture y of left edge of horizontal scanline
int16 y; // looping variable
if (y1 > _maxYClip)
y1 = _maxYClip;
if (y1 < _minYClip)
y1 = _minYClip;
int16 yBottom = y1;
int16 yTop = y1;
uint8 *texture = t->_texture;
if (yBottom > y2) {
if (y2 < _minYClip)
y2 = _minYClip;
yBottom = y2;
}
if (yTop < y2) {
if (y2 > _maxYClip)
y2 = _maxYClip;
yTop = y2;
}
if (yBottom > y3) {
if (y3 < _minYClip)
y3 = _minYClip;
yBottom = y3;
}
if (yTop < y3) {
if (y3 > _maxYClip)
y3 = _maxYClip;
yTop = y3;
}
for (y = yBottom; y < yTop; y++) {
_lEdge[y] = _maxXClip;
_rEdge[y] = _minXClip;
}
// scan the edges of the triangle
textureScanEdge(x1, y1, z1, c1, tx1, ty1, x2, y2, z2, c2, tx2, ty2);
textureScanEdge(x2, y2, z2, c2, tx2, ty2, x3, y3, z3, c3, tx3, ty3);
textureScanEdge(x3, y3, z3, c3, tx3, ty3, x1, y1, z1, c1, tx1, ty1);
// Gouraud fill the horizontal scanlines
for (y = yBottom; y < yTop; y++) {
int32 el = _lEdge[y];
if (el < _minXClip)
el = _minXClip;
int32 er = _rEdge[y];
if (er > _maxXClip)
er = _maxXClip;
// edge right - edge left
int16 dx = er - el;
if (!(dx <= 0)) {
// slope dc/_dx
int32 mc = ((int16)(_rColor[y] - (cl = _lColor[y])) << 8) / dx;
// slope _dz/_dx
int32 mz = ((int32)(_rZ[y] - (zl = _lZ[y])) << 16) / dx;
// slope dty/_dx
int32 mtx = ((int32)(_rTextX[y] - (olx = _lTextX[y])) << 16) / dx;
// slope dty/_dx
int32 mty = ((int32)(_rTextY[y] - (oly = _lTextY[y])) << 16) / dx;
// screen offset
int32 sl = el + MAXX * y;
// pointer to zbuffer
int16 *z = _zBuffer + (y - _zBufStartY) * _zBufWid + (el - _zBufStartX);
uint16 *screenPtr = _curPage + sl;
zl <<= 16;
cl <<= 8;
olx <<= 16;
oly <<= 16;
// loop through every pixel in horizontal scanline
while (dx) {
sl = zl >> 16;
if (*z > sl) {
*screenPtr = (uint16)(_vm->_actor->_textureMat[texture[(olx >> 16) + t->_dx * (oly >> 16)]][cl >> 9]);
*z = (int16)sl;
}
screenPtr++; // increase screen x
z++; // increase zbuffer
zl += mz; // increase the zbuffer by _dz/_dx
cl += mc; // increase the color by dc/_dx
olx += mtx;
oly += mty;
dx--; // pixel to do --
}
}
}
}
void Renderer3D::textureScanEdge(int32 x1, int32 y1, int32 z1, int32 c1, int32 tx1, int32 ty1, int32 x2, int32 y2, int32 z2, int32 c2, int32 tx2, int32 ty2) {
// make sure that edge goes from top to bottom
int16 dy = y2 - y1;
if (dy < 0) {
SWAP(y1, y2);
SWAP(x1, x2);
SWAP(c1, c2);
SWAP(z1, z2);
SWAP(tx1, tx2);
SWAP(ty1, ty2);
dy = -dy;
}
if (dy == 0)
dy = 1;
// initialize for stepping
int32 mx = ((x2 - x1) << 16) / dy; // dx/dy
int32 mz = ((z2 - z1) << 16) / dy; // dz/dy
int32 mc = ((c2 - c1) << 8) / dy; // dc/dy
int32 mtx = ((tx2 - tx1) << 16) / dy;
int32 mty = ((ty2 - ty1) << 16) / dy;
x1 <<= 16; // starting x coordinate
z1 <<= 16; // starting z coordinate
c1 <<= 8; // starting c color
tx1 <<= 16;
ty1 <<= 16;
// step through edge and record color values along the way
for (int32 count = y1; count < y2; count++) {
int16 x = (uint16)(x1 >> 16);
if (x < _lEdge[count]) {
_lEdge[count] = x;
_lZ[count] = (int16)(z1 >> 16);
_lTextX[count] = (uint16)(tx1 >> 16);
_lTextY[count] = (uint16)(ty1 >> 16);
_lColor[count] = (uint8)(c1 >> 8);
}
if (x > _rEdge[count]) {
_rEdge[count] = x;
_rZ[count] = (int16)(z1 >> 16);
_rTextX[count] = (uint16)(tx1 >> 16);
_rTextY[count] = (uint16)(ty1 >> 16);
_rColor[count] = (uint8)(c1 >> 8);
}
x1 += mx; // x = x + dx/dy
c1 += mc; // c = c + dc/dy
z1 += mz; // z = z + dz/dy
tx1 += mtx;
ty1 += mty;
}
}
void Renderer3D::shadowTriangle(int32 x1, int32 y1, int32 x2, int32 y2,
int32 x3, int32 y3, uint8 cv, int32 zv) {
if (y1 > _maxYClip)
y1 = _maxYClip;
if (y1 < _minYClip)
y1 = _minYClip;
int16 yBottom = y1;
int16 yTop = y1;
if (yBottom > y2) {
if (y2 < _minYClip)
y2 = _minYClip;
yBottom = y2;
}
if (yTop < y2) {
if (y2 > _maxYClip)
y2 = _maxYClip;
yTop = y2;
}
if (yBottom > y3) {
if (y3 < _minYClip)
y3 = _minYClip;
yBottom = y3;
}
if (yTop < y3) {
if (y3 > _maxYClip)
y3 = _maxYClip;
yTop = y3;
}
for (int16 y = yBottom; y < yTop; y++) {
_lEdge[y] = _maxXClip;
_rEdge[y] = _minXClip;
}
// scan the edges of the triangle
shadowScanEdge(x1, y1, x2, y2);
shadowScanEdge(x2, y2, x3, y3);
shadowScanEdge(x3, y3, x1, y1);
// gouraud fill the horizontal scanlines
for (int16 y = yBottom; y < yTop; y++) {
// coordinate of left edge of horizontal scanline
int32 el = _lEdge[y];
if (el < _minXClip)
el = _minXClip;
// coordinate of right edge of horizontal scanline
int32 er = _rEdge[y];
if (er > _maxXClip)
er = _maxXClip;
// edge right - edge left
int16 dx = er - el;
if (!(dx <= 0)) {
// screen offset
int32 sl = el + MAXX * y;
int16 *zBufferPtr = _zBuffer + (y - _zBufStartY) * _zBufWid + (el - _zBufStartX);
uint16 *screenPtr = _curPage + sl;
// loop through every pixel in horizontal scanline
while (dx) {
if (*zBufferPtr != zv) {
*screenPtr = _vm->_graphicsMgr->shadow(*screenPtr, cv);
*zBufferPtr = zv;
}
screenPtr++; // increase screen x
zBufferPtr++; // increase zbuffer
dx--; // pixel to do --
}
}
}
}
void Renderer3D::shadowScanEdge(int32 x1, int32 y1, int32 x2, int32 y2) {
// make sure that edge goes from top to bottom
int16 dy = y2 - y1;
if (dy < 0) {
SWAP(y1, y2);
SWAP(x1, x2);
dy = -dy;
}
if (dy == 0)
dy = 1;
// initialize for stepping
int32 mx = ((x2 - x1) << 16) / dy; // slope dx/dy
x1 <<= 16; // starting x coordinate
// step through edge and record color values along the way
for (int32 count = y1; count < y2; count++) {
int16 x = (int16)(x1 >> 16);
if (x < _lEdge[count])
_lEdge[count] = x;
if (x > _rEdge[count])
_rEdge[count] = x;
x1 += mx; // x = x + dx/dy
}
}
/*------------------------------------------------
Initialize a 3D Room
--------------------------------------------------*/
void Renderer3D::init3DRoom(uint16 *destBuffer) {
_curPage = destBuffer;
_vm->_cx = (MAXX - 1) / 2;
_vm->_cy = (MAXY - 1) / 2;
for (int c = 0; c < ZBUFFERSIZE / 2; ++c)
_zBuffer[c] = 0x7FFF;
}
void Renderer3D::resetZBuffer(int x1, int y1, int x2, int y2) {
if (x1 > x2 || y1 > y2)
return;
int size = (x2 - x1) * (y2 - y1);
if (size * 2 > ZBUFFERSIZE)
warning("Warning: _zBuffer size %d!\n", size * 2);
int16 *d = _zBuffer;
for (int i = 0; i < size; ++i)
*d++ = 0x7FFF;
}
/*------------------------------------------------
Change the clipping area
--------------------------------------------------*/
void Renderer3D::setClipping(int16 x1, int16 y1, int16 x2, int16 y2) {
_minXClip = x1;
_minYClip = y1;
_maxXClip = x2;
_maxYClip = y2;
}
void Renderer3D::setZBufferRegion(int16 sx, int16 sy, int16 dx) {
_zBufStartX = sx;
_zBufStartY = sy;
_zBufWid = dx;
}
/*------------------------------------------------
Determines whether a triangle has clockwise
or counterclockwise vertices
--------------------------------------------------*/
int8 Renderer3D::clockWise(int16 x1, int16 y1, int16 x2, int16 y2, int16 x3, int16 y3) {
x2 -= x1;
y2 -= y1;
x3 -= x1;
y3 -= y1;
int32 a1 = ((int32)x2) * y3;
int32 a2 = ((int32)y2) * x3;
if (a1 > a2)
return 1; // clockwise
if (a1 < a2)
return -1; // counterclockwise
a1 = ((int32)x2) * x3;
a2 = ((int32)y2) * y3;
if (a1 < 0 || a2 < 0)
return -1;
a1 = ((int32)x2) * x2 + ((int32)y2) * y2;
a2 = ((int32)x3) * x3 + ((int32)y3) * y3;
if (a1 < a2)
return 1;
return 0;
}
/*------------------------------------------------
Draw the character
--------------------------------------------------*/
void Renderer3D::drawCharacter(uint8 flag) {
Actor *actor = _vm->_actor;
// Compute pointer to frame
if (flag & CALCPOINTS) {
if (actor->_curAction > hLAST)
error("Error in drawCharacter() - _curAction > hLAST");
int cfp = 0;
int cur = 0;
while (cur < actor->_curAction)
cfp += _vm->_defActionLen[cur++];
if (actor->_curAction == hWALKOUT)
cfp = 1;
cfp += actor->_curFrame;
if (actor->_curAction == hLAST)
cfp = 0;
actor->_vertex = &actor->_characterArea[cfp * actor->_vertexNum];
}
SCamera *_curCamera = actor->_camera;
SLight *_curLight = actor->_light;
STexture *_curTexture = actor->_texture;
SVertex *_curVertex = actor->_vertex;
SFace *_curFace = actor->_face;
int vertexNum = actor->_vertexNum;
if (flag & CALCPOINTS) {
_shadowLightNum = 0;
_totalShadowVerts = 0;
// camera matrix
float e10 = _curCamera->_e1[0];
float e11 = _curCamera->_e1[1];
float e12 = _curCamera->_e1[2];
float e20 = _curCamera->_e2[0];
float e21 = _curCamera->_e2[1];
float e22 = _curCamera->_e2[2];
float e30 = _curCamera->_e3[0];
float e31 = _curCamera->_e3[1];
float e32 = _curCamera->_e3[2];
// Light directions
float l0 = 0.0;
float l1 = 0.0;
float l2 = 0.0;
actor->_lim[0] = 32000;
actor->_lim[1] = -32000;
actor->_lim[2] = 32000;
actor->_lim[3] = -32000;
actor->_lim[4] = 32000;
actor->_lim[5] = -32000;
float t = (actor->_theta * PI2) / 360.0;
float cost = cos(t);
float sint = sin(t);
// Put all vertices in dark color
for (int i = 0; i < MAXVERTEX; ++i)
_vVertex[i]._angle = 180;
float dist;
float tx = 0;
float ty = 0;
float tz = 0;
float pa0, pa1, pa2;
for (uint32 b = 0; b < actor->_lightNum; b++) {
// if off lint == 0
// if it has a shadow lint & 0x80
int lint = _curLight->_inten & 0x7F;
if (lint) { // if it's not turned off
tx = _curLight->_x - actor->_px - actor->_dx; // computes direction vector
tz = _curLight->_z - actor->_pz - actor->_dz; // between light and actor
ty = _curLight->_y;
if (_curLight->_position) { // if it's attenuated
dist = sqrt(tx * tx + ty * ty + tz * tz); // Distance light <--> actor
// adjust light intensity due to the distance
if (dist > _curLight->_outr) // if it's out of range it's off
lint = 0;
else if (dist > _curLight->_inr) // if it's inside the circle it's decreased
lint = (int)((float)lint * (_curLight->_outr - dist) / (_curLight->_outr - _curLight->_inr));
}
}
if (lint) { // If it's still on
// Light rotates around the actor
l0 = tx * cost - tz * sint;
l2 = tx * sint + tz * cost;
l1 = ty;
t = sqrt(l0 * l0 + l1 * l1 + l2 * l2);
l0 /= t;
l1 /= t;
l2 /= t;
// Adjust light intensity according to the spot
tx = (float)_curLight->_fallOff;
if (tx) { // for light spot only
ty = (float)_curLight->_hotspot;
pa0 = _curLight->_dx * cost - _curLight->_dz * sint;
pa1 = _curLight->_dy;
pa2 = _curLight->_dx * sint + _curLight->_dz * cost;
t = sqrt(pa0 * pa0 + pa1 * pa1 + pa2 * pa2);
pa0 /= t;
pa1 /= t;
pa2 /= t;
tz = acos((pa0 * l0) + (pa1 * l1) + (pa2 * l2)) * 360.0 / PI2;
tz = CLIP(tz, 0.f, 180.f);
// tx falloff
// ty hotspot
// tz current angle
_shadowIntens[_shadowLightNum] = SHADOWAMBIENT;
if (tz > tx) { // if it's out of the falloff
lint = 0;
_shadowIntens[_shadowLightNum] = 0;
} else if (tz > ty) { // if it's between the falloff and the hotspot
lint = (int)((float)lint * (tx - tz) / (tx - ty));
_shadowIntens[_shadowLightNum] = (int)((float)_shadowIntens[_shadowLightNum] * (tx - tz) / (tx - ty));
}
}
}
if ((_curLight->_inten & 0x80) && lint) { // if it's shadowed and still on
_curVertex = actor->_vertex;
// casts shadow vertices
for (int a = 0; a < SHADOWVERTSNUM; a++) {
pa0 = _curVertex[_shadowVerts[a]]._x;
pa1 = _curVertex[_shadowVerts[a]]._y;
pa2 = _curVertex[_shadowVerts[a]]._z;
_shVertex[vertexNum + _totalShadowVerts + a]._x = pa0 - (pa1 * l0);
_shVertex[vertexNum + _totalShadowVerts + a]._z = pa2 - (pa1 * l2);
_shVertex[vertexNum + _totalShadowVerts + a]._y = 0;
}
// per default all shadows are equally faint
// _shadowIntens[_shadowLightNum] = SHADOWAMBIENT;
_shadowLightNum++;
_totalShadowVerts += SHADOWVERTSNUM;
}
if (lint) { // if still on
// adapts the light vector o its intensity
t = (float)(lint) / 127.0;
l0 = l0 * t;
l1 = l1 * t;
l2 = l2 * t;
_curVertex = actor->_vertex;
for (int a = 0; a < vertexNum; a++) {
pa0 = _curVertex->_nx;
pa1 = _curVertex->_ny;
pa2 = _curVertex->_nz;
lint = (int)((acos(pa0 * l0 + pa1 * l1 + pa2 * l2) * 360.0) / PI);
lint = CLIP(lint, 0, 180);
_vVertex[a]._angle -= (180 - lint);
_curVertex++;
}
}
_curLight++;
}
// rearranged light values so they can be viewed
for (int a = 0; a < vertexNum; a++)
_vVertex[a]._angle = CLIP(_vVertex[a]._angle, 0, 180);
_curVertex = actor->_vertex;
// Calculate the distance of the character from the room
tx = _curCamera->_ex - actor->_px;
ty = _curCamera->_ey;
tz = _curCamera->_ez - actor->_pz;
dist = tx * e30 + ty * e31 + tz * e32;
for (int a = 0; a < vertexNum + _totalShadowVerts; a++) {
if (a < vertexNum) {
l0 = _curVertex->_x;
l1 = _curVertex->_z;
pa1 = ty - _curVertex->_y;
} else {
l0 = _shVertex[a]._x;
l1 = _shVertex[a]._z;
pa1 = ty - _shVertex[a]._y;
}
pa0 = tx - (l0 * cost + l1 * sint); // rotate _curVertex
pa2 = tz - (-l0 * sint + l1 * cost);
l0 = pa0 * e10 + pa1 * e11 + pa2 * e12; // project _curVertex
l1 = pa0 * e20 + pa1 * e21 + pa2 * e22;
l2 = pa0 * e30 + pa1 * e31 + pa2 * e32;
int _x2d = _vm->_cx + (int)((l0 * _curCamera->_fovX) / l2);
int _y2d = _vm->_cy + (int)((l1 * _curCamera->_fovY) / l2);
_vVertex[a]._x = _x2d;
_vVertex[a]._y = _y2d;
_vVertex[a]._z = (int32)((dist - l2) * 128.0);
actor->_lim[0] = MIN(_x2d, actor->_lim[0]);
actor->_lim[1] = MAX(_x2d, actor->_lim[1]);
actor->_lim[2] = MIN(_y2d, actor->_lim[2]);
actor->_lim[3] = MAX(_y2d, actor->_lim[3]);
actor->_lim[4] = MIN(_vVertex[a]._z, actor->_lim[4]);
actor->_lim[5] = MAX(_vVertex[a]._z, actor->_lim[5]);
_curVertex++;
}
actor->_lim[4] = (int)dist;
actor->_lim[5] = (int)dist;
// vertex clipping
actor->_lim[0] = (actor->_lim[0] <= _minXClip + 1) ? _minXClip : actor->_lim[0]--;
actor->_lim[1] = (actor->_lim[1] >= _maxXClip - 1) ? _maxXClip : actor->_lim[1]++;
actor->_lim[2] = (actor->_lim[2] <= _minYClip + 1) ? _minYClip : actor->_lim[2]--;
actor->_lim[3] = (actor->_lim[3] >= _maxYClip - 1) ? _maxYClip : actor->_lim[3]++;
if (actor->_curAction == hLAST) // exit displacer
actor->_lim[2] = actor->_lim[3] - (((actor->_lim[3] - actor->_lim[2]) * actor->_curFrame) / _vm->_defActionLen[hLAST]);
// set zbuffer vars
setZBufferRegion(actor->_lim[0], actor->_lim[2], actor->_lim[1] - actor->_lim[0]);
}
if (flag & DRAWFACES) {
if (actor->_curAction == hLAST)
setClipping(0, actor->_lim[2], MAXX, actor->_lim[3]);
for (int b = 0; b < _shadowLightNum; b++) {
for (int a = 0; a < SHADOWFACESNUM; a++) {
int p0 = _shadowFaces[a][0] + vertexNum + b * SHADOWVERTSNUM;
int p1 = _shadowFaces[a][1] + vertexNum + b * SHADOWVERTSNUM;
int p2 = _shadowFaces[a][2] + vertexNum + b * SHADOWVERTSNUM;
int px0 = _vVertex[p0]._x;
int py0 = _vVertex[p0]._y;
int px1 = _vVertex[p1]._x;
int py1 = _vVertex[p1]._y;
int px2 = _vVertex[p2]._x;
int py2 = _vVertex[p2]._y;
shadowTriangle(px0, py0, px1, py1, px2, py2, 127 - _shadowIntens[b], (int16)(0x7FF0 + b));
}
}
for (uint a = 0; a < actor->_faceNum; ++a) {
int p0 = _curFace->_a;
int p1 = _curFace->_b;
int p2 = _curFace->_c;
int px0 = _vVertex[p0]._x;
int py0 = _vVertex[p0]._y;
int px1 = _vVertex[p1]._x;
int py1 = _vVertex[p1]._y;
int px2 = _vVertex[p2]._x;
int py2 = _vVertex[p2]._y;
if (clockWise(px0, py0, px1, py1, px2, py2) > 0) {
uint16 b = _curFace->_mat;
if (_curTexture[b]._flag & TEXTUREACTIVE) {
textureTriangle(px0, py0, _vVertex[p0]._z, _vVertex[p0]._angle, actor->_textureCoord[a][0][0], actor->_textureCoord[a][0][1],
px1, py1, _vVertex[p1]._z, _vVertex[p1]._angle, actor->_textureCoord[a][1][0], actor->_textureCoord[a][1][1],
px2, py2, _vVertex[p2]._z, _vVertex[p2]._angle, actor->_textureCoord[a][2][0], actor->_textureCoord[a][2][1],
&_curTexture[b]);
}
}
_curFace++;
}
int p0 = 0;
for (int b = _zBufStartY; b < actor->_lim[3]; b++) {
int px0 = b * MAXX + _zBufStartX;
for (int a = 1; a < _zBufWid; a++) {
// CHECKME: These are always 0
//bool _shadowSplit;
int py1 = 0; //(_zBuf[p0] >= 0x7FF0) * 0x8000 * _shadowSplit;
int py2 = 0; //(_zBuf[p0 + 1] >= 0x7FF0) * 0x8000 * _shadowSplit;
int p1 = _zBuffer[p0] < 0x7FFF;
int p2 = _zBuffer[p0 + 1] < 0x7FFF;
if (p1 != p2) {
int px1 = _curPage[px0 + a - 1];
int px2 = _curPage[px0 + a];
_curPage[px0 + a - 1] = _vm->_graphicsMgr->aliasing(px1, px2, 6); // 75% 25%
_curPage[px0 + a] = _vm->_graphicsMgr->aliasing(px1, px2, 2); // 25% 75%
// if the first is the character
if (p1)
_zBuffer[p0] = 0x00BF | py1;
else
_zBuffer[p0] = 0x003F | py2;
if (a + 1 < _zBufWid) {
p0++;
a++;
// if the second is the character
if (p2)
_zBuffer[p0] = 0x00BF | py2;
else
_zBuffer[p0] = 0x003F | py1;
}
} else {
// set value alpha max
if (p1)
_zBuffer[p0] = 0x00FF | py1;
else
_zBuffer[p0] = 0x0000 | py1;
}
p0++;
// if it's the last of the line
if (a == _zBufWid - 1) {
if (p2)
_zBuffer[p0] = 0x00FF | py2;
else
_zBuffer[p0] = 0x0000 | py2;
}
}
p0++;
}
if (actor->_curAction == hLAST)
setClipping(0, TOP, MAXX, AREA + TOP);
}
}
// Path Finding
PathFinding3D::PathFinding3D(TrecisionEngine *vm) : _vm(vm) {
_lookX = 0.0f;
_lookZ = 0.0f;
_curStep = 0;
_lastStep = 0;
_characterInMovement = false;
_characterGoToPosition = -1;
_panelNum = 0;
for (int i = 0; i < MAXPATHNODES; ++i) {
_pathNode[i]._x = 0.0f;
_pathNode[i]._z = 0.0f;
_pathNode[i]._dist = 0.0f;
_pathNode[i]._oldPanel = 0;
_pathNode[i]._curPanel = 0;
}
_curPanel = -1;
_oldPanel = -1;
_numPathNodes = 0;
_numSortPan = 0;
_x3d = 0.0f;
_y3d = 0.0f;
_z3d = 0.0f;
_curX = 0.0f;
_curZ = 0.0f;
}
PathFinding3D::~PathFinding3D() {
}
void PathFinding3D::findPath() {
int b;
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) &&
// behind the starting panel
(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]._near1, _curX, _curZ) ||
pointInside(b = _panel[_oldPanel]._near2, _curX, _curZ))) ||
// behind the panel corner2
((_vm->dist2D(_panel[_oldPanel]._x2, _panel[_oldPanel]._z2, actor->_px, actor->_pz) < EPSILON) &&
(pointInside(b = _panel[_oldPanel]._near2, _curX, _curZ) ||
pointInside(b = _panel[_oldPanel]._near1, _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 (b = 0; b < _panelNum; b++) {
if (_panel[b]._flags & 0x80000000) { // it must be a wide panel
if (intersectLineLine(_panel[b]._x1, _panel[b]._z1,
_panel[b]._x2, _panel[b]._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 = b;
_pathNode[_numPathNodes]._curPanel = b;
_numPathNodes++;
// CORNERS - lever intersections in corners
if ((b == _panel[_oldPanel]._near1) || (b == _panel[_oldPanel]._near2)) {
// otherwise if it's near the starting panel
if ((_pathNode[_numPathNodes - 1]._dist < EPSILON) &&
(b != _oldPanel) && (b != _curPanel)) {
// and the distance is very small to the intersection
inters--;
_numPathNodes--;
// If the click is inside the nearby panel
if ((_curPanel < 0) && (pointInside(b, _curX, _curZ))) {
_curX = actor->_px;
_curZ = actor->_pz;
actor->_px -= actor->_dx;
actor->_pz -= actor->_dz;
_curPanel = b;
lookAt(_lookX, _lookZ);
return;
}
}
} else if ((b == _panel[_curPanel]._near1) || (b == _panel[_curPanel]._near2)) {
// otherwise if it is near the finish panel
if ((fabs(_pathNode[_numPathNodes - 1]._dist - dist) < EPSILON) &&
(b != _oldPanel) && (b != _curPanel)) {
// and the distance is very small to the intersection
inters--;
_numPathNodes--;
}
}
} else if (b == _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.0;
_pathNode[_numPathNodes]._oldPanel = _oldPanel;
_pathNode[_numPathNodes]._curPanel = _oldPanel;
_numPathNodes++;
} else if (b == _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 (b = 0; b < _panelNum; b++) {
if (!(_panel[b]._flags & 0x80000000)) {
if (intersectLineLine(_panel[b]._x1, _panel[b]._z1,
_panel[b]._x2, _panel[b]._z2,
_pathNode[_numPathNodes - 1]._x, _pathNode[_numPathNodes - 1]._z,
_curX, _curZ))
inters++;
} else {
if (_panel[b]._col1 & 0x80) {
if (intersectLineLine(_panel[b]._x1, _panel[b]._z1,
_panel[_panel[b]._col1 & 0x7F]._x2, _panel[_panel[b]._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[b]._x1, _panel[b]._z1,
_panel[_panel[b]._col1 & 0x7F]._x1, _panel[_panel[b]._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[b]._col2 & 0x80) {
if (intersectLineLine(_panel[b]._x2, _panel[b]._z2,
_panel[_panel[b]._col2 & 0x7F]._x2, _panel[_panel[b]._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[b]._x2, _panel[b]._z2,
_panel[_panel[b]._col2 & 0x7F]._x1, _panel[_panel[b]._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.0;
_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 curp, nearp, oldp;
float destx, destz;
signed int a, b, c, fail = 0;
int count = 0;
// Add departure
TempPath[count]._x = _vm->_actor->_px;
TempPath[count]._z = _vm->_actor->_pz;
TempPath[count]._dist = 0.0;
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 (a = 0; a < _numPathNodes - 1; a++) {
memcpy(&TempPath[count], &_pathNode[a], sizeof(SPathNode));
count++;
if (count >= MAXPATHNODES - 2)
count = MAXPATHNODES - 2;
curp = _pathNode[a]._curPanel;
// if source and destination panel are on the same block
if (!findAttachedPanel(curp, _pathNode[a + 1]._curPanel))
continue;
// go around obstacle starting with _near1
len1 = evalPath(a, _panel[curp]._x1, _panel[curp]._z1, _panel[curp]._near1) + _vm->dist2D(_pathNode[a]._x, _pathNode[a]._z, _panel[curp]._x1, _panel[curp]._z1);
// go around obstacle starting with _near2
len2 = evalPath(a, _panel[curp]._x2, _panel[curp]._z2, _panel[curp]._near2) + _vm->dist2D(_pathNode[a]._x, _pathNode[a]._z, _panel[curp]._x2, _panel[curp]._z2);
// Check which route was shorter
if ((len1 < 32000.0) && (len2 < 32000.0)) {
if (len1 < len2) {
destx = _panel[curp]._x1;
destz = _panel[curp]._z1;
nearp = _panel[curp]._near1;
} else {
destx = _panel[curp]._x2;
destz = _panel[curp]._z2;
nearp = _panel[curp]._near2;
}
float curx = _pathNode[a]._x;
float curz = _pathNode[a]._z;
oldp = curp;
b = 0;
// Save the shorter path
for (;;) {
TempPath[count]._x = curx;
TempPath[count]._z = curz;
TempPath[count]._oldPanel = oldp;
TempPath[count]._curPanel = curp;
count++;
if (count >= MAXPATHNODES - 2)
count = MAXPATHNODES - 2;
// if it reaches the point, exit the loop
if (curp == _pathNode[a + 1]._curPanel) {
memcpy(&TempPath[count], &_pathNode[a + 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 (((curp == _pathNode[a]._curPanel) && b) || (b > _panelNum)) {
fail = 1; // stop at the edge first
break; // and stop walking
}
// otherwise go to the next panel
if (_panel[nearp]._near1 == curp) {
// go to summit 2 next time
curx = destx;
curz = destz;
destx = _panel[nearp]._x2;
destz = _panel[nearp]._z2;
oldp = curp;
curp = nearp;
nearp = _panel[curp]._near2;
} else {
// go to summit 1 next time
curx = destx;
curz = destz;
destx = _panel[nearp]._x1;
destz = _panel[nearp]._z1;
oldp = curp;
curp = nearp;
nearp = _panel[curp]._near1;
}
b++;
}
} else {
fail = 1;
}
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.0;
TempPath[count]._oldPanel = _curPanel;
TempPath[count]._curPanel = _curPanel;
count++;
// after walking around all obstacles, optimize
_numPathNodes = 0;
for (a = 0; a < count; a++) {
if (_numPathNodes > MAXPATHNODES - 2)
_numPathNodes = MAXPATHNODES - 2;
// remove all the attached nodes
for (b = count - 1; b >= a; b--) {
if (_vm->dist2D(TempPath[b]._x, TempPath[b]._z, TempPath[a]._x, TempPath[a]._z) < EPSILON)
break;
}
a = b;
memcpy(&_pathNode[_numPathNodes], &TempPath[a], sizeof(SPathNode));
_numPathNodes++;
for (b = count - 1; b > a + 1; b--) {
int inters = 0;
for (c = 0; c < _panelNum; c++) {
// it must never intersect the small panel
if (!(_panel[c]._flags & 0x80000000)) {
if (intersectLineLine(_panel[c]._x1, _panel[c]._z1,
_panel[c]._x2, _panel[c]._z2,
TempPath[a]._x, TempPath[a]._z,
TempPath[b]._x, TempPath[b]._z))
inters++;
if (_panel[c]._col1 & 0x80) {
if (intersectLineLine(_panel[c]._x1, _panel[c]._z1,
_panel[_panel[c]._col1 & 0x7F]._x2, _panel[_panel[c]._col1 & 0x7F]._z2,
TempPath[a]._x, TempPath[a]._z,
TempPath[b]._x, TempPath[b]._z)) {
len2 = _vm->dist2D(_x3d, _z3d, TempPath[a]._x, TempPath[a]._z);
len1 = _vm->dist2D(_x3d, _z3d, TempPath[b]._x, TempPath[b]._z);
// intersect at a point distant from the start and the finish
if ((len1 > EPSILON) && (len2 > EPSILON))
inters++;
}
} else {
if (intersectLineLine(_panel[c]._x1, _panel[c]._z1,
_panel[_panel[c]._col1 & 0x7F]._x1, _panel[_panel[c]._col1 & 0x7F]._z1,
TempPath[a]._x, TempPath[a]._z,
TempPath[b]._x, TempPath[b]._z)) {
len2 = _vm->dist2D(_x3d, _z3d, TempPath[a]._x, TempPath[a]._z);
len1 = _vm->dist2D(_x3d, _z3d, TempPath[b]._x, TempPath[b]._z);
// intersect at a point distant from the start and the finish
if ((len1 > EPSILON) && (len2 > EPSILON))
inters++;
}
}
if (_panel[c]._col2 & 0x80) {
if (intersectLineLine(_panel[c]._x2, _panel[c]._z2,
_panel[_panel[c]._col2 & 0x7F]._x2, _panel[_panel[c]._col2 & 0x7F]._z2,
TempPath[a]._x, TempPath[a]._z,
TempPath[b]._x, TempPath[b]._z)) {
len2 = _vm->dist2D(_x3d, _z3d, TempPath[a]._x, TempPath[a]._z);
len1 = _vm->dist2D(_x3d, _z3d, TempPath[b]._x, TempPath[b]._z);
// intersect at a point distant from the start and the finish
if ((len1 > EPSILON) && (len2 > EPSILON))
inters++;
}
} else {
if (intersectLineLine(_panel[c]._x2, _panel[c]._z2,
_panel[_panel[c]._col2 & 0x7F]._x1, _panel[_panel[c]._col2 & 0x7F]._z1,
TempPath[a]._x, TempPath[a]._z,
TempPath[b]._x, TempPath[b]._z)) {
len2 = _vm->dist2D(_x3d, _z3d, TempPath[a]._x, TempPath[a]._z);
len1 = _vm->dist2D(_x3d, _z3d, TempPath[b]._x, TempPath[b]._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) {
curp = _pathNode[_numPathNodes - 1]._curPanel;
oldp = TempPath[b]._oldPanel;
for (c = a; c <= b; c++) {
if ((TempPath[c]._oldPanel == curp) && (TempPath[c]._curPanel == oldp))
break;
}
// if they weren't connected it means it went through the floor
if (c > b) {
_pathNode[_numPathNodes - 1]._curPanel = -1; // start
TempPath[b]._oldPanel = -1; // destination
}
a = b - 1;
break;
}
}
}
}
/*------------------------------------------------
Evaluate path length
--------------------------------------------------*/
float PathFinding3D::evalPath(int a, float destX, float destZ, int nearP) {
int b = 0;
float len = 0.0;
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) && b) || (b > _panelNum)) {
len += 32000.0; // Absurd length
break;
}
// Otherwise it goes to the next plane
// if nearP is attached to curp via vertex1
if (_panel[nearP]._near1 == 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]._near2;
} 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]._near1;
}
b++;
}
return len;
}
/*------------------------------------------------
Check if a point is inside a panel
--------------------------------------------------*/
bool PathFinding3D::pointInside(int pan, float x, float z) {
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;) {
bool yflag1 = (vtx1[1] >= z);
if (yflag0 != yflag1) {
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 a = 0; a < _vm->_actor->_lightNum; a++) {
// If it's off
if (curLight->_inten == 0) {
// If it's the required position
if (curLight->_position == num) {
_vm->_actor->_px = curLight->_x;
_vm->_actor->_pz = curLight->_z;
_vm->_actor->_dx = 0.0;
_vm->_actor->_dz = 0.0;
float ox = curLight->_dx;
float oz = curLight->_dz;
// If it's a null light
if ((ox == 0.0) && (oz == 0.0))
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 / PI;
if (theta >= 360.0)
theta -= 360.0;
if (theta < 0.0)
theta += 360.0;
_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;
}
}
curLight++;
}
}
void PathFinding3D::goToPosition(int num) {
SLight *_curLight = _vm->_actor->_light;
for (uint32 a = 0; a < _vm->_actor->_lightNum; a++) {
// If it's off and if it's a position
if (_curLight->_inten == 0) {
// If it's the right position
if (_curLight->_position == num) {
_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;
}
}
_curLight++;
}
}
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 ((ox == 0.0) && (oz == 0.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 / PI;
if (theta >= 360.0f)
theta -= 360.0f;
if (theta < 0.0f)
theta += 360.0f;
float approx = theta - _step[_lastStep]._theta;
if ((approx < 30.0f) && (approx > -30.0f))
approx = 0.0f;
else if (approx > 180.0f)
approx = -360.0f + approx;
else if (approx < -180.0)
approx = 360.0f + approx;
approx /= 3.0;
// Antepenultimate 1/3
_step[_lastStep]._theta += approx;
_step[_lastStep]._theta = (_step[_lastStep]._theta > 360.0) ? _step[_lastStep]._theta - 360.0 : (_step[_lastStep]._theta < 0.0) ? _step[_lastStep]._theta + 360.0 : _step[_lastStep]._theta;
// Penultimate 2/3
memcpy(&_step[_lastStep + 1], &_step[_lastStep], sizeof(SStep));
_lastStep++;
_step[_lastStep]._theta += approx;
_step[_lastStep]._theta = (_step[_lastStep]._theta > 360.0) ? _step[_lastStep]._theta - 360.0 : (_step[_lastStep]._theta < 0.0) ? _step[_lastStep]._theta + 360.0 : _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 a = 1; a < _numPathNodes; a++) {
for (int c = 0; c < _panelNum; c++) {
// it must never intersect narrow panel
if (!(_panel[c]._flags & 0x80000000)) {
if (intersectLineLine(_panel[c]._x1, _panel[c]._z1,
_panel[c]._x2, _panel[c]._z2,
_pathNode[a - 1]._x, _pathNode[a - 1]._z,
_pathNode[a]._x, _pathNode[a]._z)) {
_numPathNodes = a;
break;
}
}
}
}
float len = 0.0;
float curlen = 0.0;
float ox = _pathNode[0]._x;
float oz = _pathNode[0]._z;
for (int a = 1; a < _numPathNodes; a++) {
len += _vm->dist3D(_pathNode[a]._x, 0.0, _pathNode[a]._z, ox, 0.0, oz);
ox = _pathNode[a]._x;
oz = _pathNode[a]._z;
}
// total route length calculated - if too small, returns
if (len < 2.0) {
lookAt(_lookX, _lookZ);
return;
}
int a = 0;
// compute offset
SVertex *v = _vm->_actor->_characterArea;
float firstframe = _vm->_actor->FRAMECENTER(v);
float startpos = 0.0;
// if he was already walking
int CurA, CurF, cfp;
if (_vm->_actor->_curAction == hWALK) {
// compute current frame
cfp = _vm->_defActionLen[hSTART] + 1 + _vm->_actor->_curFrame;
v += cfp * _vm->_actor->_vertexNum;
CurA = hWALK;
CurF = _vm->_actor->_curFrame;
// if it wasn't the last frame, take the next step
if (_vm->_actor->_curFrame < _vm->_defActionLen[hWALK] - 1) {
cfp++;
CurF++;
v += _vm->_actor->_vertexNum;
}
} else if ((_vm->_actor->_curAction >= hSTOP0) && (_vm->_actor->_curAction <= hSTOP9)) {
// if he was stopped, starts moving again
// compute current frame
CurA = hWALK;
//o CurF = _vm->_actor->_curAction - hSTOP1;
CurF = _vm->_actor->_curAction - hSTOP0;
cfp = _vm->_defActionLen[hSTART] + 1 + CurF;
v += cfp * _vm->_actor->_vertexNum;
} else {
// if he was standing, start working or turn
oz = 0.0;
cfp = 1;
CurA = hSTART;
CurF = 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
while (((curlen = oz + _vm->_actor->FRAMECENTER(v) - firstframe) < len) || (!a)) {
_step[a]._pz = oz - firstframe; // where to render
_step[a]._dz = curlen; // where it is
_step[a]._curAction = CurA;
_step[a]._curFrame = CurF;
a++;
v += _vm->_actor->_vertexNum;
CurF++;
cfp++;
if (CurF >= _vm->_defActionLen[CurA]) {
if (CurA == hSTART) {
CurA = hWALK;
CurF = 0;
cfp = _vm->_defActionLen[hSTART] + 1;
ox = 0.0;
} else if (CurA == hWALK) {
CurA = hWALK;
CurF = 0;
cfp = _vm->_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 ((oz + ox + _vm->_actor->FRAMECENTER(v) - firstframe) < len)
oz += ox;
else
break;
}
}
if (!a)
warning("buildFramelist - Unknown error: step number = 0");
// After the destination, add the stop frame
// if he was walking
if (_step[a - 1]._curAction == hWALK)
CurA = _step[a - 1]._curFrame + hSTOP0; // stop previous step.
else
CurA = hSTOP0; // stop step 01
assert(CurA <= hLAST); // _defActionLen below has a size of hLAST + 1
CurF = 0;
int b = 0;
cfp = 0;
while (b != CurA)
cfp += _vm->_defActionLen[b++];
v = &_vm->_actor->_characterArea[cfp * _vm->_actor->_vertexNum];
for (b = 0; b < _vm->_defActionLen[CurA]; b++) {
curlen = oz + _vm->_actor->FRAMECENTER(v) - firstframe;
_step[a]._pz = oz - firstframe; // where to render
_step[a]._dz = curlen; // where it is
_step[a]._curAction = CurA;
_step[a]._curFrame = CurF;
a++;
CurF++;
v += _vm->_actor->_vertexNum;
}
// how far is it from the destination?
float approx = (len - curlen - EPSILON) / (a - 2);
float theta = 0.0;
// Adjust all the steps so it arrives exactly where clicked
for (b = 1; b < a; b++) {
// verify there's no reverse step
if ((_step[b - 1]._dz > (_step[b]._dz + approx * b)) || ((_step[b]._dz + approx * b + EPSILON) >= len)) {
theta = _step[b]._dz - _step[b]._pz;
_step[b]._dz = _step[b - 1]._dz;
_step[b]._pz = _step[b]._dz - theta;
} else {
_step[b]._pz += (approx * b);
_step[b]._dz += (approx * b);
}
}
float cx = _step[b - 1]._dz;
_lastStep = b; // last step
_curStep = 0; // current step
// now insert exact directions and start and destination points
b = 0;
len = 0.0;
startpos = 0.0;
for (a = 0; a < _numPathNodes - 1; a++) {
curlen = 0.0;
len += _vm->dist3D(_pathNode[a]._x, 0.0, _pathNode[a]._z,
_pathNode[a + 1]._x, 0.0, _pathNode[a + 1]._z);
// determine the direction
ox = _pathNode[a + 1]._x - _pathNode[a]._x;
oz = _pathNode[a + 1]._z - _pathNode[a]._z;
// if it's a useless node, remove it
if ((ox == 0.0) && (oz == 0.0)) {
continue;
}
approx = sqrt(ox * ox + oz * oz);
ox /= approx;
oz /= approx;
theta = _vm->sinCosAngle(ox, oz) * 180.0f / PI + 180.0f;
if (theta >= 360.0)
theta -= 360.0;
if (theta < 0.0)
theta += 360.0;
while ((b < _lastStep) && (_step[b]._dz <= len)) {
curlen = (_step[b]._dz - _step[b]._pz);
_step[b]._px = _pathNode[a]._x + (_step[b]._pz - startpos) * ox;
_step[b]._pz = _pathNode[a]._z + (_step[b]._pz - startpos) * oz;
_step[b]._dx = curlen * ox;
_step[b]._dz = curlen * oz;
_step[b]._theta = theta;
_step[b]._curPanel = _pathNode[a]._curPanel;
b++;
}
startpos = len;
}
reset(b, _curX, _curZ, theta);
_lastStep = b; // 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) && (_step[0]._theta == _step[1]._theta)) {
approx = theta - oldtheta;
if (approx > 180.0)
approx = -360.0 + approx;
else if (approx < -180.0)
approx = 360.0 + approx;
approx /= 3.0;
for (b = 0; b < 2; b++) {
_step[b]._theta = oldtheta + (float)(b + 1) * approx;
_step[b]._theta = (_step[b]._theta > 360.0) ? _step[b]._theta - 360.0 : (_step[b]._theta < 0.0) ? _step[b]._theta + 360.0 : _step[b]._theta;
theta = _step[b]._theta;
curlen = sqrt(_step[b]._dx * _step[b]._dx + _step[b]._dz * _step[b]._dz);
theta = ((270.0 - theta) * PI) / 180.0;
ox = cos(theta) * curlen;
oz = sin(theta) * curlen;
cx = _step[b]._px + _step[b]._dx;
float cz = _step[b]._pz + _step[b]._dz;
_step[b]._px += _step[b]._dx - ox;
_step[b]._pz += _step[b]._dz - oz;
_step[b]._dx = cx - _step[b]._px;
_step[b]._dz = cz - _step[b]._pz;
}
}
// makes the curve
oldtheta = _step[2]._theta;
for (b = 3; b <= _lastStep; b++) {
theta = _step[b]._theta;
// if it made a curve
if (oldtheta != theta) {
approx = theta - oldtheta;
if (approx > 180.0)
approx = -360.0 + approx;
else if (approx < -180.0)
approx = 360.0 + approx;
approx /= 3.0;
// for the previous one
_step[b - 1]._theta += approx;
_step[b - 1]._theta = (_step[b - 1]._theta > 360.0) ? _step[b - 1]._theta - 360.0 : (_step[b - 1]._theta < 0.0) ? _step[b - 1]._theta + 360.0 : _step[b - 1]._theta;
oldtheta = _step[b - 1]._theta;
startpos = oldtheta;
curlen = sqrt(_step[b - 1]._dx * _step[b - 1]._dx + _step[b - 1]._dz * _step[b - 1]._dz);
oldtheta = ((270.0 - oldtheta) * PI) / 180.0;
ox = cos(oldtheta) * curlen;
oz = sin(oldtheta) * curlen;
cx = _step[b - 1]._px + _step[b - 1]._dx;
float cz = _step[b - 1]._pz + _step[b - 1]._dz;
_step[b - 1]._px += _step[b - 1]._dx - ox;
_step[b - 1]._pz += _step[b - 1]._dz - oz;
_step[b - 1]._dx = cx - _step[b - 1]._px;
_step[b - 1]._dz = cz - _step[b - 1]._pz;
// for the next one
_step[b]._theta -= approx;
_step[b]._theta = (_step[b]._theta > 360.0) ? _step[b]._theta - 360.0 : (_step[b]._theta < 0.0) ? _step[b]._theta + 360.0 : _step[b]._theta;
oldtheta = theta;
theta = _step[b]._theta;
curlen = sqrt(_step[b]._dx * _step[b]._dx + _step[b]._dz * _step[b]._dz);
theta = ((270.0 - theta) * PI) / 180.0;
ox = cos(theta) * curlen;
oz = sin(theta) * curlen;
cx = _step[b]._px + _step[b]._dx;
cz = _step[b]._pz + _step[b]._dz;
_step[b]._px += _step[b]._dx - ox;
_step[b]._pz += _step[b]._dz - oz;
_step[b]._dx = cx - _step[b]._px;
_step[b]._dz = cz - _step[b]._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(int srcPanel, int 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;
int curPanel = srcPanel;
int nearPanel = _panel[srcPanel]._near1;
for (int b = 0;; b++) {
// if they are attached, return true
if (curPanel == destPanel)
return true;
// if it has returned to the starting panel, return false
if (srcPanel == curPanel && b)
return false;
if (b > _panelNum)
return false;
// if they are attached to vertex 1, take 2
if (_panel[nearPanel]._near1 == curPanel) {
curPanel = nearPanel;
nearPanel = _panel[curPanel]._near2;
} else {
curPanel = nearPanel;
nearPanel = _panel[curPanel]._near1;
}
}
}
/*------------------------------------------------
Compare route distance (qsort)
--------------------------------------------------*/
int pathCompare(const void *arg1, const void *arg2) {
SPathNode *p1 = (SPathNode *)arg1;
SPathNode *p2 = (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() {
_numSortPan = 31;
for (int a = 1; a < _numSortPan - 1; ++a) {
_sortPan[a]._min = 32000.0f;
_sortPan[a]._num = a;
}
// 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 b = 0; b < _panelNum; ++b) {
if (!(_panel[b]._flags & 0x80000000)) {
float dist1 = _vm->dist3D(actor->_camera->_ex, 0.0, actor->_camera->_ez, _panel[b]._x1, 0.0, _panel[b]._z1);
float dist2 = _vm->dist3D(actor->_camera->_ex, 0.0, actor->_camera->_ez, _panel[b]._x2, 0.0, _panel[b]._z2);
float min = MIN(dist1, dist2);
for (int a = 0; a < _numSortPan; ++a) {
if (_panel[b]._flags & (1 << a)) {
if (_sortPan[a + 1]._min > min)
_sortPan[a + 1]._min = min;
}
}
}
}
sortPanel();
for (int b = 0; b < _numSortPan; ++b) {
if (_sortPan[b]._num == BOX_BACKGROUND) {
// now the panels go from 0 (foreground) to _numSortPan (background)
_numSortPan = b;
break;
}
}
}
void PathFinding3D::read3D(Common::SeekableReadStream *ff) {
// read panels
_panelNum = ff->readSint32LE();
if (_panelNum > MAXPANELSINROOM)
error("read3D(): Too many panels");
for (int i = 0; i < _panelNum; ++i) {
_panel[i]._x1 = ff->readFloatLE();
_panel[i]._z1 = ff->readFloatLE();
_panel[i]._x2 = ff->readFloatLE();
_panel[i]._z2 = ff->readFloatLE();
_panel[i]._h = ff->readFloatLE();
_panel[i]._flags = ff->readUint32LE();
_panel[i]._near1 = ff->readSByte();
_panel[i]._near2 = ff->readSByte();
_panel[i]._col1 = ff->readSByte();
_panel[i]._col2 = ff->readSByte();
}
// projection matrix
float _proj[3][3];
_proj[0][0] = _vm->_actor->_camera->_e1[0];
_proj[0][1] = _vm->_actor->_camera->_e1[1];
_proj[0][2] = _vm->_actor->_camera->_e1[2];
_proj[1][0] = _vm->_actor->_camera->_e2[0];
_proj[1][1] = _vm->_actor->_camera->_e2[1];
_proj[1][2] = _vm->_actor->_camera->_e2[2];
_proj[2][0] = _vm->_actor->_camera->_e3[0];
_proj[2][1] = _vm->_actor->_camera->_e3[1];
_proj[2][2] = _vm->_actor->_camera->_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 (det == 0.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) {
SSortPan *p1 = (SSortPan *)arg1;
SSortPan *p2 = (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], _numSortPan, sizeof(SSortPan), panelCompare);
}
/*------------------------------------------------
Find the 3D point corresponding to the 2D point
--------------------------------------------------*/
void PathFinding3D::whereIs(int px, int py) {
float inters = 32000.0;
_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 b = 0; b < _panelNum; b++) {
if (intersectLinePanel(&_panel[b], x, y, z)) {
float temp = _vm->dist3D(_vm->_actor->_camera->_ex, _vm->_actor->_camera->_ey, _vm->_actor->_camera->_ez, _x3d, _y3d, _z3d);
if (temp < inters) {
inters = temp;
_curPanel = b;
_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() {
#define LARGEVAL 60.0 // 30 cm = 15 enlarge * 2
float x = 0.0, z = 0.0;
float inters = 32000.0;
// TODO: ask an italian speaker the meaning of this comment :)
// se ho beccato il pavimento devo contare quante volte interseca
// i box larghi la retta omino pto
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 b = 0; b < _panelNum; b++) {
panel = &_panel[b];
// 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 (temp < inters) {
inters = temp;
_curPanel = b;
x = panel->_x1;
z = panel->_z1;
}
// check point 2
temp = _vm->dist2D(_curX, _curZ, panel->_x2, panel->_z2);
if (temp < inters) {
inters = temp;
_curPanel = b;
x = panel->_x2;
z = panel->_z2;
}
// check point a 1/3
temp = _vm->dist2D(_curX, _curZ, (panel->_x1 * 2.0 + panel->_x2) / 3.0, (panel->_z1 * 2.0 + panel->_z2) / 3.0);
if (temp < inters) {
inters = temp;
_curPanel = b;
x = (panel->_x1 * 2.0 + panel->_x2) / 3.0;
z = (panel->_z1 * 2.0 + panel->_z2) / 3.0;
}
// check point a 2/3
temp = _vm->dist2D(_curX, _curZ, (panel->_x1 + panel->_x2 * 2.0) / 3.0, (panel->_z1 + panel->_z2 * 2.0) / 3.0);
if (temp < inters) {
inters = temp;
_curPanel = b;
x = (panel->_x1 + panel->_x2 * 2.0) / 3.0;
z = (panel->_z1 + panel->_z2 * 2.0) / 3.0;
}
// 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 (temp < inters) {
inters = temp;
_curPanel = b;
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 (temp < inters) {
inters = temp;
_curPanel = b;
x = _x3d;
z = _z3d;
}
}
// check intersection with normal panel
if (intersectLineLine(panel->_x1, panel->_z1, panel->_x2, panel->_z2,
_curX + nx * LARGEVAL, _curZ + nz * LARGEVAL, _curX - nx * LARGEVAL, _curZ - nz * LARGEVAL)) {
temp = _vm->dist2D(_curX, _curZ, _x3d, _z3d);
if (temp < inters) {
inters = temp;
_curPanel = b;
x = _x3d;
z = _z3d;
}
}
}
}
_curX = x;
_curZ = z;
#undef LARGEVAL
}
/*------------------------------------------------
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 = (float)(px * _invP[0][0] + py * _invP[0][1] + _invP[0][2]);
_y3d = (float)(px * _invP[1][0] + py * _invP[1][1] + _invP[1][2]);
_z3d = (float)(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 (d != 0.0) {
t = n / d;
if (t <= 0.0)
return false;
_x3d = x1 + dx * t;
_y3d = y1 + dy * t;
_z3d = z1 + dz * t;
float minx = MIN(p->_x1, p->_x2) - 1.5;
float maxx = MAX(p->_x1, p->_x2) + 1.5;
float minz = MIN(p->_z1, p->_z2) - 1.5;
float maxz = MAX(p->_z1, p->_z2) + 1.5;
// check if it fits inside the panel
if ((_x3d >= minx) && (_x3d <= maxx) && (_y3d >= 0.0) && (_y3d <= p->_h) && (_z3d >= minz) && (_z3d <= maxz))
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 (dy != 0.0) {
t = -y / dy;
if (t <= 0.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 (divisor == 0.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 ((r < 0) || (r > 1) || (s < 0) || (s > 1))
return false;
_x3d = xa + r * (xb - xa);
_y3d = 0.0;
_z3d = ya + r * (yb - ya);
return true;
}
/*------------------------------------------------
Tells after which panel the character stands
--------------------------------------------------*/
void PathFinding3D::actorOrder() {
#define LARGEVAL 15.0 // 30 cm (max)
Actor *actor = _vm->_actor;
if (_vm->_forcedActorPos != BOX_NORMAL) {
_vm->_actorPos = _vm->_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) * LARGEVAL;
float lz = (ox / dist) * LARGEVAL;
ox = actor->_px + actor->_dx;
oz = actor->_pz + actor->_dz;
// It must be copied in front of the nearest box
_vm->_actorPos = _sortPan[1]._num;
// from closest to farthest
for (int b = 1; b < _numSortPan; b++) {
for (int a = 0; a < _panelNum; a++) {
// If it's not wide and belongs to this level
if (!(_panel[a]._flags & 0x80000000) && (_panel[a]._flags & (1 << (_sortPan[b]._num - 1)))) {
// If it intersects the center of the character camera
if (intersectLineLine(_panel[a]._x1, _panel[a]._z1, _panel[a]._x2, _panel[a]._z2, actor->_camera->_ex, actor->_camera->_ez, ox, oz) || intersectLineLine(_panel[a]._x1, _panel[a]._z1, _panel[a]._x2, _panel[a]._z2, actor->_camera->_ex, actor->_camera->_ez, ox + lx, oz + lz) || intersectLineLine(_panel[a]._x1, _panel[a]._z1, _panel[a]._x2, _panel[a]._z2, actor->_camera->_ex, actor->_camera->_ez, ox - lx, oz - lz)) {
// If it intersects it must be copied after the next box
_vm->_actorPos = _sortPan[b + 1]._num;
}
}
}
}
}
} // End of namespace Trecision