/* 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 #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 arranged 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 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