// Return the maximum buffer size (in bytes) required to process the // specified collection of market 'indices'. //.. // Before showing the implementation of 'processIndices', where the most // interesting use of our managed allocator takes place, we show the site of // the call to 'processIndices'. // // First, assume that we have been given an 'IndexCollection' that has been // populated with one or more 'IndexAttributes': //.. // IndexCollection indices; // assume populated //.. // Next, we calculate the size of the buffer that is needed, allocate the // memory for the buffer from the default allocator, create our concrete // managed allocator (namely, an instance of 'my_BufferAllocator'), and call // 'processIndices': //.. // const int bufferSize = calculateMaxBufferSize(indices); // // bslma::Allocator *allocator = bslma::Default::defaultAllocator(); // char *buffer = static_cast<char *>(allocator->allocate(bufferSize)); // // my_BufferAllocator bufferAllocator(buffer, bufferSize); // // processIndices(&bufferAllocator, indices); //.. // Next, we show the implementation of 'processIndices', within which we // iterate over the market 'indices' that are passed to it: //.. static void processIndices(bdlma::ManagedAllocator *managedAllocator, const IndexCollection& indices) // Process the specified market 'indices' using the specified // 'managedAllocator' to supply memory. { for (IndexCollection::const_iterator citer = indices.begin(); citer != indices.end(); ++citer) { //.. // For each index, the 'SecurityCollection' comprising that index is created. // All of the memory needs of the 'SecurityCollection' are provided by the // 'managedAllocator'. Note that even the memory for the footprint of the // collection comes from the 'managedAllocator': //.. SecurityCollection *securities = new (managedAllocator->allocate(sizeof(SecurityCollection))) SecurityCollection(managedAllocator); //.. // Next, we call 'loadIndex' to populate 'securities', followed by the call to // 'processIndex'. 'loadIndex' also uses the 'managedAllocator', the details // of which are not shown here: //.. loadIndex(securities, managedAllocator, *citer); processIndex(*securities, *citer); //.. // After the index is processed, 'release' is called on the managed allocator // making all of the buffer supplied to the allocator at construction available // for reuse: //.. managedAllocator->release(); } //.. // Finally, we let the 'SecurityCollection' used to process the index go out of // scope intentionally without deleting 'securities'. The call to 'release' // renders superfluous the need to call the 'SecurityCollection' destructor as // well as the destructor of the contained 'my_SecurityAttributes' elements. //.. }
std::unique_ptr<GeometricPrimitive> GeometricPrimitive::CreateGeoSphere( float diameter, size_t tessellation, bool rhcoords) { // An undirected edge between two vertices, represented by a pair of indexes into a vertex array. // Becuse this edge is undirected, (a,b) is the same as (b,a). typedef std::pair<uint16_t, uint16_t> UndirectedEdge; // Makes an undirected edge. Rather than overloading comparison operators to give us the (a,b)==(b,a) property, // we'll just ensure that the larger of the two goes first. This'll simplify things greatly. auto makeUndirectedEdge = [](uint16_t a, uint16_t b) { return std::make_pair(max(a, b), min(a, b)); }; // Key: an edge // Value: the index of the vertex which lies midway between the two vertices pointed to by the key value // This map is used to avoid duplicating vertices when subdividing triangles along edges. typedef std::map<UndirectedEdge, uint16_t> EdgeSubdivisionMap; static const XMFLOAT3 OctahedronVertices[] = { // when looking down the negative z-axis (into the screen) XMFLOAT3(0, 1, 0), // 0 top XMFLOAT3(0, 0, -1), // 1 front XMFLOAT3(1, 0, 0), // 2 right XMFLOAT3(0, 0, 1), // 3 back XMFLOAT3(-1, 0, 0), // 4 left XMFLOAT3(0, -1, 0), // 5 bottom }; static const uint16_t OctahedronIndices[] = { 0, 1, 2, // top front-right face 0, 2, 3, // top back-right face 0, 3, 4, // top back-left face 0, 4, 1, // top front-left face 5, 1, 4, // bottom front-left face 5, 4, 3, // bottom back-left face 5, 3, 2, // bottom back-right face 5, 2, 1, // bottom front-right face }; const float radius = diameter / 2.0f; // Start with an octahedron; copy the data into the vertex/index collection. std::vector<XMFLOAT3> vertexPositions(std::begin(OctahedronVertices), std::end(OctahedronVertices)); IndexCollection indices; indices.insert(indices.begin(), std::begin(OctahedronIndices), std::end(OctahedronIndices)); // We know these values by looking at the above index list for the octahedron. Despite the subdivisions that are // about to go on, these values aren't ever going to change because the vertices don't move around in the array. // We'll need these values later on to fix the singularities that show up at the poles. const uint16_t northPoleIndex = 0; const uint16_t southPoleIndex = 5; for (size_t iSubdivision = 0; iSubdivision < tessellation; ++iSubdivision) { assert(indices.size() % 3 == 0); // sanity // We use this to keep track of which edges have already been subdivided. EdgeSubdivisionMap subdividedEdges; // The new index collection after subdivision. IndexCollection newIndices; const size_t triangleCount = indices.size() / 3; for (size_t iTriangle = 0; iTriangle < triangleCount; ++iTriangle) { // For each edge on this triangle, create a new vertex in the middle of that edge. // The winding order of the triangles we output are the same as the winding order of the inputs. // Indices of the vertices making up this triangle uint16_t iv0 = indices[iTriangle * 3 + 0]; uint16_t iv1 = indices[iTriangle * 3 + 1]; uint16_t iv2 = indices[iTriangle * 3 + 2]; // Get the new vertices XMFLOAT3 v01; // vertex on the midpoint of v0 and v1 XMFLOAT3 v12; // ditto v1 and v2 XMFLOAT3 v20; // ditto v2 and v0 uint16_t iv01; // index of v01 uint16_t iv12; // index of v12 uint16_t iv20; // index of v20 // Function that, when given the index of two vertices, creates a new vertex at the midpoint of those vertices. auto divideEdge = [&](uint16_t i0, uint16_t i1, XMFLOAT3& outVertex, uint16_t& outIndex) { const UndirectedEdge edge = makeUndirectedEdge(i0, i1); // Check to see if we've already generated this vertex auto it = subdividedEdges.find(edge); if (it != subdividedEdges.end()) { // We've already generated this vertex before outIndex = it->second; // the index of this vertex outVertex = vertexPositions[outIndex]; // and the vertex itself } else { // Haven't generated this vertex before: so add it now // outVertex = (vertices[i0] + vertices[i1]) / 2 XMStoreFloat3( &outVertex, XMVectorScale( XMVectorAdd(XMLoadFloat3(&vertexPositions[i0]), XMLoadFloat3(&vertexPositions[i1])), 0.5f ) ); outIndex = static_cast<uint16_t>(vertexPositions.size()); CheckIndexOverflow(outIndex); vertexPositions.push_back(outVertex); // Now add it to the map. subdividedEdges.insert(std::make_pair(edge, outIndex)); } }; // Add/get new vertices and their indices divideEdge(iv0, iv1, v01, iv01); divideEdge(iv1, iv2, v12, iv12); divideEdge(iv0, iv2, v20, iv20); // Add the new indices. We have four new triangles from our original one: // v0 // o // /a\ // v20 o---o v01 // /b\c/d\ // v2 o---o---o v1 // v12 const uint16_t indicesToAdd[] = { iv0, iv01, iv20, // a iv20, iv12, iv2, // b iv20, iv01, iv12, // c iv01, iv1, iv12, // d }; newIndices.insert(newIndices.end(), std::begin(indicesToAdd), std::end(indicesToAdd)); } indices = std::move(newIndices); } // Now that we've completed subdivision, fill in the final vertex collection VertexCollection vertices; vertices.reserve(vertexPositions.size()); for (auto it = vertexPositions.begin(); it != vertexPositions.end(); ++it) { auto vertexValue = *it; auto normal = XMVector3Normalize(XMLoadFloat3(&vertexValue)); auto pos = XMVectorScale(normal, radius); XMFLOAT3 normalFloat3; XMStoreFloat3(&normalFloat3, normal); // calculate texture coordinates for this vertex float longitude = atan2(normalFloat3.x, -normalFloat3.z); float latitude = acos(normalFloat3.y); float u = longitude / XM_2PI + 0.5f; float v = latitude / XM_PI; auto texcoord = XMVectorSet(1.0f - u, v, 0.0f, 0.0f); vertices.push_back(VertexPositionNormalTexture(pos, normal, texcoord)); } // There are a couple of fixes to do. One is a texture coordinate wraparound fixup. At some point, there will be // a set of triangles somewhere in the mesh with texture coordinates such that the wraparound across 0.0/1.0 // occurs across that triangle. Eg. when the left hand side of the triangle has a U coordinate of 0.98 and the // right hand side has a U coordinate of 0.0. The intent is that such a triangle should render with a U of 0.98 to // 1.0, not 0.98 to 0.0. If we don't do this fixup, there will be a visible seam across one side of the sphere. // // Luckily this is relatively easy to fix. There is a straight edge which runs down the prime meridian of the // completed sphere. If you imagine the vertices along that edge, they circumscribe a semicircular arc starting at // y=1 and ending at y=-1, and sweeping across the range of z=0 to z=1. x stays zero. It's along this edge that we // need to duplicate our vertices - and provide the correct texture coordinates. size_t preFixupVertexCount = vertices.size(); for (size_t i = 0; i < preFixupVertexCount; ++i) { // This vertex is on the prime meridian if position.x and texcoord.u are both zero (allowing for small epsilon). bool isOnPrimeMeridian = XMVector2NearEqual( XMVectorSet(vertices[i].position.x, vertices[i].textureCoordinate.x, 0.0f, 0.0f), XMVectorZero(), XMVectorSplatEpsilon()); if (isOnPrimeMeridian) { size_t newIndex = vertices.size(); // the index of this vertex that we're about to add CheckIndexOverflow(newIndex); // copy this vertex, correct the texture coordinate, and add the vertex VertexPositionNormalTexture v = vertices[i]; v.textureCoordinate.x = 1.0f; vertices.push_back(v); // Now find all the triangles which contain this vertex and update them if necessary for (size_t j = 0; j < indices.size(); j += 3) { uint16_t* triIndex0 = &indices[j + 0]; uint16_t* triIndex1 = &indices[j + 1]; uint16_t* triIndex2 = &indices[j + 2]; if (*triIndex0 == i) { // nothing; just keep going } else if (*triIndex1 == i) { std::swap(triIndex0, triIndex1); // swap the pointers (not the values) } else if (*triIndex2 == i) { std::swap(triIndex0, triIndex2); // swap the pointers (not the values) } else { // this triangle doesn't use the vertex we're interested in continue; } // If we got to this point then triIndex0 is the pointer to the index to the vertex we're looking at assert(*triIndex0 == i); assert(*triIndex1 != i && *triIndex2 != i); // assume no degenerate triangles const VertexPositionNormalTexture& v0 = vertices[*triIndex0]; const VertexPositionNormalTexture& v1 = vertices[*triIndex1]; const VertexPositionNormalTexture& v2 = vertices[*triIndex2]; // check the other two vertices to see if we might need to fix this triangle if (abs(v0.textureCoordinate.x - v1.textureCoordinate.x) > 0.5f || abs(v0.textureCoordinate.x - v2.textureCoordinate.x) > 0.5f) { // yep; replace the specified index to point to the new, corrected vertex *triIndex0 = static_cast<uint16_t>(newIndex); } } } } // And one last fix we need to do: the poles. A common use-case of a sphere mesh is to map a rectangular texture onto // it. If that happens, then the poles become singularities which map the entire top and bottom rows of the texture // onto a single point. In general there's no real way to do that right. But to match the behavior of non-geodesic // spheres, we need to duplicate the pole vertex for every triangle that uses it. This will introduce seams near the // poles, but reduce stretching. auto fixPole = [&](size_t poleIndex) { auto poleVertex = vertices[poleIndex]; bool overwrittenPoleVertex = false; // overwriting the original pole vertex saves us one vertex for (size_t i = 0; i < indices.size(); i += 3) { // These pointers point to the three indices which make up this triangle. pPoleIndex is the pointer to the // entry in the index array which represents the pole index, and the other two pointers point to the other // two indices making up this triangle. uint16_t* pPoleIndex; uint16_t* pOtherIndex0; uint16_t* pOtherIndex1; if (indices[i + 0] == poleIndex) { pPoleIndex = &indices[i + 0]; pOtherIndex0 = &indices[i + 1]; pOtherIndex1 = &indices[i + 2]; } else if (indices[i + 1] == poleIndex) { pPoleIndex = &indices[i + 1]; pOtherIndex0 = &indices[i + 2]; pOtherIndex1 = &indices[i + 0]; } else if (indices[i + 2] == poleIndex) { pPoleIndex = &indices[i + 2]; pOtherIndex0 = &indices[i + 0]; pOtherIndex1 = &indices[i + 1]; } else { continue; } const auto& otherVertex0 = vertices[*pOtherIndex0]; const auto& otherVertex1 = vertices[*pOtherIndex1]; // Calculate the texcoords for the new pole vertex, add it to the vertices and update the index VertexPositionNormalTexture newPoleVertex = poleVertex; newPoleVertex.textureCoordinate.x = (otherVertex0.textureCoordinate.x + otherVertex1.textureCoordinate.x) / 2; newPoleVertex.textureCoordinate.y = poleVertex.textureCoordinate.y; if (!overwrittenPoleVertex) { vertices[poleIndex] = newPoleVertex; overwrittenPoleVertex = true; } else { CheckIndexOverflow(vertices.size()); *pPoleIndex = static_cast<uint16_t>(vertices.size()); vertices.push_back(newPoleVertex); } } }; fixPole(northPoleIndex); fixPole(southPoleIndex); // Create the primitive object. std::unique_ptr<GeometricPrimitive> primitive(new GeometricPrimitive()); primitive->pImpl->Initialize( vertices, indices, rhcoords); return primitive; }