/* 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 .
*
*/
#ifndef QDENGINE_MINIGAMES_ADV_RECT_H
#define QDENGINE_MINIGAMES_ADV_RECT_H
#include "qdengine/xmath.h"
#include "qdengine/minigames/adv/Range.h"
namespace QDEngine {
/*
* FIXME: Подразумевается, что left < right и top < bottom, добавить
* стратегию для кустомизации этого понятия?
*/
/// Абстрактый прямоугольник.
/**
* @param ScalarType - скалярный тип
* @param VectType - векторный тип
*/
template
struct Rect {
typedef vect_type VectType;
typedef scalar_type ScalarType;
typedef Rect RectType;
typedef Rangef RangeType;
// конструкторы
Rect() :
_left(ScalarType(0)),
_top(ScalarType(0)),
_width(ScalarType(0)),
_height(ScalarType(0)) {}
/// Создаёт Rect размера \a _size, левый-верхний угол остаётся в точке (0, 0).
Rect(const VectType& size) :
_top(ScalarType(0)),
_left(ScalarType(0)),
_width(size.x),
_height(size.y) {}
Rect(ScalarType left, ScalarType top, ScalarType width, ScalarType height) :
_left(left),
_top(top),
_width(width),
_height(height) {}
Rect(const VectType& _topleft, const VectType& size) :
_left(_topleft.x),
_top(_topleft.y),
_width(size.x),
_height(size.y) {}
void set(ScalarType left, ScalarType top, ScalarType width, ScalarType height) {
_left = left;
_top = top;
_width = width;
_height = height;
}
inline ScalarType left() const {
return _left;
}
inline ScalarType top() const {
return _top;
}
inline ScalarType width() const {
return _width;
}
inline ScalarType height() const {
return _height;
}
VectType _lefttop() const {
return VectType(_left, _top);
}
VectType right_top() const {
return VectType(_left + _width, _top);
}
VectType _leftbottom() const {
return VectType(_left, _top + _height);
}
VectType right_bottom() const {
return VectType(_left + _width, _top + _height);
}
// аксессоры (вычисляющие):
inline ScalarType right() const {
return _left + _width;
}
inline ScalarType bottom() const {
return _top + _height;
}
/*
* FIXME: для float и double деление на 2 лучше заменить на умножение на 0.5,
* для целых типов лучше исползовать сдвиг.
*/
/// Возвращает координаты цетра прямоугольника.
inline VectType center() const {
return VectType(_left + _width / ScalarType(2),
_top + _height / ScalarType(2));
}
/// Возвращает размер прямоугольника.
inline VectType size() const {
return VectType(_width, _height);
}
// сеттеры:
inline void left(ScalarType left) {
_left = left;
}
inline void top(ScalarType top) {
_top = top;
}
inline void width(ScalarType width) {
_width = width;
}
inline void height(ScalarType height) {
_height = height;
}
// сеттеры (вычисляющие):
inline void right(ScalarType right) {
_left = right - _width;
}
inline void bottom(ScalarType bottom) {
_top = bottom - _height;
}
/// Переносит центр прямоугольника в точку \a _center не изменяя его размер.
inline void center(const VectType& center) {
_left = center.x - _width / ScalarType(2);
_top = center.y - _height / ScalarType(2);
}
/*
* FIXME: размер должен менятся относительно левого-верхнего угла (как у
* сеттеров width и height) или относительно центра? Добавить
* класс-стратегию для этих целей? Фунцию с другим именем (напр
* scale (), которая принимает центр, относительно которого происходит
* скэлинг)?
*/
/// Устанавливает новые размеры, сохраняя левый-верхний угол в преждней точке.
inline void size(const VectType& size) {
_width = size.x;
_height = size.y;
}
// утилиты:
/// Проверяет не находится ли точка \a _point внутри прямоугольника
inline bool point_inside(const VectType& point) const {
if (point.x >= left() && point.y >= top() &&
point.x <= right() && point.y <= bottom())
return true;
else
return false;
}
/// Проверяет не находится ли прямоугольник \a _rect внутри прямоугольника
inline bool rect_inside(const RectType& rect) const {
if (rect.left() >= left() && rect.top() >= top() &&
rect.bottom() <= bottom() && rect.right() <= right())
return true;
else
return false;
}
inline bool rect_overlap(const RectType& rect) const {
if (left() > rect.right() || right() < rect.left()
|| top() > rect.bottom() || bottom() < rect.top())
return false;
return true;
}
/// Производит скэлинг.
/**
* Возвращает копию прямоугольника, над которой произведён скэлинг
* относительно точки \a _origin.
*/
inline RectType scaled(const VectType& scale, const VectType& origin) const {
return (*this - origin) * scale + origin;
}
/// Исправляет отрицательную ширину/высоту
inline void validate() {
if (width() < ScalarType(0)) {
left(left() + width());
width(-width());
}
if (height() < ScalarType(0)) {
top(top() + height());
height(-height());
}
}
inline RectType intersection(const RectType& rect) const {
RangeType xRange = RangeType(left(), right()).intersection(RangeType(rect.left(), rect.right()));
RangeType yRange = RangeType(top(), bottom()).intersection(RangeType(rect.top(), rect.bottom()));
return RectType(xRange.minimum(), yRange.minimum(), xRange.length(), yRange.length());
}
// Операторы
RectType operator+(const VectType& point) const {
return RectType(left() + point.x, top() + point.y,
width(), height());
}
RectType operator-(const VectType& point) const {
return RectType(left() - point.x, top() - point.y,
width(), height());
}
RectType operator*(const VectType& point) const {
return RectType(left() * point.x, top() * point.y,
width() * point.x, height() * point.y);
}
RectType operator*(const RectType& rhs) const {
VectType leftTop(left() + width() * rhs.left(), top() + height() * rhs.top());
VectType size(this->size() * rhs.size());
return RectType(leftTop, size);
}
RectType operator/(const RectType& rhs) const {
VectType leftTop((left() - rhs.left()) / rhs.width(), (top() - rhs.top()) / rhs.height());
VectType size(width() / rhs.width(), height() / rhs.height());
return RectType(leftTop, size);
}
RectType operator/(const VectType& point) const {
return RectType(left() / point.x, top() / point.y,
width() / point.x, height() / point.y);
}
bool operator==(const RectType& rect) const {
return (_left == rect._left && _top == rect._top &&
_width == rect._width && _height == rect._height);
}
bool eq(const RectType& rect, ScalarType eps = FLT_COMPARE_TOLERANCE) const {
return (abs(_left - rect._left) < eps && abs(_top - rect._top) < eps &&
abs(_width - rect._width) < eps && abs(_height - rect._height) < eps);
}
bool operator!=(const RectType& rect) const {
return (_left != rect._left || _top != rect._top ||
_width != rect._width || _height != rect._height);
}
protected:
ScalarType _left;
ScalarType _top;
ScalarType _width;
ScalarType _height;
#if 0
public:
// SideKick на этом обламывается:
template
operator ::Rect() const {
return ::Rect(static_cast(left()),
static_cast(top()),
static_cast(width()),
static_cast(height()));
}
#endif
bool clipLine(VectType& pos0, VectType& pos1) const;
};
template
bool Rect::clipLine(VectType& pos0, VectType& pos1) const {
VectType p0(pos0), p1(pos1);
bool b0 = point_inside(p0);
bool b1 = point_inside(p1);
if (b0 && b1) // вся линия внутри clip
return true;
else {
float tc;
float t[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
int find = 0;
ScalarType dx = p1.x - p0.x;
ScalarType dy = p1.y - p0.y;
ScalarType crd;
if (abs(dy) > 0) {
tc = (float)(top() - p0.y) / dy;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.x + tc * dx;
if (crd >= left() && crd <= right())
t[find++] = tc;
}
tc = (float)(bottom() - p0.y) / dy;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.x + tc * dx;
if (crd >= left() && crd <= right())
t[find++] = tc;
}
}
if (abs(dx) > 0) {
tc = (float)(left() - p0.x) / dx;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.y + tc * dy;
if (crd >= top() && crd <= bottom())
t[find++] = tc;
}
tc = (float)(right() - p0.x) / dx;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.y + tc * dy;
if (crd >= top() && crd <= bottom())
t[find++] = tc;
}
}
if (b0) { //внутри только точка p0
pos1.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos0.set(p0.x, p0.y);
} else if (b1) { //внутри только точка p1
pos0.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos1.set(p1.x, p1.y);
} else if (find) { //обе точки снаружи, но часть отрезка внутри
if (t[0] < t[1]) {
pos0.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos1.set(p0.x + t[1]*dx, p0.y + t[1]*dy);
} else {
pos1.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos0.set(p0.x + t[1]*dx, p0.y + t[1]*dy);
}
} else
return false;
}
return true;
}
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_RECT_H