Skip to main content
Render to SVG        

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

Copy to clipboard
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:
Primitive Box
SVG:

Copy to clipboard
<?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:

Copy to clipboard
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

Copy to clipboard
// 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

Copy to clipboard
// 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
The color detection is not working now but I show a useful code fragment.

To get the color of a pixel we have to copy the rendered image to a buffer.

Copy to clipboard
// 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

Copy to clipboard
// 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

Copy to clipboard
// 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.

Copy to clipboard
// 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.

Copy to clipboard
// # } } } 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

Copy to clipboard
// 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