aboutsummaryrefslogtreecommitdiff
path: root/engines/sword25/math/polygon.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sword25/math/polygon.cpp')
-rw-r--r--engines/sword25/math/polygon.cpp491
1 files changed, 491 insertions, 0 deletions
diff --git a/engines/sword25/math/polygon.cpp b/engines/sword25/math/polygon.cpp
new file mode 100644
index 0000000000..700c375e8c
--- /dev/null
+++ b/engines/sword25/math/polygon.cpp
@@ -0,0 +1,491 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/*
+ * 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) : 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 initialising 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();
+ _isConvex = computeIsConvex();
+ _centroid = computeCentroid();
+
+ return true;
+}
+
+// Review the order of the vertices
+// ---------------------------------
+
+bool Polygon::isCW() const {
+ return _isCW;
+}
+
+bool Polygon::isCCW() 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;
+}
+
+// Testing for Convex / Concave
+// ------------------------
+
+bool Polygon::isConvex() const {
+ return _isConvex;
+}
+
+bool Polygon::isConcave() const {
+ return !isConvex();
+}
+
+bool Polygon::computeIsConvex() const {
+ // Polygons with three or less vertices can only be convex
+ if (vertexCount <= 3) return true;
+
+ // All angles in the polygon computed will have the same direction sign if the polygon is convex
+ int flag = 0;
+ for (int i = 0; i < vertexCount; i++) {
+ // Determine the next two vertecies to check
+ int j = (i + 1) % vertexCount;
+ int k = (i + 2) % vertexCount;
+
+ // Calculate the cross product of the three vertecies
+ int cross = crossProduct(vertices[i], vertices[j], vertices[k]);
+
+ // The lower two bits of the flag represent the following:
+ // 0: negative angle occurred
+ // 1: positive angle occurred
+
+ // The sign of the current angle is recorded in Flag
+ if (cross < 0)
+ flag |= 1;
+ else if (cross > 0)
+ flag |= 2;
+
+ // If flag is 3, there are both positive and negative angles; so the polygon is concave
+ if (flag == 3)
+ return false;
+ }
+
+ // Polygon is convex
+ return true;
+}
+
+// Make a determine vertex order
+// -----------------------------
+
+void Polygon::ensureCWOrder() {
+ if (!isCW())
+ reverseVertexOrder();
+}
+
+void Polygon::ensureCCWOrder() {
+ if (!isCCW())
+ 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);
+}
+
+// Scalar Product
+// --------------
+
+int Polygon::dotProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const {
+ return (v1.x - v2.x) * (v3.x - v2.x) +
+ (v1.y - v2.y) * (v3.x - v2.y);
+}
+
+// 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(vertices[i].x);
+ writer.write(vertices[i].y);
+ }
+
+ return true;
+}
+
+bool Polygon::unpersist(InputPersistenceBlock &reader) {
+ int storedvertexCount;
+ reader.read(storedvertexCount);
+
+ Common::Array<Vertex> storedvertices;
+ for (int i = 0; i < storedvertexCount; ++i) {
+ int 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