Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/base/StarWorldGeometry.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/base/StarWorldGeometry.cpp')
-rw-r--r--source/base/StarWorldGeometry.cpp358
1 files changed, 358 insertions, 0 deletions
diff --git a/source/base/StarWorldGeometry.cpp b/source/base/StarWorldGeometry.cpp
new file mode 100644
index 0000000..6fc676d
--- /dev/null
+++ b/source/base/StarWorldGeometry.cpp
@@ -0,0 +1,358 @@
+#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);
+}
+
+}