mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-12 14:09:28 +00:00
![Thierry Crozat](/assets/img/avatar_default.png)
The regression was introduced by commit e6ba26ff0d which wrote coordinates of a rect as unsigned int when they were before written as signed int. Since the load code was not modified it still expected signed int. They are now again written as signed int. Any gamed saved between commit e6ba26ff0d and this commit will therefore be corrupted.
350 lines
10 KiB
C++
350 lines
10 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.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This code is based on Broken Sword 2.5 engine
|
|
*
|
|
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
|
*
|
|
* Licensed under GNU GPL v2
|
|
*
|
|
*/
|
|
|
|
#include "sword25/kernel/inputpersistenceblock.h"
|
|
#include "sword25/kernel/outputpersistenceblock.h"
|
|
|
|
#include "sword25/math/region.h"
|
|
#include "sword25/math/walkregion.h"
|
|
#include "sword25/math/regionregistry.h"
|
|
|
|
namespace Sword25 {
|
|
|
|
Region::Region() : _valid(false), _type(RT_REGION) {
|
|
RegionRegistry::instance().registerObject(this);
|
|
}
|
|
|
|
Region::Region(InputPersistenceBlock &reader, uint handle) : _valid(false), _type(RT_REGION) {
|
|
RegionRegistry::instance().registerObject(this, handle);
|
|
unpersist(reader);
|
|
}
|
|
|
|
uint Region::create(REGION_TYPE type) {
|
|
Region *regionPtr = NULL;
|
|
switch (type) {
|
|
case RT_REGION:
|
|
regionPtr = new Region();
|
|
break;
|
|
|
|
case RT_WALKREGION:
|
|
regionPtr = new WalkRegion();
|
|
break;
|
|
|
|
default:
|
|
assert(true);
|
|
}
|
|
|
|
return RegionRegistry::instance().resolvePtr(regionPtr);
|
|
}
|
|
|
|
uint Region::create(InputPersistenceBlock &reader, uint handle) {
|
|
// Read type
|
|
uint32 type;
|
|
reader.read(type);
|
|
|
|
// Depending on the type, create a new BS_Region or BS_WalkRegion object
|
|
Region *regionPtr = NULL;
|
|
if (type == RT_REGION) {
|
|
regionPtr = new Region(reader, handle);
|
|
} else if (type == RT_WALKREGION) {
|
|
regionPtr = new WalkRegion(reader, handle);
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
return RegionRegistry::instance().resolvePtr(regionPtr);
|
|
}
|
|
|
|
Region::~Region() {
|
|
RegionRegistry::instance().deregisterObject(this);
|
|
}
|
|
|
|
bool Region::init(const Polygon &contour, const Common::Array<Polygon> *pHoles) {
|
|
// Reset object state
|
|
_valid = false;
|
|
_position = Vertex(0, 0);
|
|
_polygons.clear();
|
|
|
|
// Reserve sufficient space for countour and holes in the polygon list
|
|
if (pHoles)
|
|
_polygons.reserve(1 + pHoles->size());
|
|
else
|
|
_polygons.reserve(1);
|
|
|
|
// The first polygon will be the contour
|
|
_polygons.push_back(Polygon());
|
|
_polygons[0].init(contour.vertexCount, contour.vertices);
|
|
// Make sure that the Vertecies in the Contour are arranged in a clockwise direction
|
|
_polygons[0].ensureCWOrder();
|
|
|
|
// Place the hole polygons in the following positions
|
|
if (pHoles) {
|
|
for (uint i = 0; i < pHoles->size(); ++i) {
|
|
_polygons.push_back(Polygon());
|
|
_polygons[i + 1].init((*pHoles)[i].vertexCount, (*pHoles)[i].vertices);
|
|
_polygons[i + 1].ensureCWOrder();
|
|
}
|
|
}
|
|
|
|
|
|
// Initialize bounding box
|
|
updateBoundingBox();
|
|
|
|
_valid = true;
|
|
return true;
|
|
}
|
|
|
|
void Region::updateBoundingBox() {
|
|
if (_polygons[0].vertexCount) {
|
|
int minX = _polygons[0].vertices[0].x;
|
|
int maxX = _polygons[0].vertices[0].x;
|
|
int minY = _polygons[0].vertices[0].y;
|
|
int maxY = _polygons[0].vertices[0].y;
|
|
|
|
for (int i = 1; i < _polygons[0].vertexCount; i++) {
|
|
if (_polygons[0].vertices[i].x < minX) minX = _polygons[0].vertices[i].x;
|
|
else if (_polygons[0].vertices[i].x > maxX) maxX = _polygons[0].vertices[i].x;
|
|
if (_polygons[0].vertices[i].y < minY) minY = _polygons[0].vertices[i].y;
|
|
else if (_polygons[0].vertices[i].y > maxY) maxY = _polygons[0].vertices[i].y;
|
|
}
|
|
|
|
_boundingBox = Common::Rect(minX, minY, maxX + 1, maxY + 1);
|
|
}
|
|
}
|
|
|
|
// Position Changes
|
|
void Region::setPos(int x, int y) {
|
|
// Calculate the difference between the old and new position
|
|
Vertex delta(x - _position.x, y - _position.y);
|
|
|
|
// Save the new position
|
|
_position = Vertex(x, y);
|
|
|
|
// Move all the vertecies
|
|
for (uint i = 0; i < _polygons.size(); ++i) {
|
|
_polygons[i] += delta;
|
|
}
|
|
|
|
// Update the bounding box
|
|
updateBoundingBox();
|
|
}
|
|
|
|
void Region::setPosX(int x) {
|
|
setPos(x, _position.y);
|
|
}
|
|
|
|
void Region::setPosY(int y) {
|
|
setPos(_position.x, y);
|
|
}
|
|
|
|
// Point-Region Tests
|
|
bool Region::isPointInRegion(int x, int y) const {
|
|
// Test whether the point is in the bounding box
|
|
if (_boundingBox.contains(x, y)) {
|
|
// Test whether the point is in the contour
|
|
if (_polygons[0].isPointInPolygon(x, y, true)) {
|
|
// Test whether the point is in a hole
|
|
for (uint i = 1; i < _polygons.size(); i++) {
|
|
if (_polygons[i].isPointInPolygon(x, y, false))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Region::isPointInRegion(const Vertex &vertex) const {
|
|
return isPointInRegion(vertex.x, vertex.y);
|
|
}
|
|
|
|
Vertex Region::findClosestRegionPoint(const Vertex &point) const {
|
|
// Determine whether the point is inside a hole. If that is the case, the closest
|
|
// point on the edge of the hole is determined
|
|
int polygonIdx = 0;
|
|
{
|
|
for (uint i = 1; i < _polygons.size(); ++i) {
|
|
if (_polygons[i].isPointInPolygon(point)) {
|
|
polygonIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const Polygon &polygon = _polygons[polygonIdx];
|
|
|
|
assert(polygon.vertexCount > 1);
|
|
|
|
// For each line of the polygon, calculate the point that is cloest to the given point
|
|
// The point of this set with the smallest distance to the given point is the result.
|
|
Vertex closestVertex = findClosestPointOnLine(polygon.vertices[0], polygon.vertices[1], point);
|
|
int closestVertexDistance2 = closestVertex.distance(point);
|
|
for (int i = 1; i < polygon.vertexCount; ++i) {
|
|
int j = (i + 1) % polygon.vertexCount;
|
|
|
|
Vertex curVertex = findClosestPointOnLine(polygon.vertices[i], polygon.vertices[j], point);
|
|
if (curVertex.distance(point) < closestVertexDistance2) {
|
|
closestVertex = curVertex;
|
|
closestVertexDistance2 = curVertex.distance(point);
|
|
}
|
|
}
|
|
|
|
// Determine whether the point is really within the region. This must not be so, as a result of rounding
|
|
// errors can occur at the edge of polygons
|
|
if (isPointInRegion(closestVertex))
|
|
return closestVertex;
|
|
else {
|
|
// Try to construct a point within the region - 8 points are tested in the immediate vacinity
|
|
// of the point
|
|
if (isPointInRegion(closestVertex + Vertex(-2, -2)))
|
|
return closestVertex + Vertex(-2, -2);
|
|
else if (isPointInRegion(closestVertex + Vertex(0, -2)))
|
|
return closestVertex + Vertex(0, -2);
|
|
else if (isPointInRegion(closestVertex + Vertex(2, -2)))
|
|
return closestVertex + Vertex(2, -2);
|
|
else if (isPointInRegion(closestVertex + Vertex(-2, 0)))
|
|
return closestVertex + Vertex(-2, 0);
|
|
else if (isPointInRegion(closestVertex + Vertex(0, 2)))
|
|
return closestVertex + Vertex(0, 2);
|
|
else if (isPointInRegion(closestVertex + Vertex(-2, 2)))
|
|
return closestVertex + Vertex(-2, 2);
|
|
else if (isPointInRegion(closestVertex + Vertex(-2, 0)))
|
|
return closestVertex + Vertex(2, 2);
|
|
else if (isPointInRegion(closestVertex + Vertex(2, 2)))
|
|
return closestVertex + Vertex(2, 2);
|
|
|
|
// If no point could be found that way that lies within the region, find the next point
|
|
closestVertex = polygon.vertices[0];
|
|
int shortestVertexDistance2 = polygon.vertices[0].sqrDist(point);
|
|
{
|
|
for (int i = 1; i < polygon.vertexCount; i++) {
|
|
int curDistance2 = polygon.vertices[i].sqrDist(point);
|
|
if (curDistance2 < shortestVertexDistance2) {
|
|
closestVertex = polygon.vertices[i];
|
|
shortestVertexDistance2 = curDistance2;
|
|
}
|
|
}
|
|
}
|
|
|
|
warning("Clostest vertex forced because edgepoint was outside region.");
|
|
return closestVertex;
|
|
}
|
|
}
|
|
|
|
Vertex Region::findClosestPointOnLine(const Vertex &lineStart, const Vertex &lineEnd, const Vertex point) const {
|
|
float vector1X = static_cast<float>(point.x - lineStart.x);
|
|
float vector1Y = static_cast<float>(point.y - lineStart.y);
|
|
float vector2X = static_cast<float>(lineEnd.x - lineStart.x);
|
|
float vector2Y = static_cast<float>(lineEnd.y - lineStart.y);
|
|
float vector2Length = sqrtf(vector2X * vector2X + vector2Y * vector2Y);
|
|
vector2X /= vector2Length;
|
|
vector2Y /= vector2Length;
|
|
float distance = sqrtf(static_cast<float>((lineStart.x - lineEnd.x) * (lineStart.x - lineEnd.x) +
|
|
(lineStart.y - lineEnd.y) * (lineStart.y - lineEnd.y)));
|
|
float dot = vector1X * vector2X + vector1Y * vector2Y;
|
|
|
|
if (dot <= 0)
|
|
return lineStart;
|
|
if (dot >= distance)
|
|
return lineEnd;
|
|
|
|
Vertex vector3(static_cast<int>(vector2X * dot + 0.5f), static_cast<int>(vector2Y * dot + 0.5f));
|
|
return lineStart + vector3;
|
|
}
|
|
|
|
// Line of Sight
|
|
bool Region::isLineOfSight(const Vertex &a, const Vertex &b) const {
|
|
assert(_polygons.size());
|
|
|
|
// The line must be within the contour polygon, and outside of any hole polygons
|
|
Common::Array<Polygon>::const_iterator iter = _polygons.begin();
|
|
if (!(*iter).isLineInterior(a, b)) return false;
|
|
for (iter++; iter != _polygons.end(); iter++)
|
|
if (!(*iter).isLineExterior(a, b)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Persistence
|
|
bool Region::persist(OutputPersistenceBlock &writer) {
|
|
bool Result = true;
|
|
|
|
writer.write(static_cast<uint32>(_type));
|
|
writer.write(_valid);
|
|
writer.write((int32)_position.x);
|
|
writer.write((int32)_position.y);
|
|
|
|
writer.write((uint32)_polygons.size());
|
|
Common::Array<Polygon>::iterator It = _polygons.begin();
|
|
while (It != _polygons.end()) {
|
|
Result &= It->persist(writer);
|
|
++It;
|
|
}
|
|
|
|
writer.write((int32)_boundingBox.left);
|
|
writer.write((int32)_boundingBox.top);
|
|
writer.write((int32)_boundingBox.right);
|
|
writer.write((int32)_boundingBox.bottom);
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool Region::unpersist(InputPersistenceBlock &reader) {
|
|
reader.read(_valid);
|
|
reader.read(_position.x);
|
|
reader.read(_position.y);
|
|
|
|
_polygons.clear();
|
|
uint32 PolygonCount;
|
|
reader.read(PolygonCount);
|
|
for (uint i = 0; i < PolygonCount; ++i) {
|
|
_polygons.push_back(Polygon(reader));
|
|
}
|
|
|
|
reader.read(_boundingBox.left);
|
|
reader.read(_boundingBox.top);
|
|
reader.read(_boundingBox.right);
|
|
reader.read(_boundingBox.bottom);
|
|
|
|
return reader.isGood();
|
|
}
|
|
|
|
Vertex Region::getCentroid() const {
|
|
if (_polygons.size() > 0)
|
|
return _polygons[0].getCentroid();
|
|
return
|
|
Vertex();
|
|
}
|
|
|
|
} // End of namespace Sword25
|