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