mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-26 19:55:44 +00:00
423 lines
13 KiB
C++
423 lines
13 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 <math.h>
|
|
|
|
#include "sword25/kernel/outputpersistenceblock.h"
|
|
#include "sword25/kernel/inputpersistenceblock.h"
|
|
|
|
#include "sword25/math/polygon.h"
|
|
#include "sword25/math/line.h"
|
|
|
|
namespace Sword25 {
|
|
|
|
Polygon::Polygon() : vertexCount(0), vertices(NULL) {
|
|
}
|
|
|
|
Polygon::Polygon(int vertexCount_, const Vertex *vertices_) : vertexCount(0), vertices(NULL) {
|
|
init(vertexCount_, vertices_);
|
|
}
|
|
|
|
Polygon::Polygon(const Polygon &other) : Persistable(other), vertexCount(0), vertices(NULL) {
|
|
init(other.vertexCount, other.vertices);
|
|
}
|
|
|
|
Polygon::Polygon(InputPersistenceBlock &Reader) : vertexCount(0), vertices(NULL) {
|
|
unpersist(Reader);
|
|
}
|
|
|
|
Polygon::~Polygon() {
|
|
delete[] vertices;
|
|
}
|
|
|
|
bool Polygon::init(int vertexCount_, const Vertex *vertices_) {
|
|
// Rember the old obstate to restore it if an error occurs whilst initializing it with the new data
|
|
int oldvertexCount = this->vertexCount;
|
|
Vertex *oldvertices = this->vertices;
|
|
|
|
this->vertexCount = vertexCount_;
|
|
this->vertices = new Vertex[vertexCount_ + 1];
|
|
memcpy(this->vertices, vertices_, sizeof(Vertex) * vertexCount_);
|
|
// TODO:
|
|
// Duplicate and remove redundant vertecies (Superflous = 3 co-linear verts)
|
|
// _WeedRepeatedvertices();
|
|
// The first vertex is repeated at the end of the vertex array; this simplifies
|
|
// some algorithms, running through the edges and thus can save the overflow control.
|
|
this->vertices[vertexCount_] = this->vertices[0];
|
|
|
|
// If the polygon is self-intersecting, the object state is restore, and an error signalled
|
|
if (checkForSelfIntersection()) {
|
|
delete[] this->vertices;
|
|
this->vertices = oldvertices;
|
|
this->vertexCount = oldvertexCount;
|
|
|
|
// BS_LOG_ERROR("POLYGON: Tried to create a self-intersecting polygon.\n");
|
|
return false;
|
|
}
|
|
|
|
// Release old vertex list
|
|
delete[] oldvertices;
|
|
|
|
// Calculate properties of the polygon
|
|
_isCW = computeIsCW();
|
|
_centroid = computeCentroid();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Review the order of the vertices
|
|
// ---------------------------------
|
|
|
|
bool Polygon::isCW() const {
|
|
return _isCW;
|
|
}
|
|
bool Polygon::computeIsCW() const {
|
|
if (vertexCount) {
|
|
// Find the vertex on extreme bottom right
|
|
int v2Index = findLRVertexIndex();
|
|
|
|
// Find the vertex before and after it
|
|
int v1Index = (v2Index + (vertexCount - 1)) % vertexCount;
|
|
int v3Index = (v2Index + 1) % vertexCount;
|
|
|
|
// Cross product form
|
|
// If the cross product of the vertex lying fartherest bottom left is positive,
|
|
// the vertecies arrranged in a clockwise order. Otherwise counter-clockwise
|
|
if (crossProduct(vertices[v1Index], vertices[v2Index], vertices[v3Index]) >= 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Polygon::findLRVertexIndex() const {
|
|
if (vertexCount) {
|
|
int curIndex = 0;
|
|
int maxX = vertices[0].x;
|
|
int maxY = vertices[0].y;
|
|
|
|
for (int i = 1; i < vertexCount; i++) {
|
|
if (vertices[i].y > maxY ||
|
|
(vertices[i].y == maxY && vertices[i].x > maxX)) {
|
|
maxX = vertices[i].x;
|
|
maxY = vertices[i].y;
|
|
curIndex = i;
|
|
}
|
|
}
|
|
|
|
return curIndex;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
// Make a determine vertex order
|
|
// -----------------------------
|
|
|
|
void Polygon::ensureCWOrder() {
|
|
if (!isCW())
|
|
reverseVertexOrder();
|
|
}
|
|
// Reverse the order of vertecies
|
|
// ------------------------------
|
|
|
|
void Polygon::reverseVertexOrder() {
|
|
// vertices are exchanged in pairs, until the list has been completely reversed
|
|
for (int i = 0; i < vertexCount / 2; i++) {
|
|
Vertex tempVertex = vertices[i];
|
|
vertices[i] = vertices[vertexCount - i - 1];
|
|
vertices[vertexCount - i - 1] = tempVertex;
|
|
}
|
|
|
|
// Vertexordnung neu berechnen.
|
|
_isCW = computeIsCW();
|
|
}
|
|
|
|
// Cross Product
|
|
// -------------
|
|
|
|
int Polygon::crossProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const {
|
|
return (v2.x - v1.x) * (v3.y - v2.y) -
|
|
(v2.y - v1.y) * (v3.x - v2.x);
|
|
}
|
|
// Check for self-intersections
|
|
// ----------------------------
|
|
|
|
bool Polygon::checkForSelfIntersection() const {
|
|
// TODO: Finish this
|
|
/*
|
|
float AngleSum = 0.0f;
|
|
for (int i = 0; i < vertexCount; i++) {
|
|
int j = (i + 1) % vertexCount;
|
|
int k = (i + 2) % vertexCount;
|
|
|
|
float Dot = DotProduct(vertices[i], vertices[j], vertices[k]);
|
|
|
|
// Skalarproduct normalisieren
|
|
float Length1 = sqrt((vertices[i].x - vertices[j].x) * (vertices[i].x - vertices[j].x) +
|
|
(vertices[i].y - vertices[j].y) * (vertices[i].y - vertices[j].y));
|
|
float Length2 = sqrt((vertices[k].x - vertices[j].x) * (vertices[k].x - vertices[j].x) +
|
|
(vertices[k].y - vertices[j].y) * (vertices[k].y - vertices[j].y));
|
|
float Norm = Length1 * Length2;
|
|
|
|
if (Norm > 0.0f) {
|
|
Dot /= Norm;
|
|
AngleSum += acos(Dot);
|
|
}
|
|
}
|
|
*/
|
|
|
|
return false;
|
|
}
|
|
|
|
// Move
|
|
// ----
|
|
|
|
void Polygon::operator+=(const Vertex &delta) {
|
|
// Move all vertecies
|
|
for (int i = 0; i < vertexCount; i++)
|
|
vertices[i] += delta;
|
|
|
|
// Shift the focus
|
|
_centroid += delta;
|
|
}
|
|
|
|
// Line of Sight
|
|
// -------------
|
|
|
|
bool Polygon::isLineInterior(const Vertex &a, const Vertex &b) const {
|
|
// Both points have to be in the polygon
|
|
if (!isPointInPolygon(a, true) || !isPointInPolygon(b, true))
|
|
return false;
|
|
|
|
// If the points are identical, the line is trivially within the polygon
|
|
if (a == b)
|
|
return true;
|
|
|
|
// Test whether the line intersects a line segment strictly (proper intersection)
|
|
for (int i = 0; i < vertexCount; i++) {
|
|
int j = (i + 1) % vertexCount;
|
|
const Vertex &vs = vertices[i];
|
|
const Vertex &ve = vertices[j];
|
|
|
|
// If the line intersects a line segment strictly (proper cross section) the line is not in the polygon
|
|
if (Line::doesIntersectProperly(a, b, vs, ve))
|
|
return false;
|
|
|
|
// If one of the two line items is on the edge and the other is to the right of the edge,
|
|
// then the line is not completely within the polygon
|
|
if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexRight(vs, ve, b))
|
|
return false;
|
|
if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexRight(vs, ve, a))
|
|
return false;
|
|
|
|
// If one of the two line items is on a vertex, the line traces into the polygon
|
|
if ((a == vs) && !isLineInCone(i, b, true))
|
|
return false;
|
|
if ((b == vs) && !isLineInCone(i, a, true))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Polygon::isLineExterior(const Vertex &a, const Vertex &b) const {
|
|
// Neither of the two points must be strictly in the polygon (on the edge is allowed)
|
|
if (isPointInPolygon(a, false) || isPointInPolygon(b, false))
|
|
return false;
|
|
|
|
// If the points are identical, the line is trivially outside of the polygon
|
|
if (a == b)
|
|
return true;
|
|
|
|
// Test whether the line intersects a line segment strictly (proper intersection)
|
|
for (int i = 0; i < vertexCount; i++) {
|
|
int j = (i + 1) % vertexCount;
|
|
const Vertex &vs = vertices[i];
|
|
const Vertex &ve = vertices[j];
|
|
|
|
// If the line intersects a line segment strictly (proper intersection), then
|
|
// the line is partially inside the polygon
|
|
if (Line::doesIntersectProperly(a, b, vs, ve))
|
|
return false;
|
|
|
|
// If one of the two line items is on the edge and the other is to the right of the edge,
|
|
// the line is not completely outside the polygon
|
|
if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexLeft(vs, ve, b))
|
|
return false;
|
|
if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexLeft(vs, ve, a))
|
|
return false;
|
|
|
|
// If one of the lwo line items is on a vertex, the line must not run into the polygon
|
|
if ((a == vs) && isLineInCone(i, b, false))
|
|
return false;
|
|
if ((b == vs) && isLineInCone(i, a, false))
|
|
return false;
|
|
|
|
// If the vertex with start and end point is collinear, (a vs) and (b, vs) is not in the polygon
|
|
if (Line::isOnLine(a, b, vs)) {
|
|
if (isLineInCone(i, a, false))
|
|
return false;
|
|
if (isLineInCone(i, b, false))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Polygon::isLineInCone(int startVertexIndex, const Vertex &endVertex, bool includeEdges) const {
|
|
const Vertex &startVertex = vertices[startVertexIndex];
|
|
const Vertex &nextVertex = vertices[(startVertexIndex + 1) % vertexCount];
|
|
const Vertex &prevVertex = vertices[(startVertexIndex + vertexCount - 1) % vertexCount];
|
|
|
|
if (Line::isVertexLeftOn(prevVertex, startVertex, nextVertex)) {
|
|
if (includeEdges)
|
|
return Line::isVertexLeftOn(endVertex, startVertex, nextVertex) &&
|
|
Line::isVertexLeftOn(startVertex, endVertex, prevVertex);
|
|
else
|
|
return Line::isVertexLeft(endVertex, startVertex, nextVertex) &&
|
|
Line::isVertexLeft(startVertex, endVertex, prevVertex);
|
|
} else {
|
|
if (includeEdges)
|
|
return !(Line::isVertexLeft(endVertex, startVertex, prevVertex) &&
|
|
Line::isVertexLeft(startVertex, endVertex, nextVertex));
|
|
else
|
|
return !(Line::isVertexLeftOn(endVertex, startVertex, prevVertex) &&
|
|
Line::isVertexLeftOn(startVertex, endVertex, nextVertex));
|
|
}
|
|
}
|
|
|
|
// Point-Polygon Tests
|
|
// -------------------
|
|
|
|
bool Polygon::isPointInPolygon(int x, int y, bool borderBelongsToPolygon) const {
|
|
return isPointInPolygon(Vertex(x, y), borderBelongsToPolygon);
|
|
}
|
|
|
|
bool Polygon::isPointInPolygon(const Vertex &point, bool edgesBelongToPolygon) const {
|
|
int rcross = 0; // Number of right-side overlaps
|
|
int lcross = 0; // Number of left-side overlaps
|
|
|
|
// Each edge is checked whether it cuts the outgoing stream from the point
|
|
for (int i = 0; i < vertexCount; i++) {
|
|
const Vertex &edgeStart = vertices[i];
|
|
const Vertex &edgeEnd = vertices[(i + 1) % vertexCount];
|
|
|
|
// A vertex is a point? Then it lies on one edge of the polygon
|
|
if (point == edgeStart)
|
|
return edgesBelongToPolygon;
|
|
|
|
if ((edgeStart.y > point.y) != (edgeEnd.y > point.y)) {
|
|
int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
|
|
int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
|
|
if ((term1 > 0) == (term2 >= 0))
|
|
rcross++;
|
|
}
|
|
|
|
if ((edgeStart.y < point.y) != (edgeEnd.y < point.y)) {
|
|
int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
|
|
int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
|
|
if ((term1 < 0) == (term2 <= 0))
|
|
lcross++;
|
|
}
|
|
}
|
|
|
|
// The point is on an adge, if the number of left and right intersections have the same even numbers
|
|
if ((rcross % 2) != (lcross % 2))
|
|
return edgesBelongToPolygon;
|
|
|
|
// The point is strictly inside the polygon if and only if the number of overlaps is odd
|
|
if ((rcross % 2) == 1)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool Polygon::persist(OutputPersistenceBlock &writer) {
|
|
writer.write(vertexCount);
|
|
for (int i = 0; i < vertexCount; ++i) {
|
|
writer.write((int32)vertices[i].x);
|
|
writer.write((int32)vertices[i].y);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Polygon::unpersist(InputPersistenceBlock &reader) {
|
|
int32 storedvertexCount;
|
|
reader.read(storedvertexCount);
|
|
|
|
Common::Array<Vertex> storedvertices;
|
|
for (int i = 0; i < storedvertexCount; ++i) {
|
|
int32 x, y;
|
|
reader.read(x);
|
|
reader.read(y);
|
|
storedvertices.push_back(Vertex(x, y));
|
|
}
|
|
|
|
init(storedvertexCount, &storedvertices[0]);
|
|
|
|
return reader.isGood();
|
|
}
|
|
|
|
// Main Focus
|
|
// ----------
|
|
|
|
Vertex Polygon::getCentroid() const {
|
|
return _centroid;
|
|
}
|
|
|
|
Vertex Polygon::computeCentroid() const {
|
|
// Area of the polygon is calculated
|
|
int doubleArea = 0;
|
|
for (int i = 0; i < vertexCount; ++i) {
|
|
doubleArea += vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y;
|
|
}
|
|
|
|
// Avoid division by zero in the next step
|
|
if (doubleArea == 0)
|
|
return Vertex();
|
|
|
|
// Calculate centroid
|
|
Vertex centroid;
|
|
for (int i = 0; i < vertexCount; ++i) {
|
|
int area = vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y;
|
|
centroid.x += (vertices[i].x + vertices[i + 1].x) * area;
|
|
centroid.y += (vertices[i].y + vertices[i + 1].y) * area;
|
|
}
|
|
centroid.x /= 3 * doubleArea;
|
|
centroid.y /= 3 * doubleArea;
|
|
|
|
return centroid;
|
|
}
|
|
|
|
} // End of namespace Sword25
|