I like to explain how to render a picture to a svg file (http://en.wikipedia.org/wiki/Scalable_Vector_Graphics). Before we start, please read Projecting 3D position and size to 2D and Raycasting to the polygon level.
The main idea of using svg is that you can scale the picture without loosing quality. You can also write directly svg images in HTML files, which is very helpful, for example, to include images into doxygen. The svg code can easily be written into the documentation tag, you don’t need a binary file.
Writing a screenshot to a binary file is something like
mWindow->writeContentsToFile(name + ".png");
Creating a svg file is much more work. We have to detect all visible polygons transform their coordinates to screen coordinates and write them to a text file.
Example from Ogre Procedural Geometry Library:
PNG:
SVG:
<?xml version="1.0" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"> <g style="stroke:black;stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path style="fill:#ffffff" d="M 15,45.861 81.5962,136.867 142.869,15 z" /> <path style="fill:#ffffff" d="M 81.5962,136.867 15,45.861 94.3075,248.919 z" /> <path style="fill:#ffffff" d="M 94.3075,248.919 15,45.861 40.9194,152.754 z" /> <path style="fill:#ffffff" d="M 142.869,15 81.5962,136.867 242.271,74.1127 z" /> <path style="fill:#ffffff" d="M 81.5962,136.867 94.3075,248.919 242.271,74.1127 z" /> <path style="fill:#ffffff" d="M 242.271,74.1127 94.3075,248.919 209.245,184.948 z" /> </g> </svg>
First we have to add a few objects to the header:
RaySceneQuery* mRaySceneQuery; PixelBox* mRenderWindowPixelBox; void GetMeshInformation(Entity *entity, size_t &vertex_count, Ogre::Vector3* &vertices, size_t &index_count, Ogre::uint32* &indices, const Ogre::Vector3 &position, const Ogre::Quaternion &orient, const Ogre::Vector3 &scale); struct SVGPATH { std::vector<Vector2> points; // Points of the path ColourValue color; // Fill color Real distance; // Distance to camera bool operator<(SVGPATH rhs) { return distance > rhs.distance; } bool operator>(SVGPATH rhs) { return distance < rhs.distance; } };
As you can see I use the GetMeshInformation function from Raycasting to the polygon level. I'll skip that code in this article.
In the beginning we have to initialize the ray query and the PixelBox object
// Init ray query & PixelBox mRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray(), Ogre::SceneManager::WORLD_GEOMETRY_TYPE_MASK); if (mRaySceneQuery == NULL) return; mRaySceneQuery->setSortByDistance(true); mRenderWindowPixelBox = new PixelBox (mWindow->getWidth(), mWindow->getHeight(), 1, PF_R8G8B8); mRenderWindowPixelBox->data = new BYTE[mRenderWindowPixelBox->getConsecutiveSize()];
The next step is to define a lot of local variables in your export function
// Local variables unsigned int width = mWindow->getWidth(); // screen width unsigned int height = mWindow->getHeight(); // screen height Real fw = 0.5f * width; // scale width Real fh = -0.5f * height; // scale height Real tw = (Real)width; // translate x direction Real th = (Real)height; // translate y direction std::vector<SVGPATH> pathList; // list of path elements std::ofstream svgfile; // output file Ray ray; // ray to detect polygon RaySceneQueryResult query_result; // ray query result Real closest_distance = -1.0f; // polygon distance to camera size_t vertex_count; // number of vertices size_t index_count; // number of indecies Vector3 *vertices; // pointer to vertex buffer Vector3 eyeSpacePos[3]; // variable for a triangle uint32 *indices; // pointer to index buffer Ogre::Image img; // image for colour detection Real dx = 1.0f / (Real)width; // ray query step size in x direction Real dy = 1.0f / (Real)height; // ray query step size in y direction
To get the color of a pixel we have to copy the rendered image to a buffer.
// Copy image mWindow->copyContentsToMemory(*mRenderWindowPixelBox); img.loadDynamicImage(static_cast<Ogre::uchar*>(mRenderWindowPixelBox->data),width, height, PF_R8G8B8); img.flipAroundX();
The simplest way (maybe not most efficient way) to get all visible polygons is to create a ray for every pixel on sceen and do a search
// Create rays for every pixel on screen for(Real x = 0.0f; x <= 1.0f; x += dx) for(Real y = 0.0f; y <= 1.0f; y += dy) { mCamera->getCameraToViewportRay(x, y, &ray); mRaySceneQuery->setRay(ray); if (mRaySceneQuery->execute().size() <= 0) continue; SVGPATH closest_result;
The next code part is a copy of Raycasting to the polygon level
// Search for triangles closest_distance = -1.0f; query_result = mRaySceneQuery->getLastResults(); for (size_t qr_idx = 0; qr_idx < query_result.size(); qr_idx++) { if ((closest_distance >= 0.0f) && (closest_distance < query_result[qr_idx].distance)) break; if ((query_result[qr_idx].movable != NULL) && (query_result[qr_idx].movable->getMovableType().compare("Entity") == 0)) { Ogre::Entity *pentity = static_cast<Ogre::Entity*>(query_result[qr_idx].movable); GetMeshInformation( pentity, vertex_count, vertices, index_count, indices, pentity->getParentNode()->_getDerivedPosition(), pentity->getParentNode()->_getDerivedOrientation(), pentity->getParentNode()->_getDerivedScale()); bool new_closest_found = false; for (int i = 0; i < static_cast<int>(index_count); i += 3) { std::pair<bool, Ogre::Real> hit = Ogre::Math::intersects(ray, vertices[indices[i]], vertices[indices[i+1]], vertices[indices[i+2]], true, false); if (hit.first) { if ((closest_distance < 0.0f) || (hit.second < closest_distance)) { closest_distance = hit.second;
In the next step, the coordinates of the detected triangles are transformed to screen coordinates. Also tried to detect the color of the pixel to set the fill color. A valid triangle must in front of the camera and must not have any dark color.
// Get triangle vetices and transform coordinates to screen system closest_result.points.clear(); for(int j = 0; j < 3; j++) { eyeSpacePos[j] = mCamera->getViewMatrix(true) * vertices[indices[i+j]]; Vector3 p = mCamera->getProjectionMatrix() * eyeSpacePos[j]; closest_result.points.push_back(Vector2(p.x * fw, p.y * fh)); } closest_result.distance = closest_distance; /* +Bugfix needed closest_result.color = img.getColourAt((size_t)(x * (Real)width), (size_t)(x * (Real)width), 0); -Bugfix needed */ closest_result.color = ColourValue::White; // Use white color instead of image color because it's not working correct now // Check is result is in front of the camera new_closest_found = (eyeSpacePos[0].z < 0.0f && eyeSpacePos[1].z < 0.0f && eyeSpacePos[2].z < 0.0f && closest_result.color.r > 0.4f && closest_result.color.g > 0.4f && closest_result.color.b > 0.4f);
The last code part of the triangle detection is used to add the closest triangle to a path list (each triangle is a path in svg) and detect the left top corner of the image.
// # } } } delete[] vertices; delete[] indices; // Check for a valid triangle and a valid distance if (new_closest_found && closest_distance >= 0.0f) { // Check if triangle already exists bool inlist = false; for(std::vector<SVGPATH>::iterator iter = pathList.begin(); iter != pathList.end(); iter++) if((*iter).points[0] == closest_result.points[0] && (*iter).points[1] == closest_result.points[1] && (*iter).points[2] == closest_result.points[2]) { inlist = true; break; } if(!inlist) { // Triangle does not exist pathList.push_back(closest_result); for(int j = 0; j < 3; j++) { // Find left top corner of the image tw = std::min(tw, closest_result.points[j].x); th = std::min(th, closest_result.points[j].y); } } } } } }
In the end we have to write out all path elements to a file
// Write SVG file if(pathList.size() > 0) { std::sort(pathList.begin(), pathList.end()); tw = -1.0f * tw + 15.0f; // Create a translation in x direction with a border th = -1.0f * th + 15.0f; // Create a translation in y direction with a border svgfile.open(name + ".svg"); svgfile << "<?xml version=\"1.0\" standalone=\"no\"?>" << std::endl; svgfile << "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" << width << "\" height=\"" << height << "\">" << std::endl; svgfile << " <g style=\"stroke:black;stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\">" << std::endl; char color[8]; for(std::vector<SVGPATH>::iterator iter = pathList.begin(); iter != pathList.end(); iter++) { #if _MSC_VER > 1310 sprintf_s(color, 8, #else sprintf(color, #endif "#%02x%02x%02x", (BYTE)(iter->color.r * 255.0f), (BYTE)(iter->color.g * 255.0f), (BYTE)(iter->color.b * 255.0f)); svgfile << " <path style=\"fill:" << color << "\" d=\"M"; for(std::vector<Vector2>::iterator pt = iter->points.begin(); pt != iter->points.end(); pt++) svgfile << " " << (pt->x + tw) << "," << (pt->y + th); svgfile << " z\" />" << std::endl; } svgfile << " </g>" << std::endl; svgfile << "</svg>" << std::endl; }
Discuss here: http://www.ogre3d.org/forums/viewtopic.php?f=5&t=71647