/* 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 3 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, see . * */ /* * This code is based on the CRAB engine * * Copyright (c) Arvind Raja Yadav * * Licensed under MIT * */ #include "graphics/screen.h" #include "crab/crab.h" #include "crab/Polygon.h" namespace Crab { // Calculate the distance between [minA, maxA] and [minB, maxB] // The distance will be negative if the intervals overlap float IntervalDistance(float minA, float maxA, float minB, float maxB) { if (minA < minB) return minB - maxA; return minA - maxB; } void Polygon2D::addPoint(const Vector2f &ref, const Common::String &x, const Common::String &y, Vector2f &min, Vector2f &max) { Vector2f p; p.x = ref.x + stringToNumber(x); p.y = ref.y + stringToNumber(y); if (p.x < min.x) min.x = p.x; if (p.x > max.x) max.x = p.x; if (p.y < min.y) min.y = p.y; if (p.y > max.y) max.y = p.y; _point.push_back(p); } void Polygon2D::load(rapidxml::xml_node *node, Rect &bounds) { Vector2f ref; ref.load(node); // Converting a polygon to an axis aligned bounding box is easy - just record the minimum and maximum values of x and y // for the vertices of the polygon, then minimum = top left corner, max - min = dimensions Vector2f min(std::numeric_limits::max(), std::numeric_limits::max()); Vector2f max(-std::numeric_limits::max(), -std::numeric_limits::max()); _point.clear(); rapidxml::xml_node *polynode = node->first_node("polygon"); if (polynode != nullptr) { Common::String points, x, y; loadStr(points, "points", polynode); bool comma = false; for (const auto &i : points) { if (i == ',') comma = true; else if (i == ' ') { addPoint(ref, x, y, min, max); comma = false; x.clear(); y.clear(); } else if (comma) y += i; else x += i; } addPoint(ref, x, y, min, max); bounds.x = min.x; bounds.y = min.y; bounds.w = max.x - min.x; bounds.h = max.y - min.y; } setEdge(); } void Polygon2D::setEdge() { _edge.clear(); Vector2f p1, p2, res; for (uint i = 0; i < _point.size(); i++) { p1 = _point[i]; if (i + 1 >= _point.size()) p2 = _point[0]; else p2 = _point[i + 1]; res.x = p2.x - p1.x; res.y = p2.y - p1.y; _edge.push_back(res); } } Vector2f Polygon2D::center() const { Vector2f total; for (uint i = 0; i < _point.size(); i++) { total.x += _point[i].x; total.y += _point[i].y; } Vector2f ret; if (_point.size() > 0) { ret.x = total.x / _point.size(); ret.y = total.y / _point.size(); } return ret; } void Polygon2D::offset(const float &x, const float &y) { for (auto &i : _point) { i.x += x; i.y += y; } } void Polygon2D::project(const Vector2f &axis, float &min, float &max) const { // To project a point on an axis use the dot product float d = axis.dotProduct(_point[0]); min = d; max = d; for (auto i = _point.begin(); i != _point.end(); ++i) { d = i->dotProduct(axis); if (d < min) min = d; else if (d > max) max = d; } } PolygonCollisionResult Polygon2D::collide(const Rect &rect) const { Polygon2D polyB; Vector2f p; p.x = rect.x; p.y = rect.y; polyB._point.push_back(p); p.x = rect.x + rect.w; p.y = rect.y; polyB._point.push_back(p); p.x = rect.x + rect.w; p.y = rect.y + rect.h; polyB._point.push_back(p); p.x = rect.x; p.y = rect.y + rect.h; polyB._point.push_back(p); polyB.setEdge(); return collide(polyB); } PolygonCollisionResult Polygon2D::collide(const Polygon2D &polyB) const { PolygonCollisionResult result; result._intersect = true; int edgeCountA = _edge.size(); int edgeCountB = polyB._edge.size(); float minIntervalDistance = std::numeric_limits::max(); Vector2f translationAxis; Vector2f e; // Loop through all the edges of both polygons for (int edgeIndex = 0; edgeIndex < edgeCountA + edgeCountB; edgeIndex++) { if (edgeIndex < edgeCountA) e = _edge[edgeIndex]; else e = polyB._edge[edgeIndex - edgeCountA]; // ===== 1. Find if the Polygon2Ds are currently intersecting ===== // Find the axis perpendicular to the current edge Vector2f axis(-e.y, e.x); axis.normalize(); // Find the projection of the Polygon2D on the current axis float minA = 0; float minB = 0; float maxA = 0; float maxB = 0; project(axis, minA, maxA); polyB.project(axis, minB, maxB); // Check if the Polygon2D projections are currently intersecting float intervalDistance = IntervalDistance(minA, maxA, minB, maxB); if (intervalDistance > 0) { // If the Polygon2Ds are not intersecting and won't intersect, exit the loop result._intersect = false; break; } // Check if the current interval distance is the minimum one. If so store // the interval distance and the current distance. // This will be used to calculate the minimum translation vector intervalDistance = abs(intervalDistance); if (intervalDistance < minIntervalDistance) { minIntervalDistance = intervalDistance; translationAxis = axis; Vector2f d, ca, cb; ca = center(); cb = polyB.center(); d.x = ca.x - cb.x; d.y = ca.y - cb.y; if (d.dotProduct(translationAxis) < 0) { translationAxis.x = -translationAxis.x; translationAxis.y = -translationAxis.y; } } } // The minimum translation vector can be used to push the Polygon2Ds apart. // First moves the Polygon2Ds by their velocity // then move polyA by MinimumTranslationVector. if (result._intersect) { result._mtv.x = translationAxis.x * minIntervalDistance; result._mtv.y = translationAxis.y * minIntervalDistance; } return result; } bool Polygon2D::contains(const float &x, const float &y) { bool result = false; for (uint i = 0, j = _point.size() - 1; i < _point.size(); j = i++) { if (((_point[i].y > y) != (_point[j].y > y)) && (x < (_point[j].x - _point[i].x) * (y - _point[i].y) / (_point[j].y - _point[i].y) + _point[i].x)) result = !result; } return result; } void Polygon2D::draw(const int &xOffset, const int &yOffset, const uint8 &r, const uint8 &g, const uint8 &b, const uint8 &a) { Vector2f p1, p2; for (uint i = 0; i < _point.size(); i++) { p1 = _point[i]; if (i + 1 >= _point.size()) p2 = _point[0]; else p2 = _point[i + 1]; g_engine->_screen->drawLine(p1.x + xOffset, p1.y + yOffset, p2.x + xOffset, p2.y + yOffset, g_engine->_format->ARGBToColor(a, r, g, b)); } } } // End of namespace Crab