osb/source/base/StarWorldGeometry.cpp
2023-06-20 14:33:09 +10:00

359 lines
11 KiB
C++

#include "StarWorldGeometry.hpp"
namespace Star {
function<float(float, float)> WorldGeometry::xDiffFunction() const {
if (m_size[0] == 0) {
return [](float x1, float x2) -> float { return x1 - x2; };
} else {
unsigned xsize = m_size[0];
return [xsize](float x1, float x2) -> float { return wrapDiffF<float>(x1, x2, xsize); };
}
}
function<Vec2F(Vec2F, Vec2F)> WorldGeometry::diffFunction() const {
if (m_size[0] == 0) {
return [](Vec2F const& a, Vec2F const& b) -> Vec2F { return a - b; };
} else {
unsigned xsize = m_size[0];
return [xsize](Vec2F const& a, Vec2F const& b) -> Vec2F {
return Vec2F(wrapDiffF<float>(a[0], b[0], xsize), a[1] - b[1]);
};
}
}
function<float(float, float, float)> WorldGeometry::xLerpFunction(Maybe<float> discontinuityThreshold) const {
if (m_size[0] == 0) {
return [](float, float min, float) -> float { return min; };
} else {
unsigned xsize = m_size[0];
return [discontinuityThreshold, xsize](float offset, float min, float max) -> float {
float distance = wrapDiffF<float>(max, min, xsize);
if (discontinuityThreshold && distance > *discontinuityThreshold)
return min + distance;
return min + offset * distance;
};
}
}
function<Vec2F(float, Vec2F, Vec2F)> WorldGeometry::lerpFunction(Maybe<float> discontinuityThreshold) const {
if (m_size[0] == 0) {
return [](float, Vec2F const& min, Vec2F const&) -> Vec2F { return min; };
} else {
unsigned xsize = m_size[0];
return [discontinuityThreshold, xsize](float offset, Vec2F const& min, Vec2F const& max) -> Vec2F {
Vec2F distance = Vec2F(wrapDiffF<float>(max[0], min[0], xsize), max[1] - min[1]);
if (discontinuityThreshold && distance.magnitude() > *discontinuityThreshold)
return min + distance;
return min + offset * distance;
};
}
}
StaticList<RectF, 2> WorldGeometry::splitRect(RectF const& bbox) const {
if (bbox.isNull() || m_size[0] == 0)
return {bbox};
Vec2F minWrap = xwrap(bbox.min());
RectF bboxWrap = RectF(minWrap, minWrap + bbox.size());
// This does not work for ranges greater than m_size[0] wide!
starAssert(bbox.xMax() - bbox.xMin() <= (float)m_size[0]);
// Since min is wrapped, we're only checking to see if max is on the other
// side of the wrap point
if (bboxWrap.xMax() > m_size[0]) {
return {RectF(bboxWrap.xMin(), bboxWrap.yMin(), m_size[0], bboxWrap.yMax()),
RectF(0, bboxWrap.yMin(), bboxWrap.xMax() - m_size[0], bboxWrap.yMax())};
} else {
return {bboxWrap};
}
}
StaticList<RectF, 2> WorldGeometry::splitRect(RectF bbox, Vec2F const& position) const {
bbox.translate(position);
return splitRect(bbox);
}
StaticList<RectI, 2> WorldGeometry::splitRect(RectI const bbox) const {
if (bbox.isNull() || m_size[0] == 0)
return {bbox};
Vec2I minWrap = xwrap(bbox.min());
RectI bboxWrap = RectI(minWrap, minWrap + bbox.size());
// This does not work for ranges greater than m_size[0] wide!
starAssert(bbox.xMax() - bbox.xMin() <= (int)m_size[0]);
// Since min is wrapped, we're only checking to see if max is on the other
// side of the wrap point
if (bboxWrap.xMax() > (int)m_size[0]) {
return {RectI(bboxWrap.xMin(), bboxWrap.yMin(), m_size[0], bboxWrap.yMax()),
RectI(0, bboxWrap.yMin(), bboxWrap.xMax() - m_size[0], bboxWrap.yMax())};
} else {
return {bboxWrap};
}
}
StaticList<Line2F, 2> WorldGeometry::splitLine(Line2F line, bool preserveDirection) const {
if (m_size[0] == 0)
return {line};
bool swapDirection = line.makePositive() && preserveDirection;
Vec2F minWrap = xwrap(line.min());
// diff is safe because we're looking for the line gnostic diff
Line2F lineWrap = Line2F(minWrap, minWrap + line.diff());
// Since min is wrapped, we're only checking to see if max is on the other
// side of the wrap point
if (lineWrap.max()[0] > m_size[0]) {
Vec2F intersection = lineWrap.intersection(Line2F(Vec2F(m_size[0], 0), Vec2F(m_size)), true).point;
if (swapDirection)
return {Line2F(lineWrap.max() - Vec2F(m_size[0], 0), Vec2F(0, intersection[1])),
Line2F(Vec2F(m_size[0], intersection[1]), lineWrap.min())};
else
return {Line2F(lineWrap.min(), Vec2F(m_size[0], intersection[1])),
Line2F(Vec2F(0, intersection[1]), lineWrap.max() - Vec2F(m_size[0], 0))};
} else {
if (swapDirection)
lineWrap.reverse();
return {lineWrap};
}
}
StaticList<Line2F, 2> WorldGeometry::splitLine(Line2F line, Vec2F const& position, bool preserveDirection) const {
line.translate(position);
return splitLine(line, preserveDirection);
}
StaticList<PolyF, 2> WorldGeometry::splitPoly(PolyF const& poly) const {
if (poly.isNull() || m_size[0] == 0)
return {poly};
Array<PolyF, 2> res;
bool polySelect = false;
Line2F worldBoundRight = {Vec2F(m_size[0], 0), Vec2F(m_size[0], 1)};
Line2F worldBoundLeft = {Vec2F(0, 0), Vec2F(0, 1)};
for (unsigned i = 0; i < poly.sides(); i++) {
Line2F segment = poly.side(i);
if ((segment.min()[0] < 0) ^ (segment.max()[0] < 0)) {
Vec2F worldCorrect = {(float)m_size[0], 0};
Vec2F intersect = segment.intersection(worldBoundLeft, true).point;
if (segment.min()[0] < 0) {
res[polySelect].add(segment.min() + worldCorrect);
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
polySelect = !polySelect;
res[polySelect].add(Vec2F(0, intersect[1]));
} else {
res[polySelect].add(segment.min());
res[polySelect].add(Vec2F(0, intersect[1]));
polySelect = !polySelect;
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
}
} else if ((segment.min()[0] > m_size[0]) ^ (segment.max()[0] > m_size[0])) {
Vec2F worldCorrect = {(float)m_size[0], 0};
Vec2F intersect = segment.intersection(worldBoundRight, true).point;
if (segment.min()[0] > m_size[0]) {
res[polySelect].add(segment.min() - worldCorrect);
res[polySelect].add(Vec2F(0, intersect[1]));
polySelect = !polySelect;
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
} else {
res[polySelect].add(segment.min());
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
polySelect = !polySelect;
res[polySelect].add(Vec2F(0, intersect[1]));
}
} else {
if (segment.min()[0] < 0) {
res[polySelect].add(segment.min() + Vec2F((float)m_size[0], 0));
} else if (segment.min()[0] > m_size[0]) {
res[polySelect].add(segment.min() - Vec2F((float)m_size[0], 0));
} else {
res[polySelect].add(segment.min());
}
}
}
if (res[1].isNull())
return {res[0]};
if (res[0].isNull())
return {res[1]};
else
return {res[0], res[1]};
}
StaticList<PolyF, 2> WorldGeometry::splitPoly(PolyF poly, Vec2F const& position) const {
poly.translate(position);
return splitPoly(poly);
}
StaticList<Vec2I, 2> WorldGeometry::splitXRegion(Vec2I const& xRegion) const {
if (m_size[0] == 0)
return {xRegion};
starAssert(xRegion[1] >= xRegion[0]);
// This does not work for ranges greater than m_size[0] wide!
starAssert(xRegion[1] - xRegion[0] <= (int)m_size[0]);
int x1 = xwrap(xRegion[0]);
int x2 = x1 + xRegion[1] - xRegion[0];
if (x2 > (int)m_size[0]) {
return {Vec2I(x1, m_size[0]), Vec2I(0.0f, x2 - m_size[0])};
} else {
return {{x1, x2}};
}
}
StaticList<Vec2F, 2> WorldGeometry::splitXRegion(Vec2F const& xRegion) const {
if (m_size[0] == 0)
return {xRegion};
starAssert(xRegion[1] >= xRegion[0]);
// This does not work for ranges greater than m_size[0] wide!
starAssert(xRegion[1] - xRegion[0] <= (float)m_size[0]);
float x1 = xwrap(xRegion[0]);
float x2 = x1 + xRegion[1] - xRegion[0];
if (x2 > m_size[0]) {
return {Vec2F(x1, m_size[0]), Vec2F(0.0f, x2 - m_size[0])};
} else {
return {{x1, x2}};
}
}
bool WorldGeometry::rectContains(RectF const& rect, Vec2F const& pos) const {
auto wpos = xwrap(pos);
for (auto const& r : splitRect(rect)) {
if (r.contains(wpos))
return true;
}
return false;
}
bool WorldGeometry::rectIntersectsRect(RectF const& rect1, RectF const& rect2) const {
for (auto const& r1 : splitRect(rect1)) {
for (auto const& r2 : splitRect(rect2)) {
if (r1.intersects(r2))
return true;
}
}
return false;
}
RectF WorldGeometry::rectOverlap(RectF const& rect1, RectF const& rect2) const {
return rect1.overlap(RectF::withSize(nearestTo(rect1.min(), rect2.min()), rect2.size()));
}
bool WorldGeometry::polyContains(PolyF const& poly, Vec2F const& pos) const {
auto wpos = xwrap(pos);
for (auto const& p : splitPoly(poly)) {
if (p.contains(wpos))
return true;
}
return false;
}
float WorldGeometry::polyOverlapArea(PolyF const& poly1, PolyF const& poly2) const {
float area = 0.0f;
for (auto const& p1 : splitPoly(poly1)) {
for (auto const& p2 : splitPoly(poly2))
area += PolyF::clip(p1, p2).convexArea();
}
return area;
}
bool WorldGeometry::lineIntersectsRect(Line2F const& line, RectF const& rect) const {
for (auto l : splitLine(line)) {
for (auto box : splitRect(rect)) {
if (box.intersects(l)) {
return true;
}
}
}
return false;
}
bool WorldGeometry::lineIntersectsPoly(Line2F const& line, PolyF const& poly) const {
for (auto a : splitLine(line)) {
for (auto b : splitPoly(poly)) {
if (b.intersects(a)) {
return true;
}
}
}
return false;
}
bool WorldGeometry::polyIntersectsPoly(PolyF const& polyA, PolyF const& polyB) const {
for (auto a : splitPoly(polyA)) {
for (auto b : splitPoly(polyB)) {
if (b.intersects(a))
return true;
}
}
return false;
}
bool WorldGeometry::rectIntersectsCircle(RectF const& rect, Vec2F const& center, float radius) const {
if (rect.contains(center))
return true;
for (auto const& e : rect.edges()) {
if (lineIntersectsCircle(e, center, radius))
return true;
}
return false;
}
bool WorldGeometry::lineIntersectsCircle(Line2F const& line, Vec2F const& center, float radius) const {
for (auto const& sline : splitLine(line)) {
if (sline.distanceTo(nearestTo(sline.center(), center)) <= radius)
return true;
}
return false;
}
Maybe<Vec2F> WorldGeometry::lineIntersectsPolyAt(Line2F const& line, PolyF const& poly) const {
for (auto a : splitLine(line, true)) {
for (auto b : splitPoly(poly)) {
if (auto intersection = b.lineIntersection(a))
return intersection->point;
}
}
return {};
}
float WorldGeometry::polyDistance(PolyF const& poly, Vec2F const& point) const {
auto spoint = nearestTo(poly.center(), point);
return poly.distance(spoint);
}
Vec2F WorldGeometry::nearestCoordInBox(RectF const& box, Vec2F const& pos) const {
RectF t(box);
auto offset = t.center();
auto r = diff(pos, offset);
t.setCenter({});
return t.nearestCoordTo(r) + offset;
}
Vec2F WorldGeometry::diffToNearestCoordInBox(RectF const& box, Vec2F const& pos) const {
RectF t(box);
auto offset = t.center();
auto r = diff(pos, offset);
t.setCenter({});
auto coord = t.nearestCoordTo(r) + offset;
return diff(pos, coord);
}
}