mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
275 lines
7 KiB
C++
275 lines
7 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 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* 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<float>(x);
|
|
p.y = ref.y + stringToNumber<float>(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<char> *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<float>::max(), std::numeric_limits<float>::max());
|
|
Vector2f max(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
|
|
|
|
_point.clear();
|
|
rapidxml::xml_node<char> *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<float>::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
|