void TerrainPageSurfaceLayer::fillImage(const TerrainPageGeometry& geometry, Image& image, unsigned int channel) const { SegmentVector validSegments = geometry.getValidSegments(); for (SegmentVector::const_iterator I = validSegments.begin(); I != validSegments.end(); ++I) { Mercator::Segment* segment = I->segment; if (mShader.checkIntersect(*segment)) { Mercator::Surface* surface = getSurfaceForSegment(segment); if (surface && surface->isValid()) { Image::ImageBuffer* textureBitmap = new Image::ImageBuffer(segment->getResolution(), 1); auto srcPtr = surface->getData(); auto dataPtr = textureBitmap->getData(); auto segmentSize = segment->getSize(); for (int i = 0; i < segment->getResolution(); ++i) { for (int j = 0; j < segment->getResolution(); ++j) { //interpolate four samples to get the fragment coverage *dataPtr = (unsigned char)((srcPtr[(i * segmentSize) + j] + srcPtr[(i * segmentSize) + j + 1] + srcPtr[((i + 1) * segmentSize) + j] + srcPtr[((i + 1) * segmentSize) + j + 1]) / 4); dataPtr++; } } WFImage sourceImage(textureBitmap); image.blit(sourceImage, channel, ((int)I->index.x() * segment->getResolution()), ((mTerrainPageSurface.getNumberOfSegmentsPerAxis() - (int)I->index.y() - 1) * segment->getResolution())); } } } }
void TerrainPageSurfaceLayer::populate(const TerrainPageGeometry& geometry) { const SegmentVector validSegments = geometry.getValidSegments(); for (SegmentVector::const_iterator I = validSegments.begin(); I != validSegments.end(); ++I) { #if 0 //the current Mercator code works such that whenever an Area is added to Terrain, _all_ surfaces for the affected segments are invalidated, thus requiering a total repopulation of the segment //If however that code was changed to only invalidate the affected surface the code below would be very much handy Mercator::Surface* surface(getSurfaceForSegment(I->segment)); if (surface) { surface->populate(); } #else Mercator::Segment* segment(I->segment); if (!segment->isValid()) { segment->populate(); } Mercator::Segment::Surfacestore::iterator I2(segment->getSurfaces().find(mSurfaceIndex)); if (I2 == segment->getSurfaces().end()) { //the segment doesn't contain this surface yet, lets add it if (mShader.checkIntersect(*segment)) { S_LOG_VERBOSE("Adding new surface with id " << mSurfaceIndex << " to segment at x: " << segment->getXRef() << " y: " << segment->getYRef()); Mercator::Segment::Surfacestore & sss = segment->getSurfaces(); sss[mSurfaceIndex] = mShader.newSurface(*segment); } } //NOTE: we have to repopulate all surfaces mainly to get the foliage to work. segment->populateSurfaces(); #endif } }
TEST(Buckets, RasterBucketMaskEmpty) { RasterBucket bucket{ nullptr }; bucket.setMask({}); EXPECT_EQ((std::vector<RasterLayoutVertex>{}), bucket.vertices.vector()); EXPECT_EQ((std::vector<uint16_t>{}), bucket.indices.vector()); SegmentVector<RasterAttributes> expectedSegments; expectedSegments.emplace_back(0, 0, 0, 0); EXPECT_EQ(expectedSegments, bucket.segments); }
EyeCalibration::SegmentVector EyeCalibration::getEdges(CalibrationPointVector processingPoints) { SegmentVector edges; for (unsigned int i = 0; i < processingPoints.size(); i++) { for (unsigned int j = i + 1; j < processingPoints.size(); j++) { edges.push_back( Segment(processingPoints.at(i), processingPoints.at(j)) ); } } return edges; }
bool TerrainPageSurfaceLayer::intersects(const TerrainPageGeometry& geometry) const { const SegmentVector validSegments = geometry.getValidSegments(); //check if at least one surface intersects for (SegmentVector::const_iterator I = validSegments.begin(); I != validSegments.end(); ++I) { if (mShader.checkIntersect(*I->segment)) { return true; } } return false; }
void TerrainPageSurfaceLayer::fillImage(const TerrainPageGeometry& geometry, Image& image, unsigned int channel) const { SegmentVector validSegments = geometry.getValidSegments(); for (SegmentVector::const_iterator I = validSegments.begin(); I != validSegments.end(); ++I) { if (mShader.checkIntersect(*I->segment)) { Mercator::Surface* surface = getSurfaceForSegment(I->segment); if (surface && surface->isValid()) { WFImage sourceImage(new Image::ImageBuffer(65, 1, surface->getData())); //We need to adjust the position of the x index by one because there's a one pixel offset when converting between the Mercator Segments and the Ogre page. image.blit(sourceImage, channel, ((int)I->index.x() * 64) + 1, ((mTerrainPageSurface.getNumberOfSegmentsPerAxis() - (int)I->index.y() - 1) * 64)); } } } }
const SegmentVector TerrainPageGeometry::getValidSegments() const { SegmentVector validSegments; for (SegmentRefStore::const_iterator I = mLocalSegments.begin(); I != mLocalSegments.end(); ++I) { for (SegmentRefColumn::const_iterator J = I->second.begin(); J != I->second.end(); ++J) { Mercator::Segment& segment = J->second->getMercatorSegment(); PageSegment pageSegment; pageSegment.index = TerrainPosition(I->first, J->first); pageSegment.segment = &segment; validSegments.push_back(pageSegment); } } return validSegments; }
bool EyeCalibration::boundingPointsOfHull(SegmentVector &contour, CalibrationPointVector &result) { for (unsigned int i = 0; i < contour.size(); i++) { Segment edge = contour[i]; CalibrationPoint A = edge.p1(); CalibrationPoint B = edge.p2(); bool foundA = false; bool foundB = false; for (unsigned int k = 0; k < result.size(); k++) { if (result[k].equal(A)) foundA = true; if (result[k].equal(B)) foundB = true; if (foundA && foundB) break; } if (!foundA) result.push_back(A); if (!foundB) result.push_back(B); } return true; }
TEST(Buckets, RasterBucketMaskTwoChildren) { RasterBucket bucket{ nullptr }; bucket.setMask( { CanonicalTileID{ 1, 0, 0 }, CanonicalTileID{ 1, 1, 1 } }); EXPECT_EQ( (std::vector<RasterLayoutVertex>{ // 1/0/1 RasterProgram::layoutVertex({ 0, 0 }, { 0, 0 }), RasterProgram::layoutVertex({ 4096, 0 }, { 4096, 0 }), RasterProgram::layoutVertex({ 0, 4096 }, { 0, 4096 }), RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), // 1/1/1 RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), RasterProgram::layoutVertex({ 8192, 4096 }, { 8192, 4096 }), RasterProgram::layoutVertex({ 4096, 8192 }, { 4096, 8192 }), RasterProgram::layoutVertex({ 8192, 8192 }, { 8192, 8192 }), }), bucket.vertices.vector()); EXPECT_EQ( (std::vector<uint16_t>{ // 1/0/1 0, 1, 2, 1, 2, 3, // 1/1/1 4, 5, 6, 5, 6, 7, }), bucket.indices.vector()); SegmentVector<RasterAttributes> expectedSegments; expectedSegments.emplace_back(0, 0, 8, 12); EXPECT_EQ(expectedSegments, bucket.segments); }
bool EyeCalibration::calibrate() { char buf[512]; // Print starting message EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog("Starting Calibration\n"); // Create buffer if (this->eyeVectorArray == NULL) this->eyeVectorArray = (float*) malloc(sizeof(float) * ClientHandler::getDikablisViewingSize()); sprintf_s(buf, "Creating buffer of size %d bytes\n", sizeof(float) * ClientHandler::getDikablisViewingSize()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); // Print the number of points used in the calibration sprintf_s(buf, "A total of %d points was collected.\n", calibrationPoints.size()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); if (calibrationPoints.size() < 4) { EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog("Not enough points to calculate calibration.\n"); return false; } // Min/Max coordinates of the polygon CalibrationPoint minX = calibrationPoints.at(0), minY = calibrationPoints.at(0), maxX = calibrationPoints.at(0), maxY = calibrationPoints.at(0); // Print each points information and get the min and max for (unsigned int i = 0; i < calibrationPoints.size(); i++) { CalibrationPoint point = calibrationPoints.at(i); osg::Vec3 ray = point.ray(); // Print info sprintf_s(buf, "Point %d (%d, %d) has a value of (%f, %f, %f)\n", i + 1, point.x(), point.y(), ray.x(), ray.y(), ray.z()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); if (minX.x() > point.x()) minX = point; if (minY.y() > point.y()) minY = point; if (maxX.x() < point.x()) maxX = point; if (maxY.y() < point.y()) maxY = point; } // Print Bounding Info { sprintf_s(buf, "Point with min X value (%d, %d)\n", minX.x(), minX.y()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); sprintf_s(buf, "Point with min Y value (%d, %d)\n", minY.x(), minY.y()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); sprintf_s(buf, "Point with max X value (%d, %d)\n", maxX.x(), maxX.y()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); sprintf_s(buf, "Point with max Y value (%d, %d)\n", maxY.x(), maxY.y()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); // Get center this->center_x = (maxX.x() - minX.x())/2; this->center_y = (maxY.y() - minY.y())/2; sprintf_s(buf, "Center point is (%d, %d)\n", center_x, center_y); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); } // Get Convex Hull CalibrationPointVector boundingPoints; SegmentVector convexHull = calculateConvexHull(calibrationPoints); // Get the bounding points of the convex hull boundingPointsOfHull(convexHull, boundingPoints); // Print each segment information for (unsigned int i = 0; i < convexHull.size(); i++) { Segment segment = convexHull.at(i); sprintf_s(buf, "Edge %d: From (%d, %d) to (%d, %d)\n", i + 1, segment.x1(), segment.y1(), segment.x2(), segment.y2()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); } // Resort the bounding points sort(boundingPoints, center_x, center_y); // Triangulate CalibrationPointVector traingles; triangulate(boundingPoints, traingles); // Check if the triangulation is valid if (traingles.size() % 3 != 0) { EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog("Error: Triangulating polygon.\n"); return false; } // Get the remaining inner points of the convex hull CalibrationPointVector innerPoints; for (unsigned int i = 0; i < calibrationPoints.size(); i++) { CalibrationPoint point = calibrationPoints[i]; bool found = false; for (unsigned int k = 0; k < boundingPoints.size(); k++) { if (point.equal(boundingPoints[k])) { found = true; break; } } if (!found) innerPoints.push_back(point); } unsigned int traingleCount = traingles.size() / 3; for (unsigned int i = 0; i < innerPoints.size(); i++) { CalibrationPoint P = innerPoints[i]; for (unsigned int k = 0; k < traingleCount; k++) { CalibrationPoint A = traingles[3*k + 0]; CalibrationPoint B = traingles[3*k + 1]; CalibrationPoint C = traingles[3*k + 2]; if (insideTriangle(A,B,C,P)) { traingles.erase(traingles.begin()+3*k, traingles.begin()+3*k+3); CalibrationPoint center, P0; // Triangle 1 CalibrationPointVector tri1; tri1.push_back(A); tri1.push_back(B); tri1.push_back(P); P0 = A + (B - A)/2.f; center = P + (P0 - P)*(3.f/2.f); sort(tri1, center.x(), center.y()); // Triangle 2 CalibrationPointVector tri2; tri2.push_back(A); tri2.push_back(C); tri2.push_back(P); P0 = A + (C - A)/2.f; center = P + (P0 - P)*(3.f/2.f); sort(tri2, center.x(), center.y()); // Triangle 3 CalibrationPointVector tri3; tri3.push_back(B); tri3.push_back(C); tri3.push_back(P); P0 = B + (C - B)/2.f; center = P + (P0 - P)*(3.f/2.f); sort(tri3, center.x(), center.y()); traingles.insert(traingles.end(), tri1.begin(), tri1.end()); traingles.insert(traingles.end(), tri2.begin(), tri2.end()); traingles.insert(traingles.end(), tri3.begin(), tri3.end()); break; } } } if (traingles.size() % 3 != 0) { EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog("Error: Triangulating inner points in polygon.\n"); return false; } traingleCount = traingles.size() / 3; // Flip edges of the triangles for (int k = traingleCount - 1; k > 0 ; k--) { CalibrationPointVector tri1; tri1.push_back(traingles[3*k + 0]); tri1.push_back(traingles[3*k + 1]); tri1.push_back(traingles[3*k + 2]); for (int i = k - 1; i >= 0; i--) { CalibrationPointVector tri2; tri2.push_back(traingles[3*i + 0]); tri2.push_back(traingles[3*i + 1]); tri2.push_back(traingles[3*i + 2]); // Get shared edge if exists int shared = 0; CalibrationPointVector sharedPoints; CalibrationPoint nonSharedPoint; for (unsigned int idx1 = 0; idx1 < 3; idx1++) { for (unsigned int idx2 = 0; idx2 < 3; idx2++) { if (tri2[idx2].equal(tri1[idx1])) { sharedPoints.push_back(tri1[idx1]); shared++; } else { nonSharedPoint = tri2[idx2]; } } } if (shared == 2) { if (isInCircumCircle(nonSharedPoint, tri1)) { sprintf_s(buf, "Point %d - (%d, %d): In circle of triangle (%d, %d), (%d, %d), (%d, %d)\n", i + 1, nonSharedPoint.x(), nonSharedPoint.y(), tri1[0].x(), tri1[0].y(), tri1[1].x(), tri1[1].y(), tri1[2].x(), tri1[2].y()); EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog(buf); // Sort tri1 for (unsigned int idx = 0; idx < 3; idx++) { if (tri1[idx].equal(sharedPoints[0]) || tri1[idx].equal(sharedPoints[1])) { CalibrationPoint shared = tri1[idx]; tri1.erase(tri1.begin()+idx); tri1.insert(tri1.begin(), shared); } } // Sort tri2 for (unsigned int idx = 0; idx < 3; idx++) { if (tri2[idx].equal(sharedPoints[0]) || tri2[idx].equal(sharedPoints[1])) { CalibrationPoint shared = tri2[idx]; tri2.erase(tri2.begin()+idx); tri2.insert(tri2.begin(), shared); } } // Clean up old triangles traingles.erase(traingles.begin()+3*k, traingles.begin()+3*k+3); traingles.erase(traingles.begin()+3*i, traingles.begin()+3*i+3); CalibrationPoint center, P0; // New Triangle 1 CalibrationPointVector new_tri1; new_tri1.push_back(tri1[0]); new_tri1.push_back(tri1[2]); new_tri1.push_back(tri2[2]); P0 = tri1[0] + (tri2[2] - tri1[0])/2.f; center = tri1[2] + (P0 - tri1[2])*(3.f/2.f); sort(new_tri1, center.x(), center.y()); // New Triangle 2 CalibrationPointVector new_tri2; new_tri2.push_back(tri1[1]); new_tri2.push_back(tri1[2]); new_tri2.push_back(tri2[2]); P0 = tri1[1] + (tri2[2] - tri1[1])/2.f; center = tri1[2] + (P0 - tri1[2])*(3.f/2.f); sort(new_tri2, center.x(), center.y()); traingles.insert(traingles.end(), new_tri1.begin(), new_tri1.end()); traingles.insert(traingles.end(), new_tri2.begin(), new_tri2.end()); break; } } } } // Draw triangles for (unsigned int j = 0; j < (viewingHeight+2*viewingMargin); j++) { for (unsigned int i = 0; i < (viewingWidth+2*viewingMargin); i++) { CalibrationPoint P(i - viewingMargin, j - viewingMargin, osg::Vec3(0.f, 0.f, 0.f)); for (unsigned int k = 0; k < traingleCount; k++) { CalibrationPoint A = traingles[3*k + 0]; CalibrationPoint B = traingles[3*k + 1]; CalibrationPoint C = traingles[3*k + 2]; if (insideTriangle(A,B,C,P)) { osg::Vec3 ray; float det = A.x()*B.y()-B.x()*A.y()+B.x()*C.y()-C.x()*B.y()+C.x()*A.y()-A.x()*C.y(); osg::Vec3 a = (A.ray()*(B.y()-C.y())+B.ray()*(C.y()-A.y())+C.ray()*(A.y()-B.y())) / det; osg::Vec3 b = (A.ray()*(C.x()-B.x())+B.ray()*(A.x()-C.x())+C.ray()*(B.x()-A.x())) / det; osg::Vec3 c = (A.ray()*(B.x()*C.y()-C.x()*B.y())+B.ray()*(C.x()*A.y()-A.x()*C.y())+C.ray()*(A.x()*B.y()-B.x()*A.y())) / det; ray = a*P.x()+b*P.y()+c; unsigned long location = 3*(j*(viewingWidth+2*viewingMargin) + i); this->eyeVectorArray[location + 0] = ray.x(); this->eyeVectorArray[location + 1] = ray.y(); this->eyeVectorArray[location + 2] = ray.z(); break; } } } } // Draw Outside for (unsigned int j = 0; j < (viewingHeight+2*viewingMargin); j++) { for (unsigned int i = 0; i < (viewingWidth+2*viewingMargin); i++) { CalibrationPoint P(i - viewingMargin, j - viewingMargin, osg::Vec3(0.f, 0.f, 0.f)); bool inside = false; for (unsigned int k = 0; k < traingleCount; k++) { CalibrationPoint A = traingles[3*k + 0]; CalibrationPoint B = traingles[3*k + 1]; CalibrationPoint C = traingles[3*k + 2]; if (insideTriangle(A,B,C,P)) { inside = true; break; } } if (!inside) { CalibrationPoint closest; float min_distance = -1; for (unsigned int k = 0; k < convexHull.size(); k++) { CalibrationPoint A = convexHull[k].p1(); CalibrationPoint B = convexHull[k].p2(); CalibrationPoint P0 = getClosestPoint(A, B, P, true); float diff_x = P0.x() - P.x(); float diff_y = P0.y() - P.y(); float distance = diff_x*diff_x + diff_y*diff_y; if (min_distance < 0 || min_distance > distance) { min_distance = distance; closest = P0; } } osg::Vec3 ray = closest.ray(); unsigned long location = 3*(j*(viewingWidth+2*viewingMargin) + i); this->eyeVectorArray[location + 0] = ray.x(); this->eyeVectorArray[location + 1] = ray.y(); this->eyeVectorArray[location + 2] = ray.z(); } } } // Testing #if 0 // Draw points for (unsigned int c = 0; c < calibrationPoints.size(); c++) { CalibrationPoint point = calibrationPoints[c]; for (int j = -10; j < 10; j++) { for (int i = -10; i < 10; i++) { int x = point.x() + i + viewingMargin; int y = point.y() + j + viewingMargin; if (x >= 0 && x < (int)(viewingWidth+2*viewingMargin) && y >= 0 && y < (int)(viewingHeight+2*viewingMargin)) { unsigned long location = 3*(y*(viewingWidth+2*viewingMargin) + x); this->eyeVectorArray[location + 0] = 255; this->eyeVectorArray[location + 1] = 0; this->eyeVectorArray[location + 2] = 0; } } } } // Draw bounding points for (unsigned int c = 0; c < boundingPoints.size(); c++) { CalibrationPoint point = boundingPoints[c]; for (int j = -10; j < 10; j++) { for (int i = -10; i < 10; i++) { int x = point.x() + i + viewingMargin; int y = point.y() + j + viewingMargin; if (x >= 0 && x < (int)(viewingWidth+2*viewingMargin) && y >= 0 && y < (int)(viewingHeight+2*viewingMargin)) { unsigned long location = 3*(y*(viewingWidth+2*viewingMargin) + x); this->eyeVectorArray[location + 0] = 0; this->eyeVectorArray[location + 1] = 255; this->eyeVectorArray[location + 2] = 0; } } } } #endif saveRayMap(); ClientHandler* client = AppData::getInstance()->getClient(); if (client) { client->setRayCalibration(this->eyeVectorArray); client->setHeadId(this->rbHeadId); } else { EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog("Failed: Unable to find client.\n"); return false; } EyeCalibrationWizardFormController::getInstance()->calibrationOutputLog("Done\n"); return true; }
EyeCalibration::SegmentVector EyeCalibration::calculateConvexHull(CalibrationPointVector processingPoints) { // Edges SegmentVector segments; if (processingPoints.size() < 3) return segments; CalibrationPoint minX = processingPoints.at(0), minY = processingPoints.at(0), maxX = processingPoints.at(0), maxY = processingPoints.at(0); for (unsigned int i = 0; i < processingPoints.size(); i++) { CalibrationPoint point = processingPoints.at(i); osg::Vec3 ray = point.ray(); if (minX.x() > point.x()) minX = point; if (minY.y() > point.y()) minY = point; if (maxX.x() < point.x()) maxX = point; if (maxY.y() < point.y()) maxY = point; } // Get center int center_x = (maxX.x() - minX.x())/2; int center_y = (maxY.y() - minY.y())/2; // Ordered Point CalibrationPointVector orderedPoints(processingPoints); // Sort Points sort(orderedPoints, center_x, center_y); // Create segments for (unsigned int i = 0; i < orderedPoints.size(); i++) { CalibrationPoint from = orderedPoints.at(i); for (unsigned int k = 0; k < orderedPoints.size(); k++) { if (i == k) continue; CalibrationPoint to = orderedPoints.at(k); Segment segment(from, to); segments.push_back(segment); } } unsigned int i = 0; unsigned int j = 0; while ( i < segments.size() ) { //ProcessingPoints will be the points that are not in the current segment CalibrationPointVector processingPoints(orderedPoints); Segment segment = segments.at(i); //this loop prepares the ProcessingPoints list for each segment while ( j < processingPoints.size() ) { CalibrationPoint point = processingPoints.at(j); if((segment.x1() == point.x() && segment.y1() == point.y()) || (segment.x2() == point.x() && segment.y2() == point.y())) { //eliminating the points that are already in the current segment... //we don't need them processingPoints.erase(processingPoints.begin()+j); j = 0; } else { j++; } } //checking if the current segment is an edge or notBy calling isEdge function if( !isEdge(processingPoints, segments.at(i)) ) { segments.erase(segments.begin()+i); i = 0; } else { i++; } } return segments; }
TEST(Buckets, RasterBucketMaskComplex) { RasterBucket bucket{ nullptr }; bucket.setMask( { CanonicalTileID{ 1, 0, 1 }, CanonicalTileID{ 1, 1, 0 }, CanonicalTileID{ 2, 2, 3 }, CanonicalTileID{ 2, 3, 2 }, CanonicalTileID{ 3, 6, 7 }, CanonicalTileID{ 3, 7, 6 } }); EXPECT_EQ( (std::vector<RasterLayoutVertex>{ // 1/0/1 RasterProgram::layoutVertex({ 0, 4096 }, { 0, 4096 }), RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), RasterProgram::layoutVertex({ 0, 8192 }, { 0, 8192 }), RasterProgram::layoutVertex({ 4096, 8192 }, { 4096, 8192 }), // 1/1/0 RasterProgram::layoutVertex({ 4096, 0 }, { 4096, 0 }), RasterProgram::layoutVertex({ 8192, 0 }, { 8192, 0 }), RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), RasterProgram::layoutVertex({ 8192, 4096 }, { 8192, 4096 }), // 2/2/3 RasterProgram::layoutVertex({ 4096, 6144 }, { 4096, 6144 }), RasterProgram::layoutVertex({ 6144, 6144 }, { 6144, 6144 }), RasterProgram::layoutVertex({ 4096, 8192 }, { 4096, 8192 }), RasterProgram::layoutVertex({ 6144, 8192 }, { 6144, 8192 }), // 2/3/2 RasterProgram::layoutVertex({ 6144, 4096 }, { 6144, 4096 }), RasterProgram::layoutVertex({ 8192, 4096 }, { 8192, 4096 }), RasterProgram::layoutVertex({ 6144, 6144 }, { 6144, 6144 }), RasterProgram::layoutVertex({ 8192, 6144 }, { 8192, 6144 }), // 3/6/7 RasterProgram::layoutVertex({ 6144, 7168 }, { 6144, 7168 }), RasterProgram::layoutVertex({ 7168, 7168 }, { 7168, 7168 }), RasterProgram::layoutVertex({ 6144, 8192 }, { 6144, 8192 }), RasterProgram::layoutVertex({ 7168, 8192 }, { 7168, 8192 }), // 3/7/6 RasterProgram::layoutVertex({ 7168, 6144 }, { 7168, 6144 }), RasterProgram::layoutVertex({ 8192, 6144 }, { 8192, 6144 }), RasterProgram::layoutVertex({ 7168, 7168 }, { 7168, 7168 }), RasterProgram::layoutVertex({ 8192, 7168 }, { 8192, 7168 }), }), bucket.vertices.vector()); EXPECT_EQ( (std::vector<uint16_t>{ // 1/0/1 0, 1, 2, 1, 2, 3, // 1/1/0 4, 5, 6, 5, 6, 7, // 2/2/3 8, 9, 10, 9, 10, 11, // 2/3/2 12, 13, 14, 13, 14, 15, // 3/6/7 16, 17, 18, 17, 18, 19, // 3/7/6 20, 21, 22, 21, 22, 23, }), bucket.indices.vector()); SegmentVector<RasterAttributes> expectedSegments; expectedSegments.emplace_back(0, 0, 24, 36); EXPECT_EQ(expectedSegments, bucket.segments); }