Thierry Crozat f55259e3b1 SWORD25: Fix regression in persistence code
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.
2013-10-05 00:25:04 +01:00

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