mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-10 11:51:52 +00:00
561 lines
17 KiB
C++
561 lines
17 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 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/util.h"
|
|
|
|
#include "engines/grim/debug.h"
|
|
#include "engines/grim/grim.h"
|
|
#include "engines/grim/sector.h"
|
|
#include "engines/grim/textsplit.h"
|
|
#include "engines/grim/savegame.h"
|
|
#include "engines/grim/set.h"
|
|
|
|
namespace Grim {
|
|
|
|
Sector::Sector() :
|
|
_vertices(nullptr), _origVertices(nullptr), _sortplanes(nullptr),_invalid(false),
|
|
_shrinkRadius(0.f), _numVertices(0), _id(0), _numSortplanes(0),
|
|
_type(NoneType), _visible(false), _height(0.f) {
|
|
}
|
|
|
|
Sector::Sector(const Sector &other) :
|
|
_vertices(nullptr), _origVertices(nullptr), _sortplanes(nullptr),
|
|
_numSortplanes(0) {
|
|
*this = other;
|
|
}
|
|
|
|
Sector::~Sector() {
|
|
delete[] _vertices;
|
|
delete[] _origVertices;
|
|
delete[] _sortplanes;
|
|
}
|
|
|
|
void Sector::saveState(SaveGame *savedState) const {
|
|
savedState->writeLESint32(_numVertices);
|
|
savedState->writeLESint32(_id);
|
|
savedState->writeLESint32(_type);
|
|
savedState->writeBool(_visible);
|
|
savedState->writeFloat(_height);
|
|
|
|
savedState->writeString(_name);
|
|
|
|
for (int i = 0; i < _numVertices + 1; ++i) {
|
|
savedState->writeVector3d(_vertices[i]);
|
|
}
|
|
|
|
savedState->writeVector3d(_normal);
|
|
|
|
savedState->writeFloat(_shrinkRadius);
|
|
savedState->writeBool(_invalid);
|
|
if (_shrinkRadius != 0.f && !_invalid) {
|
|
for (int i = 0; i < _numVertices + 1; ++i) {
|
|
savedState->writeVector3d(_origVertices[i]);
|
|
}
|
|
}
|
|
|
|
if (savedState->saveMinorVersion() > 8 && g_grim->getGameType() == GType_MONKEY4) {
|
|
savedState->writeLEUint32(_numSortplanes);
|
|
for (int i = 0; i < _numSortplanes; ++i) {
|
|
savedState->writeLEUint32(_sortplanes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Sector::restoreState(SaveGame *savedState) {
|
|
_numVertices = savedState->readLESint32();
|
|
_id = savedState->readLESint32();
|
|
_type = (SectorType)savedState->readLESint32();
|
|
_visible = savedState->readBool();
|
|
_height = savedState->readFloat();
|
|
|
|
_name = savedState->readString();
|
|
|
|
_vertices = new Math::Vector3d[_numVertices + 1];
|
|
for (int i = 0; i < _numVertices + 1; ++i) {
|
|
_vertices[i] = savedState->readVector3d();
|
|
}
|
|
|
|
_normal = savedState->readVector3d();
|
|
|
|
_shrinkRadius = savedState->readFloat();
|
|
_invalid = savedState->readBool();
|
|
if (_shrinkRadius != 0.f && !_invalid) {
|
|
_origVertices = new Math::Vector3d[_numVertices + 1];
|
|
for (int i = 0; i < _numVertices + 1; ++i) {
|
|
_origVertices[i] = savedState->readVector3d();
|
|
}
|
|
} else {
|
|
_origVertices = nullptr;
|
|
}
|
|
if (savedState->saveMinorVersion() > 8 && g_grim->getGameType() == GType_MONKEY4) {
|
|
_numSortplanes = savedState->readLEUint32();
|
|
_sortplanes = new int[_numSortplanes];
|
|
for (int i = 0; i < _numSortplanes; ++i) {
|
|
_sortplanes[i] = savedState->readLEUint32();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Sector::load(TextSplitter &ts) {
|
|
char buf[256];
|
|
int ident = 0, i = 0;
|
|
Math::Vector3d tempVert;
|
|
|
|
// Sector NAMES can be null, but ts isn't flexible enough
|
|
if (strlen(ts.getCurrentLine()) > strlen(" sector"))
|
|
ts.scanString(" sector %256s", 1, buf);
|
|
else {
|
|
ts.nextLine();
|
|
buf[0] = '\0';
|
|
}
|
|
|
|
ts.scanString(" id %d", 1, &ident);
|
|
|
|
_name = buf;
|
|
_id = ident;
|
|
ts.scanString(" type %256s", 1, buf);
|
|
|
|
if (strstr(buf, "walk"))
|
|
_type = WalkType;
|
|
|
|
else if (strstr(buf, "funnel"))
|
|
_type = FunnelType;
|
|
else if (strstr(buf, "camera"))
|
|
_type = CameraType;
|
|
else if (strstr(buf, "special"))
|
|
_type = SpecialType;
|
|
else if (strstr(buf, "chernobyl"))
|
|
_type = HotType;
|
|
else
|
|
Debug::error(Debug::Sets, "Unknown sector type '%s' in room setup", buf);
|
|
|
|
ts.scanString(" default visibility %256s", 1, buf);
|
|
if (strcmp(buf, "visible") == 0)
|
|
_visible = true;
|
|
else if (strcmp(buf, "invisible") == 0)
|
|
_visible = false;
|
|
else
|
|
error("Invalid visibility spec: %s", buf);
|
|
ts.scanString(" height %f", 1, &_height);
|
|
ts.scanString(" numvertices %d", 1, &_numVertices);
|
|
_vertices = new Math::Vector3d[_numVertices + 1];
|
|
|
|
ts.scanString(" vertices: %f %f %f", 3, &_vertices[0].x(), &_vertices[0].y(), &_vertices[0].z());
|
|
for (i = 1; i < _numVertices; i++)
|
|
ts.scanString(" %f %f %f", 3, &_vertices[i].x(), &_vertices[i].y(), &_vertices[i].z());
|
|
|
|
// Repeat the last vertex for convenience
|
|
_vertices[_numVertices] = _vertices[0];
|
|
|
|
_normal = Math::Vector3d::crossProduct(_vertices[1] - _vertices[0], _vertices[_numVertices - 1] - _vertices[0]);
|
|
float length = _normal.getMagnitude();
|
|
if (length > 0)
|
|
_normal /= length;
|
|
|
|
// Remastered
|
|
if (!ts.checkString("numtris")) {
|
|
return;
|
|
}
|
|
int _numTris;
|
|
ts.scanString(" numtris %d", 1, &_numTris);
|
|
//_vertices = new Math::Vector3d[_numVertices + 1];
|
|
int a,b,c;
|
|
if (_numTris > 0) {
|
|
ts.scanString(" triangles: %f %f %f", 3, &a, &b, &c);
|
|
for (i = 1; i < _numTris; i++)
|
|
ts.scanString(" %f %f %f", 3, &a, &b, &c);
|
|
}
|
|
}
|
|
|
|
void Sector::loadBinary(Common::SeekableReadStream *data) {
|
|
_numVertices = data->readUint32LE();
|
|
_vertices = new Math::Vector3d[_numVertices + 1];
|
|
for (int i = 0; i < _numVertices; i++) {
|
|
_vertices[i].readFromStream(data);
|
|
}
|
|
|
|
// Repeat the last vertex for convenience
|
|
_vertices[_numVertices] = _vertices[0];
|
|
|
|
_normal = Math::Vector3d::crossProduct(_vertices[1] - _vertices[0], _vertices[_numVertices - 1] - _vertices[0]);
|
|
float length = _normal.getMagnitude();
|
|
if (length > 0)
|
|
_normal /= length;
|
|
|
|
char name[128];
|
|
int nameLength = data->readUint32LE();
|
|
|
|
data->read(name, nameLength);
|
|
_name = name;
|
|
|
|
_id = data->readUint32LE();
|
|
|
|
_visible = data->readByte();
|
|
|
|
_type = (SectorType)data->readUint32LE();
|
|
|
|
_numSortplanes = data->readUint32LE();
|
|
_sortplanes = new int[_numSortplanes];
|
|
for (int i = 0; i < _numSortplanes; ++i) {
|
|
_sortplanes[i] = data->readUint32LE();
|
|
}
|
|
|
|
_height = data->readFloatLE();
|
|
}
|
|
|
|
void Sector::setVisible(bool vis) {
|
|
_visible = vis;
|
|
}
|
|
|
|
void Sector::shrink(float radius) {
|
|
if ((getType() & WalkType) == 0 || _shrinkRadius == radius)
|
|
return;
|
|
|
|
_shrinkRadius = radius;
|
|
if (!_origVertices) {
|
|
_origVertices = _vertices;
|
|
_vertices = new Math::Vector3d[_numVertices + 1];
|
|
}
|
|
|
|
// Move each vertex inwards by the given amount.
|
|
for (int j = 0; j < _numVertices; j++) {
|
|
Math::Vector3d shrinkDir;
|
|
|
|
for (int k = 0; k < g_grim->getCurrSet()->getSectorCount(); k++) {
|
|
Sector *other = g_grim->getCurrSet()->getSectorBase(k);
|
|
if ((other->getType() & WalkType) == 0)
|
|
continue;
|
|
|
|
for (int l = 0; l < other->_numVertices; l++) {
|
|
Math::Vector3d *otherVerts = other->_vertices;
|
|
if (other->_origVertices)
|
|
otherVerts = other->_origVertices;
|
|
if ((otherVerts[l] - _origVertices[j]).getMagnitude() < 0.01f) {
|
|
Math::Vector3d e1 = otherVerts[l + 1] - otherVerts[l];
|
|
Math::Vector3d e2;
|
|
if (l - 1 >= 0)
|
|
e2 = otherVerts[l] - otherVerts[l - 1];
|
|
else
|
|
e2 = otherVerts[l] - otherVerts[other->_numVertices - 1];
|
|
e1.normalize();
|
|
e2.normalize();
|
|
Math::Vector3d bisector = (e1 - e2);
|
|
bisector.normalize();
|
|
shrinkDir += bisector;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shrinkDir.getMagnitude() > 0.1f) {
|
|
shrinkDir.normalize();
|
|
_vertices[j] = _origVertices[j] + shrinkDir * radius;
|
|
} else {
|
|
_vertices[j] = _origVertices[j];
|
|
}
|
|
}
|
|
|
|
_vertices[_numVertices] = _vertices[0];
|
|
|
|
// Make sure the sector is still convex.
|
|
for (int j = 0; j < _numVertices; j++) {
|
|
Math::Vector3d e1 = _vertices[j + 1] - _vertices[j];
|
|
Math::Vector3d e2;
|
|
if (j - 1 >= 0)
|
|
e2 = _vertices[j] - _vertices[j - 1];
|
|
else
|
|
e2 = _vertices[j] - _vertices[_numVertices - 1];
|
|
|
|
if (e1.x() * e2.y() > e1.y() * e2.x()) {
|
|
// Not convex, so mark the sector invalid.
|
|
_invalid = true;
|
|
delete[] _vertices;
|
|
_vertices = _origVertices;
|
|
_origVertices = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sector::unshrink() {
|
|
if (_shrinkRadius != 0.f) {
|
|
_shrinkRadius = 0.f;
|
|
_invalid = false;
|
|
if (_origVertices) {
|
|
delete[] _vertices;
|
|
_vertices = _origVertices;
|
|
_origVertices = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
float Sector::distanceToPoint(const Math::Vector3d &point) const {
|
|
// The plane has equation ax + by + cz + d = 0
|
|
float a = _normal.x();
|
|
float b = _normal.y();
|
|
float c = _normal.z();
|
|
float d = -_vertices[0].x() * a - _vertices[0].y() * b - _vertices[0].z() * c;
|
|
|
|
// dist is positive if it is above the plain, negative if it is
|
|
// below and 0 if it is on the plane.
|
|
float dist = (a * point.x() + b * point.y() + c * point.z() + d);
|
|
dist /= sqrt(a * a + b * b + c * c);
|
|
return dist;
|
|
}
|
|
|
|
bool Sector::isPointInSector(const Math::Vector3d &point) const {
|
|
// Calculate the distance of the point from the plane of the sector.
|
|
// Return false if it isn't within a margin.
|
|
if (_height < 9000.f) { // No need to check when height is 9999.
|
|
float dist = distanceToPoint(point);
|
|
|
|
if (fabsf(dist) > _height + 0.01) // Add an error margin
|
|
return false;
|
|
}
|
|
|
|
// On the plane, so check if it is inside the polygon.
|
|
for (int i = 0; i < _numVertices; i++) {
|
|
Math::Vector3d edge = _vertices[i + 1] - _vertices[i];
|
|
Math::Vector3d delta = point - _vertices[i];
|
|
Math::Vector3d cross = Math::Vector3d::crossProduct(edge, delta);
|
|
if (cross.dotProduct(_normal) < -0.000001f) // not "< 0.f" here, since the value could be something like -7.45058e-09 and it
|
|
return false; // shuoldn't return. that was causing issue #610 (infinite loop in de.forklift_actor.dismount)
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Common::List<Math::Line3d> Sector::getBridgesTo(Sector *sector) const {
|
|
// This returns a list of "bridges", which are edges that can be travelled
|
|
// through to get to another sector. 0 bridges mean the sectors aren't
|
|
// connected.
|
|
|
|
// The algorithm starts by considering all the edges of sector A
|
|
// bridges. It then narrows them down by cutting the bridges against
|
|
// sector B, so we end up with a list of lines which are at the border
|
|
// of sector A and inside sector B.
|
|
|
|
Common::List<Math::Line3d> bridges;
|
|
Common::List<Math::Line3d>::iterator it;
|
|
|
|
for (int i = 0; i < _numVertices; i++) {
|
|
bridges.push_back(Math::Line3d(_vertices[i], _vertices[i + 1]));
|
|
}
|
|
|
|
Math::Vector3d *sectorVertices = sector->getVertices();
|
|
for (int i = 0; i < sector->getNumVertices(); i++) {
|
|
Math::Vector3d pos, edge, delta_b1, delta_b2;
|
|
Math::Line3d line(sectorVertices[i], sectorVertices[i + 1]);
|
|
it = bridges.begin();
|
|
while (it != bridges.end()) {
|
|
Math::Line3d &bridge = (*it);
|
|
edge = line.end() - line.begin();
|
|
delta_b1 = bridge.begin() - line.begin();
|
|
delta_b2 = bridge.end() - line.begin();
|
|
Math::Vector3d cross_b1 = Math::Vector3d::crossProduct(edge, delta_b1);
|
|
Math::Vector3d cross_b2 = Math::Vector3d::crossProduct(edge, delta_b2);
|
|
bool b1_out = cross_b1.dotProduct(_normal) < -1e-7;
|
|
bool b2_out = cross_b2.dotProduct(_normal) < -1e-7;
|
|
|
|
bool useXZ = (g_grim->getGameType() == GType_MONKEY4);
|
|
|
|
if (b1_out && b2_out) {
|
|
// Both points are outside.
|
|
it = bridges.erase(it);
|
|
continue;
|
|
} else if (b1_out) {
|
|
if (bridge.intersectLine2d(line, &pos, useXZ)) {
|
|
bridge = Math::Line3d(pos, bridge.end());
|
|
}
|
|
} else if (b2_out) {
|
|
if (bridge.intersectLine2d(line, &pos, useXZ)) {
|
|
bridge = Math::Line3d(bridge.begin(), pos);
|
|
}
|
|
}
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
// All the bridges should be at the same height on both sectors.
|
|
it = bridges.begin();
|
|
while (it != bridges.end()) {
|
|
if (g_grim->getGameType() == GType_MONKEY4) {
|
|
// Set pac contains sectors which are not parallel to any
|
|
// other sector or share any edge. Since one sector isn't
|
|
// a plane, finding the intersections in 3D would be complicated.
|
|
//
|
|
// Checking for bridges using a projection in 2D and having a height
|
|
// threshold to avoid that characters jump from lower to higher floors
|
|
// seems to be a good compromise.
|
|
//
|
|
// The value of at least 0.1 was chosen to fix a path finding issue
|
|
// in set pac when guybrush tried to reach the pile of rocks.
|
|
if (fabs(getProjectionToPlane((*it).begin()).y() - sector->getProjectionToPlane((*it).begin()).y()) > 0.1f ||
|
|
fabs(getProjectionToPlane((*it).end()).y() - sector->getProjectionToPlane((*it).end()).y()) > 0.1f) {
|
|
it = bridges.erase(it);
|
|
continue;
|
|
}
|
|
} else {
|
|
if (fabs(getProjectionToPlane((*it).begin()).z() - sector->getProjectionToPlane((*it).begin()).z()) > 0.01f ||
|
|
fabs(getProjectionToPlane((*it).end()).z() - sector->getProjectionToPlane((*it).end()).z()) > 0.01f) {
|
|
it = bridges.erase(it);
|
|
continue;
|
|
}
|
|
}
|
|
++it;
|
|
}
|
|
return bridges;
|
|
}
|
|
|
|
Math::Vector3d Sector::getProjectionToPlane(const Math::Vector3d &point) const {
|
|
if (_normal.getMagnitude() == 0)
|
|
error("Sector normal is (0,0,0)");
|
|
|
|
// Formula: return p - n * (n . (p - v_0))
|
|
Math::Vector3d result = point;
|
|
result -= _normal * _normal.dotProduct(point - _vertices[0]);
|
|
return result;
|
|
}
|
|
|
|
Math::Vector3d Sector::getProjectionToPuckVector(const Math::Vector3d &v) const {
|
|
if (_normal.getMagnitude() == 0)
|
|
error("Sector normal is (0,0,0)");
|
|
|
|
Math::Vector3d result = v;
|
|
result -= _normal * _normal.dotProduct(v);
|
|
return result;
|
|
}
|
|
|
|
// Find the closest point on the walkplane to the given point
|
|
Math::Vector3d Sector::getClosestPoint(const Math::Vector3d &point) const {
|
|
// First try to project to the plane
|
|
Math::Vector3d p2 = getProjectionToPlane(point);
|
|
if (isPointInSector(p2))
|
|
return p2;
|
|
|
|
// Now try to project to some edge
|
|
for (int i = 0; i < _numVertices; i++) {
|
|
Math::Vector3d edge = _vertices[i + 1] - _vertices[i];
|
|
Math::Vector3d delta = point - _vertices[i];
|
|
float scalar = Math::Vector3d::dotProduct(delta, edge) / Math::Vector3d::dotProduct(edge, edge);
|
|
Math::Vector3d cross = Math::Vector3d::crossProduct(delta, edge);
|
|
if (scalar >= 0 && scalar <= 1 && cross.dotProduct(_normal) > 0)
|
|
// That last test is just whether the z-component
|
|
// of delta cross edge is positive; we don't
|
|
// want to return opposite edges.
|
|
return _vertices[i] + scalar * edge;
|
|
}
|
|
|
|
// Otherwise, just find the closest vertex
|
|
float minDist = (point - _vertices[0]).getMagnitude();
|
|
int index = 0;
|
|
for (int i = 1; i < _numVertices; i++) {
|
|
float currDist = (point - _vertices[i]).getMagnitude();
|
|
if (currDist < minDist) {
|
|
minDist = currDist;
|
|
index = i;
|
|
}
|
|
}
|
|
return _vertices[index];
|
|
}
|
|
|
|
void Sector::getExitInfo(const Math::Vector3d &s, const Math::Vector3d &dirVec, struct ExitInfo *result) const {
|
|
Math::Vector3d start = getProjectionToPlane(s);
|
|
Math::Vector3d dir = getProjectionToPuckVector(dirVec);
|
|
|
|
// First find the edge the ray exits through: this is where
|
|
// the z-component of (v_i - start) x dir changes sign from
|
|
// positive to negative.
|
|
|
|
// First find a vertex such that the cross product has
|
|
// positive z-component.
|
|
int i;
|
|
for (i = 0; i < _numVertices; i++) {
|
|
Math::Vector3d delta = _vertices[i] - start;
|
|
Math::Vector3d cross = Math::Vector3d::crossProduct(delta, dir);
|
|
if (cross.dotProduct(_normal) > 0)
|
|
break;
|
|
}
|
|
|
|
// Now continue until the cross product has negative
|
|
// z-component.
|
|
while (i < _numVertices) {
|
|
i++;
|
|
Math::Vector3d delta = _vertices[i] - start;
|
|
Math::Vector3d cross = Math::Vector3d::crossProduct(delta, dir);
|
|
if (cross.dotProduct(_normal) <= 0)
|
|
break;
|
|
}
|
|
|
|
result->edgeDir = _vertices[i] - _vertices[i - 1];
|
|
result->angleWithEdge = Math::Vector3d::angle(dir, result->edgeDir);
|
|
result->edgeVertex = i - 1;
|
|
|
|
Math::Vector3d edgeNormal = Math::Vector3d::crossProduct(result->edgeDir, _normal);
|
|
float d = Math::Vector3d::dotProduct(dir, edgeNormal);
|
|
// This is 0 for the albinizod monster in the at set
|
|
if (!d)
|
|
d = 1.f;
|
|
result->exitPoint = start + (Math::Vector3d::dotProduct(_vertices[i] - start, edgeNormal) / d) * dir;
|
|
}
|
|
|
|
Sector &Sector::operator=(const Sector &other) {
|
|
_numVertices = other._numVertices;
|
|
_id = other._id;
|
|
_name = other._name;
|
|
_type = other._type;
|
|
_visible = other._visible;
|
|
_vertices = new Math::Vector3d[_numVertices + 1];
|
|
for (int i = 0; i < _numVertices + 1; ++i) {
|
|
_vertices[i] = other._vertices[i];
|
|
}
|
|
if (other._origVertices) {
|
|
_origVertices = new Math::Vector3d[_numVertices + 1];
|
|
for (int i = 0; i < _numVertices + 1; ++i) {
|
|
_origVertices[i] = other._origVertices[i];
|
|
}
|
|
} else {
|
|
_origVertices = nullptr;
|
|
}
|
|
_height = other._height;
|
|
_normal = other._normal;
|
|
_shrinkRadius = other._shrinkRadius;
|
|
_invalid = other._invalid;
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool Sector::operator==(const Sector &other) const {
|
|
bool ok = _numVertices == other._numVertices &&
|
|
_id == other._id &&
|
|
_name == other._name &&
|
|
_type == other._type &&
|
|
_visible == other._visible;
|
|
for (int i = 0; i < _numVertices + 1; ++i) {
|
|
ok = ok && _vertices[i] == other._vertices[i];
|
|
}
|
|
ok = ok && _height == other._height && _normal == other._normal;
|
|
|
|
return ok;
|
|
}
|
|
|
|
} // end of namespace Grim
|