Files
2026-02-02 04:50:13 +01:00

680 lines
22 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/>.
*
*/
#include "alcachofa/shape.h"
using namespace Common;
using namespace Math;
namespace Alcachofa {
static int sideOfLine(Point a, Point b, Point q) {
return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
}
static bool lineIntersects(Point a1, Point b1, Point a2, Point b2) {
return (sideOfLine(a1, b1, a2) > 0) != (sideOfLine(a1, b1, b2) > 0);
}
static bool segmentsIntersect(Point a1, Point b1, Point a2, Point b2) {
// as there are a number of special cases to consider,
// this method is a direct translation of the original engine
// rather than using common Math:: code
if (a2.x > b2.x) {
if (a1.x > b1.x)
return lineIntersects(b1, a1, b2, a2) && lineIntersects(b2, a2, b1, a1); //-V764
else
return lineIntersects(a1, b1, b2, a2) && lineIntersects(b2, a2, a1, b1); //-V764
} else {
if (a1.x > b1.x)
return lineIntersects(b1, a1, a2, b2) && lineIntersects(a2, b2, b1, a1); //-V764
else
return lineIntersects(a1, b1, a2, b2) && lineIntersects(a2, b2, a1, b1);
}
}
EdgeDistances::EdgeDistances(Point edgeA, Point edgeB, Point query) {
Vector2d
a = as2D(edgeA),
b = as2D(edgeB),
q = as2D(query);
float edgeLength = a.getDistanceTo(b);
Vector2d edgeDir = (b - a) / edgeLength;
Vector2d edgeNormal(-edgeDir.getY(), edgeDir.getX());
_edgeLength = edgeLength;
_onEdge = edgeDir.dotProduct(q - a);
_toEdge = abs(edgeNormal.dotProduct(q) - edgeNormal.dotProduct(a));
}
bool Polygon::contains(Point query) const {
switch (_points.size()) {
case 0:
return false;
case 1:
return query == _points[0];
case 2:
return edgeDistances(0, query)._toEdge < 2.0f;
default:
// we assume that the polygon is convex
for (uint i = 1; i < _points.size(); i++) {
if (sideOfLine(_points[i - 1], _points[i], query) < 0)
return false;
}
return sideOfLine(_points[_points.size() - 1], _points[0], query) >= 0;
}
}
bool Polygon::intersectsEdge(uint startPointI, Point a, Point b) const {
assert(startPointI < _points.size());
uint endPointI = (startPointI + 1) % _points.size();
return segmentsIntersect(_points[startPointI], _points[endPointI], a, b);
}
EdgeDistances Polygon::edgeDistances(uint startPointI, Point query) const {
assert(startPointI < _points.size());
uint endPointI = startPointI + 1 == _points.size() ? 0 : startPointI + 1;
return EdgeDistances(_points[startPointI], _points[endPointI], query);
}
static Point wiggleOnToLine(Point a, Point b, Point q) {
// due to rounding errors contains(bestPoint) might be false for on-edge closest points, let's fix that
// maybe there is a more mathematical solution to this, but it suffices for now
if (sideOfLine(a, b, q) >= 0) return q;
if (sideOfLine(a, b, q + Point(+1, 0)) >= 0) return q + Point(+1, 0);
if (sideOfLine(a, b, q + Point(-1, 0)) >= 0) return q + Point(-1, 0);
if (sideOfLine(a, b, q + Point(0, +1)) >= 0) return q + Point(0, +1);
if (sideOfLine(a, b, q + Point(0, -1)) >= 0) return q + Point(0, -1);
assert(false && "More than two pixels means some more serious math error occured");
return q;
}
Point Polygon::closestPointTo(Point query, float &distanceSqr) const {
assert(_points.size() > 0);
Common::Point bestPoint = {};
distanceSqr = std::numeric_limits<float>::infinity();
for (uint i = 0; i < _points.size(); i++) {
auto edgeDists = edgeDistances(i, query);
if (edgeDists._onEdge < 0.0f) {
float pointDistSqr = as2D(query - _points[i]).getSquareMagnitude();
if (pointDistSqr < distanceSqr) {
bestPoint = _points[i];
distanceSqr = pointDistSqr;
}
}
if (edgeDists._onEdge >= 0.0f && edgeDists._onEdge <= edgeDists._edgeLength) {
float edgeDistSqr = powf(edgeDists._toEdge, 2.0f);
if (edgeDistSqr < distanceSqr) {
distanceSqr = edgeDistSqr;
uint j = (i + 1) % _points.size();
bestPoint = _points[i] + (_points[j] - _points[i]) * (edgeDists._onEdge / edgeDists._edgeLength);
bestPoint = wiggleOnToLine(_points[i], _points[j], bestPoint);
}
}
}
return bestPoint;
}
Point Polygon::midPoint() const {
assert(_points.size() > 0);
Common::Point sum = {};
for (uint i = 0; i < _points.size(); i++)
sum += _points[i];
return sum / (int16)_points.size();
}
static float depthAtForLine(Point a, Point b, Point q, int8 depthA, int8 depthB) {
return (sqrtf(a.sqrDist(q)) / a.sqrDist(b) * depthB + depthA) * 0.01f;
}
static float depthAtForConvex(const PathFindingPolygon &p, Point q) {
float sumDepths = 0, sumDistances = 0;
for (uint i = 0; i < p._points.size(); i++) {
uint j = i + 1 == p._points.size() ? 0 : i + 1;
auto distances = p.edgeDistances(i, q);
float depthOnEdge = p._pointDepths[i] + distances._onEdge * (p._pointDepths[j] - p._pointDepths[i]) / distances._edgeLength;
if (distances._toEdge < epsilon) // q is directly on the edge
return depthOnEdge * 0.01f;
sumDepths += 1 / distances._toEdge * depthOnEdge;
sumDistances += 1 / distances._toEdge;
}
return sumDistances < epsilon ? 0
: sumDepths / sumDistances * 0.01f;
}
float PathFindingPolygon::depthAt(Point query) const {
switch (_points.size()) {
case 0:
case 1:
return 1.0f;
case 2:
return depthAtForLine(_points[0], _points[1], query, _pointDepths[0], _pointDepths[1]);
default:
return depthAtForConvex(*this, query);
}
}
uint PathFindingPolygon::findSharedPoints(
const PathFindingPolygon &other,
Common::Span<SharedPoint> sharedPoints) const {
uint count = 0;
for (uint outerI = 0; outerI < _points.size(); outerI++) {
for (uint innerI = 0; innerI < other._points.size(); innerI++) {
if (_points[outerI] == other._points[innerI]) {
assert(count < sharedPoints.size());
sharedPoints[count++] = { outerI, innerI };
}
}
}
return count;
}
static Color colorAtForLine(Point a, Point b, Point q, Color colorA, Color colorB) {
// I highly suspect RGB calculation being very bugged, so for now I just ignore and only calc alpha
float phase = sqrtf(q.sqrDist(a)) / a.sqrDist(b);
colorA.a += phase * colorB.a;
return colorA;
}
static Color colorAtForConvex(const FloorColorPolygon &p, Point query) {
// This is a quite literal translation of the original engine
// There may very well be a better way than this...
float weights[FloorColorShape::kPointsPerPolygon];
fill(weights, weights + FloorColorShape::kPointsPerPolygon, 0.0f);
for (uint i = 0; i < p._points.size(); i++) {
EdgeDistances distances = p.edgeDistances(i, query);
float edgeWeight = distances._toEdge * ABS(distances._onEdge) / distances._edgeLength;
if (distances._edgeLength > 1) {
weights[i] += edgeWeight;
weights[i + 1 == p._points.size() ? 0 : i + 1] += edgeWeight;
}
}
float weightSum = 0;
for (uint i = 0; i < p._points.size(); i++)
weightSum += weights[i];
for (uint i = 0; i < p._points.size(); i++) {
if (weights[i] < epsilon)
return p._pointColors[i];
weights[i] = weightSum / weights[i];
}
weightSum = 0;
for (uint i = 0; i < p._points.size(); i++)
weightSum += weights[i];
for (uint i = 0; i < p._points.size(); i++)
weights[i] /= weightSum;
float r = 0, g = 0, b = 0, a = 0.5f;
for (uint i = 0; i < p._points.size(); i++) {
r += p._pointColors[i].r * weights[i];
g += p._pointColors[i].g * weights[i];
b += p._pointColors[i].b * weights[i];
a += p._pointColors[i].a * weights[i];
}
return {
(byte)MIN(255, MAX(0, (int)r)),
(byte)MIN(255, MAX(0, (int)g)),
(byte)MIN(255, MAX(0, (int)b)),
(byte)MIN(255, MAX(0, (int)a)),
};
}
Color FloorColorPolygon::colorAt(Point query) const {
switch (_points.size()) {
case 0:
return kWhite;
case 1:
return { 255, 255, 255, _pointColors[0].a };
case 2:
return colorAtForLine(_points[0], _points[1], query, _pointColors[0], _pointColors[1]);
default:
return colorAtForConvex(*this, query);
}
}
Shape::Shape() {}
Shape::Shape(ReadStream &stream) {
byte complexity = stream.readByte();
uint8 pointsPerPolygon;
if (complexity > 3)
error("Invalid shape complexity %d", complexity);
else if (complexity == 3)
pointsPerPolygon = 0; // read in per polygon
else
pointsPerPolygon = 1 << complexity;
int polygonCount = stream.readUint16LE();
_polygons.reserve(polygonCount);
_points.reserve(MIN(3, (int)pointsPerPolygon) * polygonCount);
for (int i = 0; i < polygonCount; i++) {
auto pointCount = pointsPerPolygon == 0
? stream.readByte()
: pointsPerPolygon;
for (int j = 0; j < pointCount; j++)
_points.push_back(readPoint(stream));
addPolygon(pointCount);
}
}
uint Shape::addPolygon(uint maxCount) {
// Common functionality of shapes is that polygons are reduced
// so that the first point is not duplicated
uint firstI = empty() ? 0 : _polygons.back().first + _polygons.back().second;
uint newCount = maxCount;
if (maxCount > 1) {
for (newCount = 1; newCount < maxCount; newCount++) {
if (_points[firstI + newCount] == _points[firstI])
break;
}
_points.resize(firstI + newCount);
}
_polygons.push_back({ firstI, newCount });
return newCount;
}
Polygon Shape::at(uint index) const {
auto range = _polygons[index];
Polygon p;
p._index = index;
p._points = Span<const Point>(_points.data() + range.first, range.second);
return p;
}
int32 Shape::polygonContaining(Point query) const {
for (uint i = 0; i < _polygons.size(); i++) {
if (at(i).contains(query))
return (int32)i;
}
return -1;
}
bool Shape::contains(Point query) const {
return polygonContaining(query) >= 0;
}
Point Shape::closestPointTo(Point query, int32 &polygonI) const {
assert(_polygons.size() > 0);
float bestDistanceSqr = std::numeric_limits<float>::infinity();
Point bestPoint = {};
for (uint i = 0; i < _polygons.size(); i++) {
float curDistanceSqr = std::numeric_limits<float>::infinity();
Point curPoint = at(i).closestPointTo(query, curDistanceSqr);
if (curDistanceSqr < bestDistanceSqr) {
bestDistanceSqr = curDistanceSqr;
bestPoint = curPoint;
polygonI = (int32)i;
}
}
return bestPoint;
}
void Shape::setAsRectangle(const Rect &rect) {
_polygons.resize(1);
_polygons[0] = { 0, 4 };
_points.resize(4);
_points[0] = { rect.left, rect.top };
_points[1] = { rect.right, rect.top };
_points[2] = { rect.right, rect.bottom };
_points[3] = { rect.left, rect.bottom };
}
PathFindingShape::PathFindingShape() {}
PathFindingShape::PathFindingShape(ReadStream &stream) {
auto polygonCount = stream.readUint16LE();
_polygons.reserve(polygonCount);
_polygonOrders.reserve(polygonCount);
_points.reserve(polygonCount * kPointsPerPolygon);
_pointDepths.reserve(polygonCount * kPointsPerPolygon);
for (int i = 0; i < polygonCount; i++) {
for (uint j = 0; j < kPointsPerPolygon; j++)
_points.push_back(readPoint(stream));
_polygonOrders.push_back(stream.readSByte());
for (uint j = 0; j < kPointsPerPolygon; j++)
_pointDepths.push_back(stream.readByte());
uint pointCount = addPolygon(kPointsPerPolygon);
assert(pointCount <= kPointsPerPolygon);
_pointDepths.resize(_points.size());
}
setupLinks();
initializeFloydWarshall();
calculateFloydWarshall();
}
PathFindingPolygon PathFindingShape::at(uint index) const {
auto range = _polygons[index];
PathFindingPolygon p;
p._index = index;
p._points = Span<const Point>(_points.data() + range.first, range.second);
p._pointDepths = Span<const uint8>(_pointDepths.data() + range.first, range.second);
p._order = _polygonOrders[index];
return p;
}
int8 PathFindingShape::orderAt(Point query) const {
int32 polygon = polygonContaining(query);
return polygon < 0 ? 49 : _polygonOrders[polygon];
}
float PathFindingShape::depthAt(Point query) const {
int32 polygon = polygonContaining(query);
return polygon < 0 ? 1.0f : at(polygon).depthAt(query);
}
PathFindingShape::LinkPolygonIndices::LinkPolygonIndices() {
Common::fill(_points, _points + kPointsPerPolygon, LinkIndex(-1, -1));
}
static Pair<int32, int32> orderPoints(const Polygon &polygon, int32 point1, int32 point2) {
if (point1 > point2) {
int32 tmp = point1;
point1 = point2;
point2 = tmp;
}
const int32 maxPointI = polygon._points.size() - 1;
if (point1 == 0 && point2 == maxPointI)
return { maxPointI, 0 };
return { point1, point2 };
}
void PathFindingShape::setupLinks() {
// just a heuristic, each polygon will be attached to at least one other
_linkPoints.reserve(polygonCount() * 3);
_linkIndices.resize(polygonCount());
_targetQuads.resize(polygonCount() * kPointsPerPolygon);
Common::fill(_targetQuads.begin(), _targetQuads.end(), -1);
Pair<uint, uint> sharedPoints[2];
for (uint outerI = 0; outerI < polygonCount(); outerI++) {
const auto outer = at(outerI);
for (uint innerI = outerI + 1; innerI < polygonCount(); innerI++) {
const auto inner = at(innerI);
uint sharedPointCount = outer.findSharedPoints(inner, { sharedPoints, 2 });
if (sharedPointCount > 0)
setupLinkPoint(outer, inner, sharedPoints[0]);
if (sharedPointCount > 1) {
auto outerPoints = orderPoints(outer, sharedPoints[0].first, sharedPoints[1].first);
auto innerPoints = orderPoints(inner, sharedPoints[0].second, sharedPoints[1].second);
setupLinkEdge(outer, inner, outerPoints, innerPoints);
setupLinkPoint(outer, inner, sharedPoints[1]);
}
}
}
}
void PathFindingShape::setupLinkPoint(
const PathFindingPolygon &outer,
const PathFindingPolygon &inner,
PathFindingPolygon::SharedPoint pointI) {
auto &outerLink = _linkIndices[outer._index]._points[pointI.first];
auto &innerLink = _linkIndices[inner._index]._points[pointI.second];
if (outerLink.first < 0) {
outerLink.first = _linkPoints.size();
_linkPoints.push_back(outer._points[pointI.first]);
}
innerLink.first = outerLink.first;
}
void PathFindingShape::setupLinkEdge(
const PathFindingPolygon &outer,
const PathFindingPolygon &inner,
PathFindingShape::LinkIndex outerP,
PathFindingShape::LinkIndex innerP) {
_targetQuads[outer._index * kPointsPerPolygon + outerP.first] = inner._index;
_targetQuads[inner._index * kPointsPerPolygon + innerP.first] = outer._index;
auto &outerLink = _linkIndices[outer._index]._points[outerP.first];
auto &innerLink = _linkIndices[inner._index]._points[innerP.first];
if (outerLink.second < 0) {
outerLink.second = _linkPoints.size();
_linkPoints.push_back((outer._points[outerP.first] + outer._points[outerP.second]) / 2);
}
innerLink.second = outerLink.second;
}
void PathFindingShape::initializeFloydWarshall() {
_distanceMatrix.resize(_linkPoints.size() * _linkPoints.size());
_previousTarget.resize(_linkPoints.size() * _linkPoints.size());
Common::fill(_distanceMatrix.begin(), _distanceMatrix.end(), UINT_MAX);
Common::fill(_previousTarget.begin(), _previousTarget.end(), -1);
// every linkpoint is the shortest path to itself
for (uint i = 0; i < _linkPoints.size(); i++) {
_distanceMatrix[i * _linkPoints.size() + i] = 0;
_previousTarget[i * _linkPoints.size() + i] = i;
}
// every linkpoint to linkpoint within the same polygon *is* the shortest path
// between them. Therefore these are our initial paths for Floyd-Warshall
for (const auto &linkPolygon : _linkIndices) {
for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
LinkIndex linkFrom = linkPolygon._points[i / 2];
int32 linkFromI = (i % 2) ? linkFrom.second : linkFrom.first;
if (linkFromI < 0)
continue;
for (uint j = i + 1; j < 2 * kPointsPerPolygon; j++) {
LinkIndex linkTo = linkPolygon._points[j / 2];
int32 linkToI = (j % 2) ? linkTo.second : linkTo.first;
if (linkToI >= 0) {
const int32 linkFromFullI = linkFromI * _linkPoints.size() + linkToI;
const int32 linkToFullI = linkToI * _linkPoints.size() + linkFromI;
_distanceMatrix[linkFromFullI] = _distanceMatrix[linkToFullI] =
(uint)sqrtf(_linkPoints[linkFromI].sqrDist(_linkPoints[linkToI]) + 0.5f);
_previousTarget[linkFromFullI] = linkFromI;
_previousTarget[linkToFullI] = linkToI;
}
}
}
}
}
void PathFindingShape::calculateFloydWarshall() {
const auto distance = [&] (uint a, uint b) -> uint &{
return _distanceMatrix[a * _linkPoints.size() + b];
};
const auto previousTarget = [&] (uint a, uint b) -> int32 &{
return _previousTarget[a * _linkPoints.size() + b];
};
for (uint over = 0; over < _linkPoints.size(); over++) {
for (uint from = 0; from < _linkPoints.size(); from++) {
for (uint to = 0; to < _linkPoints.size(); to++) {
if (distance(from, over) != UINT_MAX && distance(over, to) != UINT_MAX &&
distance(from, over) + distance(over, to) < distance(from, to)) {
distance(from, to) = distance(from, over) + distance(over, to);
previousTarget(from, to) = previousTarget(over, to);
}
}
}
}
// in the game all floors should be fully connected
assert(find(_previousTarget.begin(), _previousTarget.end(), -1) == _previousTarget.end());
}
bool PathFindingShape::findPath(Point from, Point to_, Stack<Point> &path) const {
Point to = to_; // we might want to correct it
path.clear();
int32 fromContaining = polygonContaining(from);
if (fromContaining < 0)
return false;
int32 toContaining = polygonContaining(to);
if (toContaining < 0) {
to = closestPointTo(to, toContaining);
assert(toContaining >= 0);
}
//if (canGoStraightThrough(from, to, fromContaining, toContaining)) {
if (canGoStraightThrough(from, to, fromContaining, toContaining)) {
path.push(to);
return true;
}
floydWarshallPath(from, to, fromContaining, toContaining, path);
return true;
}
int32 PathFindingShape::edgeTarget(uint polygonI, uint pointI) const {
assert(polygonI < polygonCount() && pointI < kPointsPerPolygon);
uint fullI = polygonI * kPointsPerPolygon + pointI;
return _targetQuads[fullI];
}
bool PathFindingShape::canGoStraightThrough(
Point from, Point to,
int32 fromContainingI, int32 toContainingI) const {
int32 lastContainingI = -1;
while (fromContainingI != toContainingI) {
auto toContaining = at(toContainingI);
bool foundPortal = false;
for (uint i = 0; i < toContaining._points.size(); i++) {
int32 target = edgeTarget((uint)toContainingI, i);
if (target < 0 || target == lastContainingI)
continue;
if (toContaining.intersectsEdge(i, from, to)) {
foundPortal = true;
lastContainingI = toContainingI;
toContainingI = target;
break;
}
}
if (!foundPortal)
return false;
}
return true;
}
void PathFindingShape::floydWarshallPath(
Point from, Point to,
int32 fromContaining, int32 toContaining,
Stack<Point> &path) const {
path.push(to);
// first find the tuple of link points to be used
uint fromLink = UINT_MAX, toLink = UINT_MAX, bestDistance = UINT_MAX;
const auto &fromIndices = _linkIndices[fromContaining];
const auto &toIndices = _linkIndices[toContaining];
for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
const auto &curFromPoint = fromIndices._points[i / 2];
int32 curFromLink = (i % 2) ? curFromPoint.second : curFromPoint.first;
if (curFromLink < 0)
continue;
uint curFromDistance = (uint)sqrtf(from.sqrDist(_linkPoints[curFromLink]) + 0.5f);
for (uint j = 0; j < 2 * kPointsPerPolygon; j++) {
const auto &curToPoint = toIndices._points[j / 2];
int32 curToLink = (j % 2) ? curToPoint.second : curToPoint.first;
if (curToLink < 0)
continue;
uint totalDistance =
curFromDistance +
_distanceMatrix[curFromLink * _linkPoints.size() + curToLink] +
(uint)sqrtf(to.sqrDist(_linkPoints[curToLink]) + 0.5f);
if (totalDistance < bestDistance) {
bestDistance = totalDistance;
fromLink = curFromLink;
toLink = curToLink;
}
}
}
assert(fromLink != UINT_MAX && toLink != UINT_MAX);
// then walk the matrix back to reconstruct the path
while (fromLink != toLink) {
path.push(_linkPoints[toLink]);
toLink = _previousTarget[fromLink * _linkPoints.size() + toLink];
assert(toLink < _linkPoints.size());
}
path.push(_linkPoints[fromLink]);
}
bool PathFindingShape::findEvadeTarget(
Point centerTarget,
float depthScale, float minDistSqr,
Point &evadeTarget) const {
for (float tryDistBase = 60; tryDistBase < 250; tryDistBase += 10) {
for (int tryAngleI = 0; tryAngleI < 6; tryAngleI++) {
const float tryAngle = tryAngleI / 3.0f * M_PI + deg2rad(30.0f);
const float tryDist = tryDistBase * depthScale;
const Point tryPos = evadeTarget + Point(
(int16)(cosf(tryAngle) * tryDist),
(int16)(sinf(tryAngle) * tryDist));
if (contains(tryPos) && tryPos.sqrDist(centerTarget) > minDistSqr) {
evadeTarget = tryPos;
return true;
}
}
}
return false;
}
FloorColorShape::FloorColorShape() {}
FloorColorShape::FloorColorShape(ReadStream &stream) {
auto polygonCount = stream.readUint16LE();
_polygons.reserve(polygonCount);
_points.reserve(polygonCount * kPointsPerPolygon);
_pointColors.reserve(polygonCount * kPointsPerPolygon);
for (int i = 0; i < polygonCount; i++) {
for (uint j = 0; j < kPointsPerPolygon; j++)
_points.push_back(readPoint(stream));
// For the colors the alpha channel is not used so we store the brightness into it instead
// Brightness is store 0-100, but we can scale it up here
int firstColorI = _pointColors.size();
_pointColors.resize(_pointColors.size() + kPointsPerPolygon);
for (uint j = 0; j < kPointsPerPolygon; j++)
_pointColors[firstColorI + j].a = (uint8)MIN(255, stream.readByte() * 255 / 100);
for (uint j = 0; j < kPointsPerPolygon; j++) {
_pointColors[firstColorI + j].r = stream.readByte();
_pointColors[firstColorI + j].g = stream.readByte();
_pointColors[firstColorI + j].b = stream.readByte();
stream.readByte(); // second alpha value is ignored
}
stream.readByte(); // unused byte per polygon
uint pointCount = addPolygon(kPointsPerPolygon);
assert(pointCount <= kPointsPerPolygon);
_pointColors.resize(_points.size());
}
}
FloorColorPolygon FloorColorShape::at(uint index) const {
auto range = _polygons[index];
FloorColorPolygon p;
p._index = index;
p._points = Span<const Point>(_points.data() + range.first, range.second);
p._pointColors = Span<const Color>(_pointColors.data() + range.first, range.second);
return p;
}
OptionalColor FloorColorShape::colorAt(Point query) const {
int32 polygon = polygonContaining(query);
return polygon < 0
? OptionalColor(false, kClear)
: OptionalColor(true, at(polygon).colorAt(query));
}
}