/* 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 "tetraedge/tetraedge.h"
#include "common/file.h"
#include "common/compression/deflate.h"
#include "tetraedge/te/te_free_move_zone.h"
#include "tetraedge/te/micropather.h"
#include "tetraedge/te/te_renderer.h"
#include "tetraedge/te/te_ray_intersection.h"
#include "tetraedge/te/te_core.h"
//#define TETRAEDGE_DUMP_PATHFINDING_DATA 1
namespace Tetraedge {
/*static*/
//TeIntrusivePtr TeFreeMoveZone::_globalCamera;
/*static*/
bool TeFreeMoveZone::_collisionSlide = false;
class TeFreeMoveZoneGraph : micropather::Graph {
friend class TeFreeMoveZone;
TeFreeMoveZoneGraph() : _owner(nullptr), _bordersDistance(2048.0f) {}
TeVector2s32 _size;
Common::Array _flags;
float _bordersDistance;
TeFreeMoveZone *_owner;
// These don't match ScummVM naming convention but are needed to match MicroPather API.
virtual float LeastCostEstimate(void * stateStart, void *stateEnd);
virtual void AdjacentCost(void *state, Common::Array *adjacent);
virtual void PrintStateInfo(void *state);
int flag(const TeVector2s32 &loc);
void setSize(const TeVector2s32 &size);
void deserialize(Common::ReadStream &stream);
void serialize(Common::WriteStream &stream) const;
float costForPoint(TeVector2s32 pt) {
int flg = flag(pt);
if (flg == 1)
return FLT_MAX;
if (flg == 2)
return _bordersDistance;
return 1.0;
}
};
TeFreeMoveZone::TeFreeMoveZone() : _actzones(nullptr), _blockers(nullptr), _rectBlockers(nullptr),
_transformedVerticiesDirty(true), _bordersDirty(true), _pickMeshDirty(true), _projectedPointsDirty(true),
_loadedFromBin(false), _gridWorldY(0.0), _gridSquareSize(5.0f, 5.0f), _gridDirty(true)
{
_graph = new TeFreeMoveZoneGraph();
_graph->_bordersDistance = 2048.0f;
_graph->_owner = this;
_micropather = new micropather::MicroPather(_graph);
}
TeFreeMoveZone::~TeFreeMoveZone() {
if (_camera)
_camera->onViewportChangedSignal().remove(this, &TeFreeMoveZone::onViewportChanged);
delete _micropather;
delete _graph;
}
float TeFreeMoveZone::bordersDistance() const {
return _graph->_bordersDistance;
}
TeVector2s32 TeFreeMoveZone::aStarResolution() const {
TeVector2f32 diff = (_gridBottomRight - _gridTopLeft);
TeVector2s32 retval = TeVector2s32(diff / _gridSquareSize) + TeVector2s32(1, 1);
if (retval._x > 2000)
retval._x = 200;
if (retval._y > 2000)
retval._y = 200;
return retval;
}
void TeFreeMoveZone::buildAStar() {
preUpdateGrid();
const TeVector2s32 graphSize = aStarResolution();
_graph->setSize(graphSize);
// Original checks these inside the loop below, seems like a waste as they never change?
if (graphSize._x == 0 || graphSize._y == 0)
return;
bool regenerate = true;
if (!_aszGridPath.empty()) {
regenerate = !loadAStar(_aszGridPath, graphSize);
}
if (!regenerate)
return;
if (!_loadedFromBin) {
for (int x = 0; x < graphSize._x; x++) {
for (int y = 0; y < graphSize._y; y++) {
byte blockerIntersection = hasBlockerIntersection(TeVector2s32(x, y));
if (blockerIntersection == 1) {
_graph->_flags[_graph->_size._x * y + x] = 1;
} else {
if (!hasCellBorderIntersection(TeVector2s32(x, y))) {
const float gridSquareX = _gridSquareSize.getX();
const float gridSquareY = _gridSquareSize.getY();
TeVector3f32 vout;
float fout;
TeVector3f32 gridPt(x * gridSquareX + _gridTopLeft.getX() + gridSquareX / 2,
1000000.0,
y * gridSquareY + _gridTopLeft.getY() + gridSquareY / 2);
bool doesIntersect = intersect(gridPt, TeVector3f32(0.0, -1.0, 0.0), vout, fout, true, nullptr);
if (!doesIntersect)
doesIntersect = intersect(gridPt, TeVector3f32(0.0, 1.0, 0.0), vout, fout, true, nullptr);
if (!doesIntersect)
_graph->_flags[graphSize._x * y + x] = 1;
else if (blockerIntersection == 2)
_graph->_flags[graphSize._x * y + x] = 2;
else
_graph->_flags[graphSize._x * y + x] = 0;
} else {
_graph->_flags[graphSize._x * y + x] = 2;
}
}
}
}
} else {
// Loaded from bin..
for (int x = 0; x < graphSize._x; x++) {
for (int y = 0; y < graphSize._y; y++) {
byte blockerIntersection = hasBlockerIntersection(TeVector2s32(x, y));
if (blockerIntersection == 1) {
_graph->_flags[_graph->_size._x * y + x] = 1;
} else {
if (!hasCellBorderIntersection(TeVector2s32(x, y))) {
const float gridSquareX = _gridSquareSize.getX();
const float gridSquareY = _gridSquareSize.getY();
TeVector3f32 gridPt = _gridMatrix * TeVector3f32(
x * gridSquareX + _gridTopLeft.getX() + gridSquareX / 2, 0.0,
y * gridSquareY + _gridTopLeft.getY() + gridSquareY / 2);
bool doesIntersect = intersect2D(TeVector2f32(gridPt.x(), gridPt.z()));
if (!doesIntersect)
_graph->_flags[graphSize._x * y + x] = 1;
else if (blockerIntersection == 2)
_graph->_flags[graphSize._x * y + x] = 2;
else
_graph->_flags[graphSize._x * y + x] = 0;
} else {
_graph->_flags[graphSize._x * y + x] = 2;
}
}
}
}
}
}
bool TeFreeMoveZone::loadAStar(const Common::Path &path, const TeVector2s32 &size) {
TetraedgeFSNode node = g_engine->getCore()->findFile(path);
Common::ScopedPtr file;
if (!node.isReadable()) {
warning("[TeFreeMoveZone::loadAStar] Can't open file : %s.", path.toString().c_str());
return false;
}
file.reset(node.createReadStream());
if (!file) {
warning("[TeFreeMoveZone::loadAStar] Can't open file : %s.", path.toString().c_str());
return false;
}
TeVector2s32 readSize;
readSize.deserialize(*file, readSize);
if (size != readSize) {
warning("[TeFreeMoveZone::loadAStar] Wrong file : %s.", path.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
uint32 bytes = file->readUint32LE();
if (bytes > 100000)
error("Improbable size %d for compressed astar data", bytes);
unsigned long decompBytes = size._x * size._y;
byte *buf = new byte[bytes];
byte *outBuf = new byte[decompBytes];
file->read(buf, bytes);
bool result = Common::inflateZlib(outBuf, &decompBytes, buf, bytes);
delete [] buf;
if (result) {
for (uint i = 0; i < decompBytes; i++)
_graph->_flags.data()[i] = outBuf[i];
}
delete [] outBuf;
return result;
}
void TeFreeMoveZone::calcGridMatrix() {
float angle = 0.0f;
float mul = 0.0f;
for (uint i = 0; i < _borders.size() - 1; i += 2) {
const TeVector3f32 &v1 = _verticies[_borders[i]];
const TeVector3f32 &v2 = _verticies[_borders[i + 1]];
const TeVector3f32 diff = v2 - v1;
const TeVector2f32 diff2(diff.x(), diff.z());
float len = diff2.length();
float f = fmod(atan2(diff.z(), diff.x()), M_PI_2);
if (f < 0)
f += (float)M_PI_2;
if (f - angle < -M_PI_4) {
angle -= (float)M_PI_2;
} else if (f - angle > M_PI_4) {
f -= (float)M_PI_2;
}
angle *= mul;
mul += len;
angle = fmod((f * len + angle) / mul, M_PI_2);
if (angle < 0)
angle += (float)M_PI_2;
}
const TeQuaternion rot = TeQuaternion::fromAxisAndAngle(TeVector3f32(0, 1, 0), angle);
const TeMatrix4x4 rotMatrix = rot.toTeMatrix();
_gridMatrix = TeMatrix4x4() * rotMatrix;
}
void TeFreeMoveZone::clear() {
setNbTriangles(0);
_pickMeshDirty = true;
_projectedPointsDirty = true;
_transformedVerticies.clear();
_borders.clear();
// TODO: Clear some other TeVector2f32 list here (field_0x178)
_gridDirty = true;
_graph->_flags.clear();
_graph->_size = TeVector2s32(0, 0);
_micropather->Reset();
}
Common::Array TeFreeMoveZone::collisions(const TeVector3f32 &v1, const TeVector3f32 &v2) {
updatePickMesh();
updateProjectedPoints();
error("TODO: Implement TeFreeMoveZone::collisions");
}
TeVector3f32 TeFreeMoveZone::correctCharacterPosition(const TeVector3f32 &pos, bool *flagout, bool intersectFlag) {
float f = 0.0;
TeVector3f32 intersectPoint;
TeVector3f32 testPos(pos.x(), 0, pos.z());
if (!intersect(testPos, TeVector3f32(0, -1, 0), intersectPoint, f, intersectFlag, nullptr)) {
if (!intersect(testPos, TeVector3f32(0, 1, 0), intersectPoint, f, intersectFlag, nullptr)) {
// Note: This flag should only ever get set in Syberia 2.
if (!_collisionSlide) {
if (flagout)
*flagout = false;
return pos;
}
return slide(pos);
}
}
if (flagout)
*flagout = true;
return intersectPoint;
}
TeIntrusivePtr TeFreeMoveZone::curve(const TeVector3f32 &startpt, const TeVector2s32 &clickPt, float param_5, bool findMeshFlag) {
updateGrid(false);
Common::Array meshes;
TeVector3f32 newend;
meshes.push_back(this);
TePickMesh2 *nearest = findNearestMesh(_camera, clickPt, meshes, &newend, findMeshFlag);
if (!nearest) {
if (g_engine->gameType() == TetraedgeEngine::kSyberia2) {
newend = findNearestPointOnBorder(TeVector2f32(clickPt));
} else {
return TeIntrusivePtr();
}
}
return curve(startpt, newend);
}
TeIntrusivePtr TeFreeMoveZone::curve(const TeVector3f32 &startpt, const TeVector3f32 &endpt) {
updateGrid(false);
const TeVector2s32 projectedStart = projectOnAStarGrid(startpt);
const TeVector2s32 projectedEnd = projectOnAStarGrid(endpt);
const int xsize = _graph->_size._x;
char *graphData = _graph->_flags.data();
float cost = 0;
Common::Array path;
int pathResult = _micropather->Solve(graphData + xsize * projectedStart._y + projectedStart._x,
graphData + xsize * projectedEnd._y + projectedEnd._x, &path, &cost);
TeIntrusivePtr retval;
if (pathResult == micropather::MicroPather::SOLVED || pathResult == micropather::MicroPather::START_END_SAME) {
Common::Array points;
for (auto pathpt : path) {
// each path point is an array offset
int offset = (char *)pathpt - graphData;
points.push_back(TeVector2s32(offset % xsize, offset / xsize));
}
Common::Array pts3d;
// Skip first and last points and use the exact start/end values.
pts3d.push_back(startpt);
for (int i = 1; i < (int)points.size() - 1; i++) {
pts3d.push_back(transformAStarGridInWorldSpace(points[i]));
}
pts3d.push_back(endpt);
#ifdef TETRAEDGE_DUMP_PATHFINDING_DATA
debug("curve: pathfind from %s to %s", startpt.dump().c_str(), endpt.dump().c_str());
debug("curve: %d grid points:", points.size());
for (uint i = 0; i < points.size(); i++)
debug("curve: gridpt %2d: %d, %d", i, points[i]._x, points[i]._y);
debug("curve: %d 3d (world) points:", pts3d.size());
for (uint i = 0; i < pts3d.size(); i++)
debug("curve: wrldpt %2d: %s", i, pts3d[i].dump().c_str());
uint firstsz = pts3d.size();
#endif
removeInsignificantPoints(pts3d);
#ifdef TETRAEDGE_DUMP_PATHFINDING_DATA
debug("curve: removed insignificant pts, %d -> %d", firstsz, pts3d.size());
#endif
retval = new TeBezierCurve();
retval->setControlPoints(pts3d);
}
// If no path found, return nullptr.
return retval;
}
/*static*/
void TeFreeMoveZone::deserialize(Common::ReadStream &stream, TeFreeMoveZone &dest, const Common::Array *blockers,
const Common::Array *rectblockers, const Common::Array *actzones) {
dest.clear();
TePickMesh2::deserialize(stream, dest);
TeVector2f32::deserialize(stream, dest._gridSquareSize);
dest._transformedVerticiesDirty = (stream.readByte() != 0);
dest._bordersDirty = (stream.readByte() != 0);
dest._pickMeshDirty = (stream.readByte() != 0);
dest._projectedPointsDirty = (stream.readByte() != 0);
dest._gridDirty = (stream.readByte() != 0);
Te3DObject2::deserializeVectorArray(stream, dest._freeMoveZoneVerticies);
Te3DObject2::deserializeUintArray(stream, dest._pickMesh);
Te3DObject2::deserializeVectorArray(stream, dest._transformedVerticies);
Te3DObject2::deserializeUintArray(stream, dest._borders);
TeOBP::deserialize(stream, dest._obp);
TeVector2f32::deserialize(stream, dest._gridTopLeft);
TeVector2f32::deserialize(stream, dest._gridBottomRight);
dest._gridWorldY = stream.readFloatLE();
dest._graph->deserialize(stream);
if (dest.name().contains("19000")) {
dest._gridSquareSize = TeVector2f32(2.0f, 2.0f);
dest._gridDirty = true;
}
dest._blockers = blockers;
dest._rectBlockers = rectblockers;
dest._actzones = actzones;
}
void TeFreeMoveZone::draw() {
if (!worldVisible())
return;
TeRenderer *renderer = g_engine->getRenderer();
renderer->enableWireFrame();
TePickMesh2::draw();
Common::SharedPtr mesh(TeMesh::makeInstance());
mesh->setConf(_borders.size(), _borders.size(), TeMesh::MeshMode_Lines, 0, 0);
for (uint i = 0; i < _borders.size(); i++) {
mesh->setIndex(i, i);
mesh->setVertex(i, verticies()[_borders[i]]);
}
const TeColor prevColor = renderer->currentColor();
renderer->pushMatrix();
renderer->multiplyMatrix(worldTransformationMatrix());
renderer->setCurrentColor(TeColor(0, 0x80, 0xff, 0xff));
mesh->draw();
if (!_loadedFromBin)
renderer->popMatrix();
if (!_gridDirty && false) {
const TeVector2s32 aStarRes = aStarResolution();
// Note: original iterates through the graph first here and
// calls flag but doesn't do anything with it.. not sure why?
for (int x = 0; x < aStarRes._x; x++) {
for (int y = 0; y < aStarRes._y; y++) {
float left = _gridSquareSize.getX() * x + _gridTopLeft.getX();
float top = _gridSquareSize.getY() * y + _gridTopLeft.getY();
const TeVector3f32 tl(left, _gridWorldY, top);
const TeVector3f32 tr(left + _gridSquareSize.getX(), _gridWorldY, top);
const TeVector3f32 bl(left, _gridWorldY, top + _gridSquareSize.getY());
const TeVector3f32 br(left + _gridSquareSize.getX(), _gridWorldY, top + _gridSquareSize.getY());
int flag = _graph->flag(TeVector2s32(x, y));
if (flag == 1) {
renderer->setCurrentColor(TeColor(0xff, 0xff, 0xff, 0xff));
} else if (flag == 2) {
renderer->setCurrentColor(TeColor(0xff, 0xff, 0, 0xff));
} else {
renderer->setCurrentColor(TeColor(0, 0xff, 0, 0xff));
}
renderer->drawLine(tl, tr);
renderer->drawLine(tr, br);
renderer->drawLine(tl, br);
renderer->drawLine(tr, bl);
}
}
}
// TODO: do a bunch of other drawing stuff here (line 294 on)
if (_loadedFromBin)
renderer->popMatrix();
renderer->setCurrentColor(prevColor);
renderer->disableWireFrame();
}
static int segmentIntersection(const TeVector2f32 &s1start, const TeVector2f32 &s1end,
const TeVector2f32 &s2start, const TeVector2f32 &s2end,
TeVector2f32 *sout, float *fout1, float *fout2) {
const TeVector2f32 s1len = s1end - s1start;
const TeVector2f32 s2len = s2end - s2start;
float det = s1len.getX() * s2len.getY() + s1len.getY() * s2len.getX();
int result = 0;
if (det != 0) {
result = 1;
float intersect1 = -((s1len.getY() * s1start.getX() +
(s1len.getX() * s2start.getY() - s1len.getX() * s1start.getY())) -
s1len.getY() * s2start.getX()) / det;
if (intersect1 >= 0.0f && intersect1 <= 1.0f) {
float intersect2 = -((s2len.getY() * s2start.getX() +
(s2len.getX() * s1start.getY() - s2len.getX() * s2start.getY())) -
s2len.getY() * s1start.getX()) / det;
if (intersect2 >= 0.0f && intersect2 <= 1.0f) {
result = 2;
if (sout)
*sout = s1start + s1len * intersect2;
if (fout1)
*fout1 = intersect2;
if (fout2)
*fout2 = intersect1;
}
}
}
return result;
}
TeVector3f32 TeFreeMoveZone::findNearestPointOnBorder(const TeVector2f32 &pt) {
TeVector3f32 retval;
const TeVector2f32 pt_x0(pt.getX(), 0);
const TeVector2f32 pt_x1(pt.getX(), _camera->getViewportHeight());
const TeVector2f32 pt_y0(0, pt.getY());
const TeVector2f32 pt_y1(_camera->getViewportWidth(), pt.getY());
updateProjectedPoints();
updateBorders();
float leastDist = FLT_MAX;
for (uint i = 0; i < _borders.size() / 2; i++) {
uint b1 = _borders[i * 2];
uint b2 = _borders[i * 2 + 1];
const TeVector2f32 &projb1 = _projectedPoints[b1];
const TeVector2f32 &projb2 = _projectedPoints[b2];
const TeVector3f32 &transb1 = _transformedVerticies[_pickMesh[b1]];
const TeVector3f32 &transb2 = _transformedVerticies[_pickMesh[b2]];
float dist = 0;
TeVector2f32 dir;
if (segmentIntersection(pt_x0, pt_x1, projb1, projb2, &dir, nullptr, &dist) == 2) {
float sqLen = (dir - pt).getSquareMagnitude();
if (sqLen < leastDist) {
retval = transb1 + (transb2 - transb1) * dist;
leastDist = sqLen;
}
}
if (segmentIntersection(pt_y0, pt_y1, projb1, projb2, &dir, nullptr, &dist) == 2) {
float sqLen = (dir - pt).getSquareMagnitude();
if (sqLen < leastDist) {
retval = transb1 + (transb2 - transb1) * dist;
leastDist = sqLen;
}
}
}
return retval;
}
byte TeFreeMoveZone::hasBlockerIntersection(const TeVector2s32 &pt) {
TeVector2f32 borders[4];
const float gridSquareX = _gridSquareSize.getX();
const float gridSquareY = _gridSquareSize.getY();
borders[0] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX(),
pt._y * gridSquareY + _gridTopLeft.getY());
borders[1] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX() + gridSquareX,
pt._y * gridSquareY + _gridTopLeft.getY());
borders[2] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX(),
pt._y * gridSquareY + _gridTopLeft.getY() + gridSquareY);
borders[3] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX() + gridSquareX,
pt._y * gridSquareY + _gridTopLeft.getY() + gridSquareY);
for (uint i = 0; i < _blockers->size(); i++) {
const TeBlocker &blocker = (*_blockers)[i];
if (blocker._s != name())
continue;
for (uint b = 0; b < 4; b++) {
int si = segmentIntersection(borders[b], borders[(b + 1) % 4], blocker._pts[0],
blocker._pts[1], nullptr, nullptr, nullptr);
if (si == 2)
return 2;
}
TeVector2f32 borderVec = ((borders[0] + borders[3]) / 2.0) - blocker._pts[0];
TeVector2f32 blockerVec = blocker._pts[1] - blocker._pts[0];
float dotVal = borderVec.dotProduct(blockerVec.getNormalized());
float crosVal = borderVec.crossProduct(blockerVec);
if ((crosVal < 0.0) && (0.0 <= dotVal)) {
if (dotVal < blockerVec.length())
return 1;
}
}
return 0;
}
bool TeFreeMoveZone::hasCellBorderIntersection(const TeVector2s32 &pt) {
TeVector2f32 borders[4];
const float gridSquareX = _gridSquareSize.getX();
const float gridSquareY = _gridSquareSize.getY();
borders[0] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX(),
pt._y * gridSquareY + _gridTopLeft.getY());
borders[1] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX() + gridSquareX,
pt._y * gridSquareY + _gridTopLeft.getY());
borders[2] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX(),
pt._y * gridSquareY + _gridTopLeft.getY() + gridSquareY);
borders[3] = TeVector2f32(pt._x * gridSquareX + _gridTopLeft.getX() + gridSquareX,
pt._y * gridSquareY + _gridTopLeft.getY() + gridSquareY);
int iresult = 0;
for (uint border = 0; border < _borders.size() / 2; border++) {
TeVector2f32 v1;
TeVector2f32 v2;
uint off1 = _pickMesh[_borders[border * 2]];
uint off2 = _pickMesh[_borders[border * 2 + 1]];
if (!_loadedFromBin) {
v1 = TeVector2f32(_transformedVerticies[off1].x(), _transformedVerticies[off1].z());
v2 = TeVector2f32(_transformedVerticies[off2].x(), _transformedVerticies[off2].z());
} else {
TeMatrix4x4 gridInverse = _gridMatrix;
gridInverse.inverse();
const TeVector3f32 v1_inv = gridInverse * _freeMoveZoneVerticies[off1];
const TeVector3f32 v2_inv = gridInverse * _freeMoveZoneVerticies[off2];
v1 = TeVector2f32(v1_inv.x(), v1_inv.z());
v2 = TeVector2f32(v2_inv.x(), v2_inv.z());
}
iresult = segmentIntersection(borders[0], borders[1], v1, v2, nullptr, nullptr, nullptr);
if (iresult == 2) break;
iresult = segmentIntersection(borders[1], borders[2], v1, v2, nullptr, nullptr, nullptr);
if (iresult == 2) break;
iresult = segmentIntersection(borders[2], borders[3], v1, v2, nullptr, nullptr, nullptr);
if (iresult == 2) break;
iresult = segmentIntersection(borders[3], borders[0], v1, v2, nullptr, nullptr, nullptr);
if (iresult == 2) break;
}
return iresult == 2;
}
TeActZone *TeFreeMoveZone::isInZone(const TeVector3f32 &pt) {
error("TODO: Implement TeFreeMoveZone::isInZone");
}
bool TeFreeMoveZone::loadBin(const Common::Path &path, const Common::Array *blockers,
const Common::Array *rectblockers, const Common::Array *actzones,
const TeVector2f32 &gridSize) {
TetraedgeFSNode node = g_engine->getCore()->findFile(path);
if (!node.isReadable()) {
warning("[TeFreeMoveZone::loadBin] Can't open file : %s.", node.getName().c_str());
return false;
}
_aszGridPath = path.append(".aszgrid");
Common::ScopedPtr file(node.createReadStream());
return loadBin(*file, blockers, rectblockers, actzones, gridSize);
}
bool TeFreeMoveZone::loadBin(Common::ReadStream &stream, const Common::Array *blockers,
const Common::Array *rectblockers, const Common::Array *actzones,
const TeVector2f32 &gridSize) {
_loadGridSize = gridSize;
_loadedFromBin = true;
// Load position, rotation, scale (not name)
Te3DObject2::deserialize(stream, *this, false);
Common::Array vecs;
Te3DObject2::deserializeVectorArray(stream, vecs);
uint32 triangles = stream.readUint32LE();
_freeMoveZoneVerticies.resize(triangles * 3);
_gridDirty = true;
_transformedVerticiesDirty = true;
_bordersDirty = true;
_pickMeshDirty = true;
_projectedPointsDirty = true;
for (uint v = 0; v < triangles * 3; v++) {
uint16 s = stream.readUint16LE();
if (s >= vecs.size())
error("Invalid vertex offset %d (of %d) loading TeFreeMoveZone", s, vecs.size());
_freeMoveZoneVerticies[v] = vecs[s];
}
updateTransformedVertices();
updatePickMesh();
_blockers = blockers;
_rectBlockers = rectblockers;
_actzones = actzones;
updateGrid(false);
Common::Path p(name());
setName(p.baseName());
return true;
}
bool TeFreeMoveZone::onViewportChanged() {
_projectedPointsDirty = true;
return false;
}
void TeFreeMoveZone::preUpdateGrid() {
updateTransformedVertices();
updatePickMesh();
updateBorders();
if (_loadedFromBin)
calcGridMatrix();
TeMatrix4x4 gridInverse = _gridMatrix;
gridInverse.inverse();
TeVector3f32 newVec;
if (_transformedVerticies.empty() || _pickMesh.empty()) {
debug("[TeFreeMoveZone::buildAStar] %s have no mesh or is entierly occluded", name().c_str());
} else {
if (!_loadedFromBin)
newVec = _transformedVerticies[_pickMesh[0]];
else
newVec = gridInverse * _freeMoveZoneVerticies[_pickMesh[0]];
_gridTopLeft.setX(newVec.x());
_gridTopLeft.setY(newVec.z());
_gridBottomRight = _gridTopLeft;
_gridWorldY = newVec.y();
}
for (uint i = 0; i < _pickMesh.size(); i++) {
uint vertNo = _pickMesh[_pickMesh[i]];
if (!_loadedFromBin)
newVec = _transformedVerticies[vertNo];
else
newVec = gridInverse * _freeMoveZoneVerticies[vertNo];
if (_gridTopLeft.getX() > newVec.x()) {
_gridTopLeft.setX(newVec.x());
} else if (_gridBottomRight.getX() < newVec.x()) {
_gridBottomRight.setX(newVec.x());
}
if (_gridTopLeft.getY() > newVec.z()) {
_gridTopLeft.setY(newVec.z());
} else if (_gridBottomRight.getY() < newVec.z()) {
_gridBottomRight.setY(newVec.z());
}
if (newVec.y() < _gridWorldY)
_gridWorldY = newVec.y();
}
if (!_loadedFromBin) {
if (!name().contains("19000"))
_gridSquareSize = TeVector2f32(5.0f, 5.0f);
else
_gridSquareSize = TeVector2f32(2.0f, 2.0f);
} else {
/* Syberia 1 code, never actually used..
const TeVector2f32 gridVecDiff = _gridBottomRight - _gridTopLeft;
float minSide = MIN(gridVecDiff.getX(), gridVecDiff.getY()) / 20.0f;
_gridSquareSize.setX(minSide);
_gridSquareSize.setY(minSide);
if (_loadGridSize.getX())
_gridSquareSize = _loadGridSize;
*/
_gridSquareSize = TeVector2f32(20.0f, 20.0f);
}
TeMatrix4x4 worldTrans = worldTransformationMatrix();
worldTrans.inverse();
_inverseWorldTransform = worldTrans;
}
TeVector2s32 TeFreeMoveZone::projectOnAStarGrid(const TeVector3f32 &pt) {
TeVector2f32 offsetpt;
if (!_loadedFromBin) {
offsetpt = TeVector2f32(pt.x() - _gridTopLeft.getX(), pt.z() - _gridTopLeft.getY());
} else {
TeMatrix4x4 invGrid = _gridMatrix;
invGrid.inverse();
TeVector3f32 transPt = invGrid * (_inverseWorldTransform * pt);
offsetpt.setX(transPt.x() - _gridTopLeft.getX());
offsetpt.setY(transPt.z() - _gridTopLeft.getY());
}
const TeVector2f32 projected = offsetpt / _gridSquareSize;
return TeVector2s32((int)projected.getX(), (int)projected.getY());
}
Common::Array TeFreeMoveZone::removeInsignificantPoints(const Common::Array &points) {
if (points.size() < 2)
return points;
Common::Array result;
result.push_back(points[0]);
if (points.size() > 2) {
int point1 = 0;
int point2 = 2;
do {
const TeVector2f32 pt1(points[point1].x(), points[point1].z());
const TeVector2f32 pt2(points[point2].x(), points[point2].z());
for (uint i = 0; i * 2 < _borders.size() / 2; i++) {
const TeVector3f32 transpt3d1 = worldTransformationMatrix() * verticies()[_borders[i * 2]];
const TeVector2f32 transpt1(transpt3d1.x(), transpt3d1.z());
const TeVector3f32 transpt3d2 = worldTransformationMatrix() * verticies()[_borders[i * 2 + 1]];
const TeVector2f32 transpt2(transpt3d2.x(), transpt3d2.z());
if (segmentIntersection(pt1, pt2, transpt1, transpt2, nullptr, nullptr, nullptr) == 2)
break;
}
point1 = point2 - 1;
result.push_back(points[point1]);
point2++;
} while (point2 < (int)points.size());
}
if (result.back() != points[points.size() - 2]) {
result.push_back(points[points.size() - 1]);
} else {
result.back() = points[points.size() - 1];
}
return result;
}
void TeFreeMoveZone::setBordersDistance(float dist) {
_graph->_bordersDistance = dist;
}
void TeFreeMoveZone::setCamera(TeIntrusivePtr &cam, bool noRecalcProjPoints) {
if (_camera) {
_camera->onViewportChangedSignal().remove(this, &TeFreeMoveZone::onViewportChanged);
}
//_globalCamera = camera; // Seems like this is never used?
_camera = cam;
cam->onViewportChangedSignal().add(this, &TeFreeMoveZone::onViewportChanged);
if (!noRecalcProjPoints)
_projectedPointsDirty = true;
}
void TeFreeMoveZone::setNbTriangles(uint len) {
_freeMoveZoneVerticies.resize(len * 3);
_gridDirty = true;
_transformedVerticiesDirty = true;
_bordersDirty = true;
_pickMeshDirty = true;
_projectedPointsDirty = true;
}
void TeFreeMoveZone::setPathFindingOccluder(const TeOBP &occluder) {
_obp = occluder;
_projectedPointsDirty = true;
_bordersDirty = true;
_gridDirty = true;
}
void TeFreeMoveZone::setVertex(uint offset, const TeVector3f32 &vertex) {
_freeMoveZoneVerticies[offset] = vertex;
_gridDirty = true;
_transformedVerticiesDirty = true;
_bordersDirty = true;
_pickMeshDirty = true;
_projectedPointsDirty = true;
}
TeVector3f32 TeFreeMoveZone::transformAStarGridInWorldSpace(const TeVector2s32 &gridpt) {
float offsetx = (float)gridpt._x * _gridSquareSize.getX() + _gridTopLeft.getX() +
_gridSquareSize.getX() / 2.0f;
float offsety = (float)gridpt._y * _gridSquareSize.getY() + _gridTopLeft.getY() +
_gridSquareSize.getY() / 2.0f;
if (!_loadedFromBin) {
return TeVector3f32(offsetx, _gridWorldY, offsety);
} else {
TeVector3f32 result = _gridMatrix * TeVector3f32(offsetx, _gridWorldY, offsety);
return worldTransformationMatrix() * result;
}
}
float TeFreeMoveZone::transformHeightMin(float minval) {
TeVector3f32 vec = worldTransformationMatrix() * TeVector3f32(_gridTopLeft.getX(), minval, _gridTopLeft.getY());
return vec.y();
}
TeVector3f32 TeFreeMoveZone::transformVectorInWorldSpace(float x, float y) {
TeVector3f32 vec = _gridMatrix * TeVector3f32(x, _gridWorldY, y);
return worldTransformationMatrix() * vec;
}
void TeFreeMoveZone::updateBorders() {
if (!_bordersDirty)
return;
updatePickMesh();
for (uint triNo1 = 0; triNo1 < _verticies.size() / 3; triNo1++) {
for (uint vecNo1 = 0; vecNo1 < 3; vecNo1++) {
uint left1 = triNo1 * 3 + vecNo1;
uint left2 = triNo1 * 3 + (vecNo1 == 2 ? 0 : vecNo1 + 1);
const TeVector3f32 vleft1 = _verticies[left1];
const TeVector3f32 vleft2 = _verticies[left2];
bool skip = false;
for (uint triNo2 = 0; triNo2 < _verticies.size() / 3; triNo2++) {
if (skip)
break;
if (triNo2 == triNo1)
continue;
for (uint vecNo2 = 0; vecNo2 < 3; vecNo2++) {
uint right1 = triNo2 * 3 + vecNo2;
uint right2 = triNo2 * 3 + (vecNo2 == 2 ? 0 : vecNo2 + 1);
const TeVector3f32 vright1 = _verticies[right1];
const TeVector3f32 vright2 = _verticies[right2];
if ((vright1 == vleft1 && vright2 == vleft2) || (vright1 == vleft2 && vright2 == vleft1)) {
skip = true;
break;
}
}
}
if (!skip) {
_borders.push_back(left1);
_borders.push_back(left2);
}
}
}
_bordersDirty = false;
}
void TeFreeMoveZone::updateGrid(bool force) {
if (!force && !_gridDirty)
return;
_gridDirty = true;
_updateTimer.stop();
_updateTimer.start();
buildAStar();
_micropather->Reset();
// This debug msg copied from the original, but it's
// a bit noisy and not so useful.
// debug("[TeFreeMoveZone::updateGrid()] %s time : %.2f", name().c_str(), _updateTimer.getTimeFromStart() / 1000000.0);
_gridDirty = false;
}
void TeFreeMoveZone::updatePickMesh() {
if (!_pickMeshDirty)
return;
updateTransformedVertices();
_pickMesh.clear();
_pickMesh.reserve(_freeMoveZoneVerticies.size());
int vecNo = 0;
for (uint tri = 0; tri < _freeMoveZoneVerticies.size() / 3; tri++) {
_pickMesh.push_back(vecNo);
_pickMesh.push_back(vecNo + 1);
_pickMesh.push_back(vecNo + 2);
vecNo += 3;
}
debug("[TeFreeMoveZone::updatePickMesh] %s nb triangles reduced from : %d to : %d", name().c_str(),
_freeMoveZoneVerticies.size() / 3, _pickMesh.size() / 3);
TePickMesh2::setNbTriangles(_pickMesh.size() / 3);
for (uint i = 0; i < _pickMesh.size(); i++) {
_verticies[i] = _freeMoveZoneVerticies[_pickMesh[i]];
}
_bordersDirty = true;
_pickMeshDirty = false;
_projectedPointsDirty = true;
_gridDirty = true;
}
void TeFreeMoveZone::updateProjectedPoints() {
if (!_projectedPointsDirty)
return;
updateTransformedVertices();
updatePickMesh();
if (!_camera) {
_projectedPoints.clear();
_projectedPointsDirty = false;
return;
}
_projectedPoints.resize(_pickMesh.size());
for (uint i = 0; i < _pickMesh.size(); i++) {
_projectedPoints[i] = _camera->projectPoint(_transformedVerticies[_pickMesh[i]]);
_projectedPoints[i].setY(_camera->getViewportHeight() - _projectedPoints[i].getY());
}
_projectedPointsDirty = false;
}
void TeFreeMoveZone::updateTransformedVertices() {
if (!_transformedVerticiesDirty)
return;
const TeMatrix4x4 worldTransform = worldTransformationMatrix();
_transformedVerticies.resize(_freeMoveZoneVerticies.size());
for (uint i = 0; i < _transformedVerticies.size(); i++) {
_transformedVerticies[i] = worldTransform * _freeMoveZoneVerticies[i];
}
_transformedVerticiesDirty = false;
}
/*========*/
float TeFreeMoveZoneGraph::LeastCostEstimate(void *stateStart, void *stateEnd) {
char *dataStart = _flags.data();
int startInt = (char *)stateStart - dataStart;
int endInt = (char *)stateEnd - dataStart;
int starty = startInt / _size._x;
int endy = endInt / _size._x;
TeVector2s32 start(startInt - starty * _size._x, starty);
TeVector2s32 end(endInt - endy * _size._x, endy);
return (end - start).squaredLength();
}
void TeFreeMoveZoneGraph::AdjacentCost(void *state, Common::Array *adjacent) {
char *flagStart = _flags.data();
int stateInt = (char *)state - flagStart;
int stateY = stateInt / _size._x;
const TeVector2s32 statept(stateInt - stateY * _size._x, stateY);
micropather::StateCost cost;
TeVector2s32 pt;
pt = TeVector2s32(statept._x - 1, statept._y);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
pt = TeVector2s32(statept._x - 1, statept._y + 1);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
pt = TeVector2s32(statept._x, statept._y + 1);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
pt = TeVector2s32(statept._x + 1, statept._y + 1);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
pt = TeVector2s32(statept._x + 1, statept._y);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
pt = TeVector2s32(statept._x + 1, statept._y - 1);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
pt = TeVector2s32(statept._x, statept._y - 1);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
pt = TeVector2s32(statept._x - 1, statept._y - 1);
cost.state = flagStart + _size._x * pt._y + pt._x;
cost.cost = costForPoint(pt);
adjacent->push_back(cost);
}
void TeFreeMoveZoneGraph::PrintStateInfo(void *state) {
error("TODO: Implement TeFreeMoveZone::TeFreeMoveZoneGraph::PrintStateInfo");
}
int TeFreeMoveZoneGraph::flag(const TeVector2s32 &loc) {
if (loc._x < 0 || loc._x >= _size._x || loc._y < 0 || loc._y >= _size._y)
return 1;
return _flags[loc._y * _size._x + loc._x];
}
void TeFreeMoveZoneGraph::setSize(const TeVector2s32 &size) {
_flags.clear();
_size = size;
_flags.resize(size._x * _size._y);
}
void TeFreeMoveZoneGraph::deserialize(Common::ReadStream &stream) {
TeVector2s32::deserialize(stream, _size);
uint32 flaglen = stream.readUint32LE();
if (flaglen > 1000000 || (int)flaglen != _size._x * _size._y)
error("TeFreeMoveZoneGraph: Flags unexpected size, expect %d got %d", _size._x * _size._y, flaglen);
_flags.resize(flaglen);
for (uint i = 0; i < flaglen; i++) {
_flags[i] = stream.readByte();
}
_bordersDistance = stream.readFloatLE();
}
void TeFreeMoveZoneGraph::serialize(Common::WriteStream &stream) const {
error("TODO: Implement TeFreeMoveZoneGraph::serialize");
}
/*static*/
TePickMesh2 *TeFreeMoveZone::findNearestMesh(TeIntrusivePtr &camera, const TeVector2s32 &fromPt,
Common::Array &pickMeshes, TeVector3f32 *outloc, bool lastHitFirst) {
TeVector3f32 closestLoc;
TePickMesh2 *nearestMesh = nullptr;
if (!camera)
return nullptr;
float closestDist = camera->orthoFarPlane();
Math::Ray camRay;
for (uint i = 0; i < pickMeshes.size(); i++) {
TePickMesh2 *mesh = pickMeshes[i];
const TeMatrix4x4 meshWorldTransform = mesh->worldTransformationMatrix();
if (lastHitFirst) {
// Note: it seems like a bug in the original.. this never sets
// the ray parameters?? It should still find the right triangle below.
uint tricount = mesh->verticies().size() / 3;
uint vert = mesh->lastTriangleHit() * 3;
if (mesh->lastTriangleHit() >= tricount)
vert = 0;
const TeVector3f32 v1 = meshWorldTransform * mesh->verticies()[vert + 0];
const TeVector3f32 v2 = meshWorldTransform * mesh->verticies()[vert + 1];
const TeVector3f32 v3 = meshWorldTransform * mesh->verticies()[vert + 2];
TeVector3f32 intersectLoc;
float intersectDist;
bool intResult = camRay.intersectTriangle(v1, v2, v3, intersectLoc, intersectDist);
if (intResult && intersectDist < closestDist && intersectDist >= camera->orthoNearPlane())
return mesh;
}
for (uint tri = 0; tri < mesh->verticies().size() / 3; tri++) {
const TeVector3f32 v1 = meshWorldTransform * mesh->verticies()[tri * 3 + 0];
const TeVector3f32 v2 = meshWorldTransform * mesh->verticies()[tri * 3 + 1];
const TeVector3f32 v3 = meshWorldTransform * mesh->verticies()[tri * 3 + 2];
camRay = camera->getRay(fromPt);
TeVector3f32 intersectLoc;
float intersectDist;
bool intResult = camRay.intersectTriangle(v1, v2, v3, intersectLoc, intersectDist);
/*debug("PickMesh2 %s intersect Ray(%s, %s) Triangle(%s, %s, %s) -> %s", mesh->name().c_str(),
TeVector3f32(camRay.getOrigin()).dump().c_str(),
TeVector3f32(camRay.getDirection()).dump().c_str(),
v1.dump().c_str(), v2.dump().c_str(), v3.dump().c_str(),
intResult ? "hit!" : "no hit");*/
if (intResult && intersectDist < closestDist && intersectDist >= camera->orthoNearPlane()) {
mesh->setLastTriangleHit(tri);
closestLoc = intersectLoc;
closestDist = intersectDist;
nearestMesh = mesh;
if (lastHitFirst)
break;
}
}
}
if (outloc) {
*outloc = closestLoc;
}
return nearestMesh;
}
} // end namespace Tetraedge